diff --git a/step-templates/letsencrypt-cloudflare.json b/step-templates/letsencrypt-cloudflare.json index 916ebd648..8b22a919e 100644 --- a/step-templates/letsencrypt-cloudflare.json +++ b/step-templates/letsencrypt-cloudflare.json @@ -9,7 +9,7 @@ "Properties": { "Octopus.Action.Script.ScriptSource": "Inline", "Octopus.Action.Script.Syntax": "PowerShell", - "Octopus.Action.Script.ScriptBody": "###############################################################################\n# TLS 1.2\n###############################################################################\n[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\n\n###############################################################################\n# Required Modules folder\n###############################################################################\nWrite-Host \"Checking for required powershell modules folder\"\n$ModulesFolder = \"$HOME\\Documents\\WindowsPowerShell\\Modules\"\nif ($PSEdition -eq \"Core\") {\n if ($PSVersionTable.Platform -eq \"Unix\") {\n $ModulesFolder = \"$HOME/.local/share/powershell/Modules\"\n }\n else {\n $ModulesFolder = \"$HOME\\Documents\\PowerShell\\Modules\"\n }\n}\n$PSModuleFolderExists = (Test-Path $ModulesFolder)\nif ($PSModuleFolderExists -eq $False) {\n\tWrite-Host \"Creating directory: $ModulesFolder\"\n\tNew-Item $ModulesFolder -ItemType Directory -Force\n $env:PSModulePath = $ModulesFolder + [System.IO.Path]::PathSeparator + $env:PSModulePath\n}\n\n###############################################################################\n# Required Modules\n###############################################################################\nWrite-Host \"Checking for required modules.\"\n$required_posh_acme_version = 3.12.0\n$module_check = Get-Module -ListAvailable -Name Posh-Acme | Where-Object { $_.Version -ge $required_posh_acme_version }\n\nif (-not ($module_check)) {\n Write-Host \"Ensuring NuGet provider is bootstrapped.\"\n Get-PackageProvider NuGet -ForceBootstrap | Out-Null\n Write-Host \"Installing Posh-ACME.\"\n Install-Module -Name Posh-ACME -MinimumVersion 3.12.0 -Scope CurrentUser -Force\n}\n\nImport-Module Posh-ACME\n\n###############################################################################\n# Constants\n###############################################################################\n$LE_Cloudflare_CertificateDomain = $OctopusParameters[\"LE_Cloudflare_CertificateDomain\"]\n$LE_Cloudflare_CertificateName = \"Lets Encrypt - $($LE_Cloudflare_CertificateDomain)\"\n\n# Issuer used in a cert could be one of multiple, including ones no longer supported by Let's Encrypt\n$LE_Cloudflare_Fake_Issuers = @(\"Fake LE Intermediate X1\", \"(STAGING) Artificial Apricot R3\", \"(STAGING) Ersatz Edamame E1\", \"(STAGING) Pseudo Plum E5\", \"(STAGING) False Fennel E6\", \"(STAGING) Puzzling Parsnip E7\", \"(STAGING) Mysterious Mulberry E8\", \"(STAGING) Fake Fig E9\", \"(STAGING) Counterfeit Cashew R10\", \"(STAGING) Wannabe Watercress R11\", \"(STAGING) Riddling Rhubarb R12\", \"(STAGING) Tenuous Tomato R13\", \"(STAGING) Not Nectarine R14\")\n$LE_Cloudflare_Issuers = @(\"Let's Encrypt Authority X3\", \"E1\", \"E2\", \"E7\", \"E8\", \"R3\", \"R4\", \"R5\", \"R6\", \"R10\", \"R11\", \"R12\", \"R13\")\n\n###############################################################################\n# Helpers\n###############################################################################\nfunction Get-WebRequestErrorBody {\n param (\n $RequestError\n )\n\n # Powershell < 6 you can read the Exception\n if ($PSVersionTable.PSVersion.Major -lt 6) {\n if ($RequestError.Exception.Response) {\n $reader = New-Object System.IO.StreamReader($RequestError.Exception.Response.GetResponseStream())\n $reader.BaseStream.Position = 0\n $reader.DiscardBufferedData()\n $response = $reader.ReadToEnd()\n\n return $response | ConvertFrom-Json\n }\n }\n else {\n return $RequestError.ErrorDetails.Message\n }\n}\n\n###############################################################################\n# Functions\n###############################################################################\nfunction Get-LetsEncryptCertificate {\n Write-Debug \"Entering: Get-LetsEncryptCertificate\"\n\n if ($OctopusParameters[\"LE_Cloudflare_Use_Staging\"] -eq $True) {\n Write-Host \"Using Lets Encrypt Server: Staging\"\n Set-PAServer LE_STAGE;\n }\n else {\n Write-Host \"Using Lets Encrypt Server: Production\"\n Set-PAServer LE_PROD;\n }\n\n # Clobber account if it exists.\n $le_account = Get-PAAccount\n if ($le_account) {\n Remove-PAAccount $le_account.Id -Force\n }\n\n # Cloudflare API tokens require some special wrangling.\n $cloudflare_token = ConvertTo-SecureString -String $OctopusParameters[\"LE_Cloudflare_PrimaryToken\"] -AsPlainText -Force\n $cloudflare_args = @{\n CFToken = $cloudflare_token\n }\n\n if ($OctopusParameters[\"LE_Cloudflare_SecondaryToken\"]) {\n Write-Debug \"LE_Cloudflare_SecondaryToken has a value. Passing it to the Cloudflare DNS plugin as a Read All Token.\"\n $cloudflare_token_secondary = ConvertTo-SecureString -String $OctopusParameters[\"LE_Cloudflare_SecondaryToken\"] -AsPlainText -Force\n $cloudflare_args.CFTokenReadAll = $cloudflare_token_secondary\n }\n\n try {\n\n $DnsPlugins = @(\"Cloudflare\")\n $DomainList = @($LE_Cloudflare_CertificateDomain)\n \n # If domain is a wildcard e.g. *.example-domain.com, check if a SAN has been requested e.g. example-domain.com.\n if ($LE_Cloudflare_CertificateDomain -match \"\\*.\" -and $OctopusParameters[\"LE_Cloudflare_CreateWildcardSAN\"] -eq $True) {\n $LE_Cloudflare_Certificate_SAN = $LE_Cloudflare_CertificateDomain.Replace(\"*.\",\"\")\n $DomainList += $LE_Cloudflare_Certificate_SAN\n # Include additional DnsPlugin of same type to suppress warning.\n $DnsPluginList += \"Cloudflare\"\n }\n\n $Cert_Params = @{\n Domain = $DomainList\n AcceptTOS = $True;\n Contact = $OctopusParameters[\"LE_Cloudflare_ContactEmailAddress\"];\n DnsPlugin = $DnsPlugins;\n PluginArgs = $cloudflare_args;\n PfxPass = $OctopusParameters[\"LE_Cloudflare_PfxPassword\"];\n Force = $True;\n }\n\n return New-PACertificate @Cert_Params\n }\n catch {\n Write-Host \"Failed to Create Certificate. Error Message: $($_.Exception.Message). See Debug output for details.\"\n Write-Debug (Get-WebRequestErrorBody -RequestError $_)\n exit 1\n }\n}\n\nfunction Get-OctopusCertificates {\n Write-Debug \"Entering: Get-OctopusCertificates\"\n\n $octopus_uri = $OctopusParameters[\"Octopus.Web.ServerUri\"]\n $octopus_space_id = $OctopusParameters[\"Octopus.Space.Id\"]\n $octopus_headers = @{ \"X-Octopus-ApiKey\" = $OctopusParameters[\"LE_Cloudflare_Octopus_APIKey\"] }\n $octopus_certificates_uri = \"$octopus_uri/api/$octopus_space_id/certificates?search=$($LE_Cloudflare_CertificateDomain)\"\n\n try {\n # Get a list of certificates that match our domain search criteria.\n $certificates_search = Invoke-WebRequest -Uri $octopus_certificates_uri -Method Get -Headers $octopus_headers -UseBasicParsing -ErrorAction Stop | ConvertFrom-Json | Select-Object -ExpandProperty Items\n\n # We don't want to confuse Production and Staging Lets Encrypt Certificates.\n $possible_issuers = $LE_Cloudflare_Issuers\n if ($OctopusParameters[\"LE_Cloudflare_Use_Staging\"] -eq $True) {\n $possible_issuers = $LE_Cloudflare_Fake_Issuers\n }\n\n return $certificates_search | Where-Object {\n $_.SubjectCommonName -eq $LE_Cloudflare_CertificateDomain -and\n $possible_issuers -contains $_.IssuerCommonName -and\n $null -eq $_.ReplacedBy -and\n $null -eq $_.Archived\n }\n }\n catch {\n Write-Host \"Could not retrieve certificates from Octopus Deploy. Error: $($_.Exception.Message). See Debug output for details.\"\n Write-Debug (Get-WebRequestErrorBody -RequestError $_)\n exit 1\n }\n}\n\nfunction Publish-OctopusCertificate {\n param (\n [string] $JsonBody\n )\n\n Write-Debug \"Entering: Publish-OctopusCertificate\"\n\n if (-not ($JsonBody)) {\n Write-Host \"Existing Certificate is required.\"\n exit 1\n }\n\n $octopus_uri = $OctopusParameters[\"Octopus.Web.ServerUri\"]\n $octopus_space_id = $OctopusParameters[\"Octopus.Space.Id\"]\n $octopus_headers = @{ \"X-Octopus-ApiKey\" = $OctopusParameters[\"LE_Cloudflare_Octopus_APIKey\"] }\n $octopus_certificates_uri = \"$octopus_uri/api/$octopus_space_id/certificates\"\n\n try {\n Invoke-WebRequest -Uri $octopus_certificates_uri -Method Post -Headers $octopus_headers -Body $JsonBody -UseBasicParsing\n Write-Host \"Published $($LE_Cloudflare_CertificateDomain) certificate to the Octopus Deploy Certificate Store.\"\n }\n catch {\n Write-Host \"Failed to publish $($LE_Cloudflare_CertificateDomain) certificate. Error: $($_.Exception.Message). See Debug output for details.\"\n Write-Debug (Get-WebRequestErrorBody -RequestError $_)\n exit 1\n }\n}\n\nfunction Update-OctopusCertificate {\n param (\n [string]$Certificate_Id,\n [string]$JsonBody\n )\n\n Write-Debug \"Entering: Update-OctopusCertificate\"\n\n if (-not ($Certificate_Id -and $JsonBody)) {\n Write-Host \"Existing Certificate Id and a replace Certificate are required.\"\n exit 1\n }\n\n $octopus_uri = $OctopusParameters[\"Octopus.Web.ServerUri\"]\n $octopus_space_id = $OctopusParameters[\"Octopus.Space.Id\"]\n $octopus_headers = @{ \"X-Octopus-ApiKey\" = $OctopusParameters[\"LE_Cloudflare_Octopus_APIKey\"] }\n $octopus_certificates_uri = \"$octopus_uri/api/$octopus_space_id/certificates/$Certificate_Id/replace\"\n\n try {\n Invoke-WebRequest -Uri $octopus_certificates_uri -Method Post -Headers $octopus_headers -Body $JsonBody -UseBasicParsing\n Write-Host \"Replaced $($LE_Cloudflare_CertificateDomain) certificate in the Octopus Deploy Certificate Store.\"\n }\n catch {\n Write-Error \"Failed to replace $($LE_Cloudflare_CertificateDomain) certificate. Error: $($_.Exception.Message)\"\n exit 1\n }\n}\n\nfunction Get-NewCertificatePFXAsJson {\n param (\n $Certificate\n )\n\n Write-Debug \"Entering: Get-NewCertificatePFXAsJson\"\n\n if (-not ($Certificate)) {\n Write-Host \"Certificate is required.\"\n Exit 1\n }\n\n [Byte[]]$certificate_buffer = [System.IO.File]::ReadAllBytes($Certificate.PfxFullChain)\n $certificate_base64 = [convert]::ToBase64String($certificate_buffer)\n\n $certificate_body = @{\n Name = \"$LE_Cloudflare_CertificateName\";\n Notes = \"\";\n CertificateData = @{\n HasValue = $true;\n NewValue = $certificate_base64;\n };\n Password = @{\n HasValue = $true;\n NewValue = $OctopusParameters[\"LE_Cloudflare_PfxPassword\"];\n };\n }\n\n return $certificate_body | ConvertTo-Json\n}\n\nfunction Get-ReplaceCertificatePFXAsJson {\n param (\n $Certificate\n )\n\n Write-Debug \"Entering: Get-ReplaceCertificatePFXAsJson\"\n\n if (-not ($Certificate)) {\n Write-Host \"Certificate is required.\"\n Exit 1\n }\n\n [Byte[]]$certificate_buffer = [System.IO.File]::ReadAllBytes($Certificate.PfxFullChain)\n $certificate_base64 = [convert]::ToBase64String($certificate_buffer)\n\n $certificate_body = @{\n CertificateData = $certificate_base64;\n Password = $OctopusParameters[\"LE_Cloudflare_PfxPassword\"];\n }\n\n return $certificate_body | ConvertTo-Json\n}\n\n###############################################################################\n# DO THE THING | MAIN |\n###############################################################################\nWrite-Debug \"Do the Thing\"\n\nWrite-Host \"Checking for existing Lets Encrypt Certificates in the Octopus Deploy Certificates Store.\"\n$certificates = Get-OctopusCertificates\n\n# Check for PFX & PEM\nif ($certificates) {\n\n # Handle weird behavior between Powershell 5 and Powershell 6+\n $certificate_count = 1\n if ($certificates.Count -ge 1) {\n $certificate_count = $certificates.Count\n }\n\n Write-Host \"Found $certificate_count for $($LE_Cloudflare_CertificateDomain).\"\n Write-Host \"Checking to see if any expire within $($OctopusParameters[\"LE_Cloudflare_ReplaceIfExpiresInDays\"]) days.\"\n\n # Check Expiry Dates\n $expiring_certificates = $certificates | Where-Object { [DateTime]$_.NotAfter -lt (Get-Date).AddDays($OctopusParameters[\"LE_Cloudflare_ReplaceIfExpiresInDays\"]) }\n\n if ($expiring_certificates) {\n Write-Host \"Found certificates that expire with $($OctopusParameters[\"LE_Cloudflare_ReplaceIfExpiresInDays\"]) days. Requesting new certificates for $($LE_Cloudflare_CertificateDomain) from Lets Encrypt\"\n $le_certificate = Get-LetsEncryptCertificate\n\n # PFX\n $existing_certificate = $certificates | Where-Object { $_.CertificateDataFormat -eq \"Pkcs12\" } | Select-Object -First 1\n $certificate_as_json = Get-ReplaceCertificatePFXAsJson -Certificate $le_certificate\n Update-OctopusCertificate -Certificate_Id $existing_certificate.Id -JsonBody $certificate_as_json\n }\n else {\n Write-Host \"Nothing to do here...\"\n }\n\n exit 0\n}\n\n# No existing Certificates - Lets get some new ones.\nWrite-Host \"No existing certificates found for $($LE_Cloudflare_CertificateDomain).\"\nWrite-Host \"Request New Certificate for $($LE_Cloudflare_CertificateDomain) from Lets Encrypt\"\n\n# New Certificate..\n$le_certificate = Get-LetsEncryptCertificate\n\nWrite-Host \"Publishing: LetsEncrypt - $($LE_Cloudflare_CertificateDomain) (PFX)\"\n$certificate_as_json = Get-NewCertificatePFXAsJson -Certificate $le_certificate\nPublish-OctopusCertificate -JsonBody $certificate_as_json\n\nWrite-Host \"GREAT SUCCESS\"\n", + "Octopus.Action.Script.ScriptBody": "###############################################################################\n# TLS 1.2\n###############################################################################\n[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\n\n###############################################################################\n# Required Modules folder\n###############################################################################\nWrite-Host \"Checking for required powershell modules folder\"\n$ModulesFolder = \"$HOME\\Documents\\WindowsPowerShell\\Modules\"\nif ($PSEdition -eq \"Core\") {\n if ($PSVersionTable.Platform -eq \"Unix\") {\n $ModulesFolder = \"$HOME/.local/share/powershell/Modules\"\n }\n else {\n $ModulesFolder = \"$HOME\\Documents\\PowerShell\\Modules\"\n }\n}\n$PSModuleFolderExists = (Test-Path $ModulesFolder)\nif ($PSModuleFolderExists -eq $False) {\n\tWrite-Host \"Creating directory: $ModulesFolder\"\n\tNew-Item $ModulesFolder -ItemType Directory -Force\n $env:PSModulePath = $ModulesFolder + [System.IO.Path]::PathSeparator + $env:PSModulePath\n}\n\n###############################################################################\n# Required Modules\n###############################################################################\nWrite-Host \"Checking for required modules.\"\n$required_posh_acme_version = 3.12.0\n$module_check = Get-Module -ListAvailable -Name Posh-Acme | Where-Object { $_.Version -ge $required_posh_acme_version }\n\nif (-not ($module_check)) {\n Write-Host \"Ensuring NuGet provider is bootstrapped.\"\n Get-PackageProvider NuGet -ForceBootstrap | Out-Null\n Write-Host \"Installing Posh-ACME.\"\n Install-Module -Name Posh-ACME -MinimumVersion 3.12.0 -Scope CurrentUser -Force\n}\n\nImport-Module Posh-ACME\n\n###############################################################################\n# Constants\n###############################################################################\n$LE_Cloudflare_CertificateDomain = $OctopusParameters[\"LE_Cloudflare_CertificateDomain\"]\n$LE_Cloudflare_CertificateName = \"Lets Encrypt - $($LE_Cloudflare_CertificateDomain)\"\n\n# Issuer used in a cert could be one of multiple, including ones no longer supported by Let's Encrypt\n$LE_Cloudflare_Fake_Issuers = @(\"Fake LE Intermediate X1\", \"(STAGING) Artificial Apricot R3\", \"(STAGING) Ersatz Edamame E1\", \"(STAGING) Pseudo Plum E5\", \"(STAGING) False Fennel E6\", \"(STAGING) Puzzling Parsnip E7\", \"(STAGING) Mysterious Mulberry E8\", \"(STAGING) Fake Fig E9\", \"(STAGING) Counterfeit Cashew R10\", \"(STAGING) Wannabe Watercress R11\", \"(STAGING) Riddling Rhubarb R12\", \"(STAGING) Tenuous Tomato R13\", \"(STAGING) Not Nectarine R14\")\n$LE_Cloudflare_Issuers = @(\"Let's Encrypt Authority X3\", \"E1\", \"E2\", \"E7\", \"E8\", \"R3\", \"R4\", \"R5\", \"R6\", \"R10\", \"R11\", \"R12\", \"R13\")\n\n###############################################################################\n# Helpers\n###############################################################################\nfunction Get-WebRequestErrorBody {\n param (\n $RequestError\n )\n\n # Powershell < 6 you can read the Exception\n if ($PSVersionTable.PSVersion.Major -lt 6) {\n if ($RequestError.Exception.Response) {\n $reader = New-Object System.IO.StreamReader($RequestError.Exception.Response.GetResponseStream())\n $reader.BaseStream.Position = 0\n $reader.DiscardBufferedData()\n $response = $reader.ReadToEnd()\n\n return $response | ConvertFrom-Json\n }\n }\n else {\n return $RequestError.ErrorDetails.Message\n }\n}\n\n###############################################################################\n# Functions\n###############################################################################\nfunction Get-LetsEncryptCertificate {\n Write-Debug \"Entering: Get-LetsEncryptCertificate\"\n\n if ($OctopusParameters[\"LE_Cloudflare_Use_Staging\"] -eq $True) {\n Write-Host \"Using Lets Encrypt Server: Staging\"\n Set-PAServer LE_STAGE;\n }\n else {\n Write-Host \"Using Lets Encrypt Server: Production\"\n Set-PAServer LE_PROD;\n }\n\n # Clobber account if it exists.\n $le_account = Get-PAAccount\n if ($le_account) {\n Remove-PAAccount $le_account.Id -Force\n }\n\n # Cloudflare API tokens require some special wrangling.\n $cloudflare_token = ConvertTo-SecureString -String $OctopusParameters[\"LE_Cloudflare_PrimaryToken\"] -AsPlainText -Force\n $cloudflare_args = @{\n CFToken = $cloudflare_token\n }\n\n if ($OctopusParameters[\"LE_Cloudflare_SecondaryToken\"]) {\n Write-Debug \"LE_Cloudflare_SecondaryToken has a value. Passing it to the Cloudflare DNS plugin as a Read All Token.\"\n $cloudflare_token_secondary = ConvertTo-SecureString -String $OctopusParameters[\"LE_Cloudflare_SecondaryToken\"] -AsPlainText -Force\n $cloudflare_args.CFTokenReadAll = $cloudflare_token_secondary\n }\n\n try {\n\n $DnsPlugins = @(\"Cloudflare\")\n $DomainList = @($LE_Cloudflare_CertificateDomain)\n \n # If domain is a wildcard e.g. *.example-domain.com, check if a SAN has been requested e.g. example-domain.com.\n if ($LE_Cloudflare_CertificateDomain -match \"\\*.\" -and $OctopusParameters[\"LE_Cloudflare_CreateWildcardSAN\"] -eq $True) {\n $LE_Cloudflare_Certificate_SAN = $LE_Cloudflare_CertificateDomain.Replace(\"*.\",\"\")\n $DomainList += $LE_Cloudflare_Certificate_SAN\n # Include additional DnsPlugin of same type to suppress warning.\n $DnsPlugins += \"Cloudflare\"\n }\n\n $Cert_Params = @{\n Domain = $DomainList\n AcceptTOS = $True;\n Contact = $OctopusParameters[\"LE_Cloudflare_ContactEmailAddress\"];\n DnsPlugin = $DnsPlugins;\n PluginArgs = $cloudflare_args;\n PfxPass = $OctopusParameters[\"LE_Cloudflare_PfxPassword\"];\n Force = $True;\n }\n\n return New-PACertificate @Cert_Params\n }\n catch {\n Write-Host \"Failed to Create Certificate. Error Message: $($_.Exception.Message). See Debug output for details.\"\n Write-Debug (Get-WebRequestErrorBody -RequestError $_)\n exit 1\n }\n}\n\nfunction Get-OctopusCertificates {\n Write-Debug \"Entering: Get-OctopusCertificates\"\n\n $octopus_uri = $OctopusParameters[\"Octopus.Web.ServerUri\"]\n $octopus_space_id = $OctopusParameters[\"Octopus.Space.Id\"]\n $octopus_headers = @{ \"X-Octopus-ApiKey\" = $OctopusParameters[\"LE_Cloudflare_Octopus_APIKey\"] }\n $octopus_certificates_uri = \"$octopus_uri/api/$octopus_space_id/certificates?search=$($LE_Cloudflare_CertificateDomain)\"\n\n try {\n # Get a list of certificates that match our domain search criteria.\n $certificates_search = Invoke-WebRequest -Uri $octopus_certificates_uri -Method Get -Headers $octopus_headers -UseBasicParsing -ErrorAction Stop | ConvertFrom-Json | Select-Object -ExpandProperty Items\n\n # We don't want to confuse Production and Staging Lets Encrypt Certificates.\n $possible_issuers = $LE_Cloudflare_Issuers\n if ($OctopusParameters[\"LE_Cloudflare_Use_Staging\"] -eq $True) {\n $possible_issuers = $LE_Cloudflare_Fake_Issuers\n }\n\n return $certificates_search | Where-Object {\n $_.SubjectCommonName -eq $LE_Cloudflare_CertificateDomain -and\n $possible_issuers -contains $_.IssuerCommonName -and\n $null -eq $_.ReplacedBy -and\n $null -eq $_.Archived\n }\n }\n catch {\n Write-Host \"Could not retrieve certificates from Octopus Deploy. Error: $($_.Exception.Message). See Debug output for details.\"\n Write-Debug (Get-WebRequestErrorBody -RequestError $_)\n exit 1\n }\n}\n\nfunction Publish-OctopusCertificate {\n param (\n [string] $JsonBody\n )\n\n Write-Debug \"Entering: Publish-OctopusCertificate\"\n\n if (-not ($JsonBody)) {\n Write-Host \"Existing Certificate is required.\"\n exit 1\n }\n\n $octopus_uri = $OctopusParameters[\"Octopus.Web.ServerUri\"]\n $octopus_space_id = $OctopusParameters[\"Octopus.Space.Id\"]\n $octopus_headers = @{ \"X-Octopus-ApiKey\" = $OctopusParameters[\"LE_Cloudflare_Octopus_APIKey\"] }\n $octopus_certificates_uri = \"$octopus_uri/api/$octopus_space_id/certificates\"\n\n try {\n Invoke-WebRequest -Uri $octopus_certificates_uri -Method Post -Headers $octopus_headers -Body $JsonBody -UseBasicParsing\n Write-Host \"Published $($LE_Cloudflare_CertificateDomain) certificate to the Octopus Deploy Certificate Store.\"\n }\n catch {\n Write-Host \"Failed to publish $($LE_Cloudflare_CertificateDomain) certificate. Error: $($_.Exception.Message). See Debug output for details.\"\n Write-Debug (Get-WebRequestErrorBody -RequestError $_)\n exit 1\n }\n}\n\nfunction Update-OctopusCertificate {\n param (\n [string]$Certificate_Id,\n [string]$JsonBody\n )\n\n Write-Debug \"Entering: Update-OctopusCertificate\"\n\n if (-not ($Certificate_Id -and $JsonBody)) {\n Write-Host \"Existing Certificate Id and a replace Certificate are required.\"\n exit 1\n }\n\n $octopus_uri = $OctopusParameters[\"Octopus.Web.ServerUri\"]\n $octopus_space_id = $OctopusParameters[\"Octopus.Space.Id\"]\n $octopus_headers = @{ \"X-Octopus-ApiKey\" = $OctopusParameters[\"LE_Cloudflare_Octopus_APIKey\"] }\n $octopus_certificates_uri = \"$octopus_uri/api/$octopus_space_id/certificates/$Certificate_Id/replace\"\n\n try {\n Invoke-WebRequest -Uri $octopus_certificates_uri -Method Post -Headers $octopus_headers -Body $JsonBody -UseBasicParsing\n Write-Host \"Replaced $($LE_Cloudflare_CertificateDomain) certificate in the Octopus Deploy Certificate Store.\"\n }\n catch {\n Write-Error \"Failed to replace $($LE_Cloudflare_CertificateDomain) certificate. Error: $($_.Exception.Message)\"\n exit 1\n }\n}\n\nfunction Get-NewCertificatePFXAsJson {\n param (\n $Certificate\n )\n\n Write-Debug \"Entering: Get-NewCertificatePFXAsJson\"\n\n if (-not ($Certificate)) {\n Write-Host \"Certificate is required.\"\n Exit 1\n }\n\n [Byte[]]$certificate_buffer = [System.IO.File]::ReadAllBytes($Certificate.PfxFullChain)\n $certificate_base64 = [convert]::ToBase64String($certificate_buffer)\n\n $certificate_body = @{\n Name = \"$LE_Cloudflare_CertificateName\";\n Notes = \"\";\n CertificateData = @{\n HasValue = $true;\n NewValue = $certificate_base64;\n };\n Password = @{\n HasValue = $true;\n NewValue = $OctopusParameters[\"LE_Cloudflare_PfxPassword\"];\n };\n }\n\n return $certificate_body | ConvertTo-Json\n}\n\nfunction Get-ReplaceCertificatePFXAsJson {\n param (\n $Certificate\n )\n\n Write-Debug \"Entering: Get-ReplaceCertificatePFXAsJson\"\n\n if (-not ($Certificate)) {\n Write-Host \"Certificate is required.\"\n Exit 1\n }\n\n [Byte[]]$certificate_buffer = [System.IO.File]::ReadAllBytes($Certificate.PfxFullChain)\n $certificate_base64 = [convert]::ToBase64String($certificate_buffer)\n\n $certificate_body = @{\n CertificateData = $certificate_base64;\n Password = $OctopusParameters[\"LE_Cloudflare_PfxPassword\"];\n }\n\n return $certificate_body | ConvertTo-Json\n}\n\n###############################################################################\n# DO THE THING | MAIN |\n###############################################################################\nWrite-Debug \"Do the Thing\"\n\nWrite-Host \"Checking for existing Lets Encrypt Certificates in the Octopus Deploy Certificates Store.\"\n$certificates = Get-OctopusCertificates\n\n# Check for PFX & PEM\nif ($certificates) {\n\n # Handle weird behavior between Powershell 5 and Powershell 6+\n $certificate_count = 1\n if ($certificates.Count -ge 1) {\n $certificate_count = $certificates.Count\n }\n\n Write-Host \"Found $certificate_count for $($LE_Cloudflare_CertificateDomain).\"\n Write-Host \"Checking to see if any expire within $($OctopusParameters[\"LE_Cloudflare_ReplaceIfExpiresInDays\"]) days.\"\n\n # Check Expiry Dates\n $expiring_certificates = $certificates | Where-Object { [DateTime]$_.NotAfter -lt (Get-Date).AddDays($OctopusParameters[\"LE_Cloudflare_ReplaceIfExpiresInDays\"]) }\n\n if ($expiring_certificates) {\n Write-Host \"Found certificates that expire with $($OctopusParameters[\"LE_Cloudflare_ReplaceIfExpiresInDays\"]) days. Requesting new certificates for $($LE_Cloudflare_CertificateDomain) from Lets Encrypt\"\n $le_certificate = Get-LetsEncryptCertificate\n\n # PFX\n $existing_certificate = $certificates | Where-Object { $_.CertificateDataFormat -eq \"Pkcs12\" } | Select-Object -First 1\n $certificate_as_json = Get-ReplaceCertificatePFXAsJson -Certificate $le_certificate\n Update-OctopusCertificate -Certificate_Id $existing_certificate.Id -JsonBody $certificate_as_json\n }\n else {\n Write-Host \"Nothing to do here...\"\n }\n\n exit 0\n}\n\n# No existing Certificates - Lets get some new ones.\nWrite-Host \"No existing certificates found for $($LE_Cloudflare_CertificateDomain).\"\nWrite-Host \"Request New Certificate for $($LE_Cloudflare_CertificateDomain) from Lets Encrypt\"\n\n# New Certificate..\n$le_certificate = Get-LetsEncryptCertificate\n\nWrite-Host \"Publishing: LetsEncrypt - $($LE_Cloudflare_CertificateDomain) (PFX)\"\n$certificate_as_json = Get-NewCertificatePFXAsJson -Certificate $le_certificate\nPublish-OctopusCertificate -JsonBody $certificate_as_json\n\nWrite-Host \"GREAT SUCCESS\"\n", "Octopus.Action.SubstituteInFiles.Enabled": "True" }, "Parameters": [{