Public/Get-NewLetsEncryptCertificate.ps1
<#
.SYNOPSIS ACMESharpRoute53Automation is a PowerShell module which automates the ACMESharp process of obtaining SSL certificates from LetsEncrypt.org https://letsencrypt.org using Amazon AWS Route53 https://aws.amazon.com/route53/ to enable the DNS Domain Validation method .DESCRIPTION --- REQUIRES AWSPowerShell & ACMESharp PowerShell Modules for use!!! --- Automates the following into a single process: * Accepts Parameter input specifying one of the following: * DomainNames - FQDN(s) from one or more Route53 Hosted Zone * ZoneNames - DNS zone(s) to query for all ResourceRecordSets in a Route53 Hosted Zone * -ALL to query ALL ResourceRecordSets in ALL Route53 Hosted Zones * Registers Domains with LetsEncrypt * Requests Domain Validation Challenges * Adds/Updates Route53 DNS TXT records with Challenge Response values * Verifies DNS resolution of Challenge Response TXT records adhering to Exponential Backoff methodology * Submits for Challenge Response Verification * Generates one SAN Certificate per DNS Zone, adding all A & CNAME ResourceRecordSets as Subject Alternative Names * Waits for Certificate Generation adhering to Exponential Backoff methodology * Downloads SSL Certificates in various formats You will need to have permissions to read Route53 Hosted Zones and write to ResourceRecordSets This can be accomplished in multiple ways: Option 1: Run Get-NewLetsEncryptCertificate under a Windows User Profile which has AWS Credentials for an appropriate AWS IAM User stored via the Get-AWSCredentials cmdlet Set-AWSCredentials -AccessKey {AKIAIOSFODNN7EXAMPLE} -SecretKey {wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY} -StoreAs {MyProfileName} Option 2: Run Get-NewLetsEncryptCertificate on an EC2 Instance which has an IAM Role assigned with an appropriate IAM Policy. An example of a CloudFormation snippet with such a policy can be found at: https://github.com/RobBiddle/ACMESharpRoute53Automation/Route53.IAM.Policy.snippet.json.template .EXAMPLE Get-NewLetsEncryptCertificate -ALL -Staging -Contacts "me@privacy.net" This -ALL Example would generate SSL Certificates via LetsEncrypt for ALL of your Route53 A & CNAME records in ALL of your Route53 Hosted Zones. One SAN Certificate would be generated per zone, containing all of the records for that zone. The -Contacts Parameter should be passed an email address, or a list of addresses, for someone responsible for the domains Notice that this example is specifying **-Staging** which is a [Switch] Type Parameter; this will result in the certificates being generated from the LetsEncrypt Staging systems, which is meant for testing and allows for much higher request limits. It is suggested that you use the **-Staging** Switch until you are sure the output certificates are correct. **NOTE:** The LetsEncrypt Staging system will not generate a well known trusted certificates, instead the certificates are issued by "Fake LE Intermediate X1". For more details go here: https://letsencrypt.org/docs/staging-environment/ .EXAMPLE Get-NewLetsEncryptCertificate -ZoneNames "fabrikam.net" -Staging -CertPwd "test123" -Contacts "me@privacy.net" -OutputPath c:\temp\ This -ZoneNames Example would generate a single SSL SAN Certificate for fabrikam.net which would include all A & CNAME records in the fabrikam.net DNS zone as Subject Alternative Names. The -CertPwd Parameter is specified which is used to protect the .pfx formatted file. An -OutputPath is specified which determines where the certificate files will be exported, the default location is determined by the ACMESharp Vault which can be found by pasting this into a PowerShell console for the user running the cmdlet "$((Get-ACMEVaultProfile).VaultParameters.RootPath)" .EXAMPLE Get-NewLetsEncryptCertificate -DomainNames "fabrikam.net","contoso.com","www.acme.net","app2.contoso.com" -Contacts "me@privacy.net" This -DomainNames Example would end up generating 3 SSL Certificates, 1 for fabrikam.net, 1 for www.acme.net, and finally 1 for contoso.com which would be a SAN Certificate containing contoso.com & app2.contoso.com .INPUTS DomainNames and ZoneNames should be expressed in basic PowerShell array format like this: "domain1.com","domain1.com" .OUTPUTS Certificates will be output in various formats to OutputPath OR "$((Get-ACMEVaultProfile).VaultParameters.RootPath)\certs" by default A second copy of files will be output to a subfolder structure by ZoneName\Timestamp\ .NOTES Author: Robert D. Biddle https://github.com/RobBiddle https://github.com/RobBiddle/ACMESharpRoute53Automation ACMESharpRoute53Automation Copyright (C) 2017 Robert D. Biddle This program comes with ABSOLUTELY NO WARRANTY; for details type `"help Get-NewLetsEncryptCertificate -full`". This is free software, and you are welcome to redistribute it under certain conditions; for details type `"help Get-NewLetsEncryptCertificate -full`". This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <http://www.gnu.org/philosophy/why-not-lgpl.html>. #> function Get-NewLetsEncryptCertificate { [CmdletBinding( SupportsShouldProcess = $false, PositionalBinding = $false, HelpUri = 'https://github.com/RobBiddle/ACMESharpRoute53Automation/', ConfirmImpact = 'Medium')] [Alias()] [OutputType([System.Management.Automation.PSCustomObject])] Param ( # Get Certificates for ALL 'A' & 'CNAME' Records in ALL Route53 Hosted Zones #[Parameter(ParameterSetName = "Staging-ALL")] #[Parameter(ParameterSetName = "Production-ALL")] [Switch] $ALL, # Switch to LetEncrypt Staging instead of Production # [Parameter(ParameterSetName = "Staging")] # [Parameter(ParameterSetName = "Staging-ALL")] # [Parameter(ParameterSetName = "Staging-NAME")] # [Parameter(ParameterSetName = "Staging-ZONE")] [Switch] $Staging, # DNS Zone Name(s) filter, Certificates will be created for ALL 'A' & 'CNAME' Records in this Route53 Hosted Zone [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)] # [Parameter(ParameterSetName = "Production-ZONE")] # [Parameter(ParameterSetName = "Staging-ZONE")] [String[]] $ZoneNames, # Domain Name(s) filter. Only Create Certificates for these FQDNs. First name will be primary, remaining will be additional hosts in SAN certificate [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)] # [Parameter(ParameterSetName = "Production-NAME")] # [Parameter(ParameterSetName = "Staging-NAME")] [String[]] $DomainNames, # Path to Output Certificates [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false, ValueFromRemainingArguments = $false)] [String] $OutputPath, # Password to protect PFX file [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $false, ValueFromRemainingArguments = $false)] [ValidateNotNullOrEmpty()] [String] $CertPwd, # Email Address(es) for LetsEncrypt Registration - used for all Domains [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.Net.Mail.MailAddress[]] $Contacts, # ACME Challenge Type (This cmdlet only allows for DNS) [ValidateNotNullOrEmpty()] [ValidateSet('dns-01')] [String] $ChallengeType = 'dns-01' ) # Notice Write-Output `@" ACMESharpRoute53Automation Copyright (C) 2017 Robert D. Biddle This program comes with ABSOLUTELY NO WARRANTY; for details type `'help Get-NewLetsEncryptCertificate -full`'. This is free software, and you are welcome to redistribute it under certain conditions; for details type `'help Get-NewLetsEncryptCertificate -full`'" # Dot Source Private Function Files $Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue ) $Private | ForEach-Object { $FileToImport = $_ Try { .$FileToImport.FullName } Catch { Write-Error -Message "Failed to import: $($FileToImport.FullName): $_" } } if (!$DomainNames -and !$ZoneNames -and !$ALL) { Write-Warning "Either a list of -DomainNames, OR a list of -ZoneNames, OR -ALL must be specified" Throw "Incorrect Parameters Entered" } # Get All Route53 RecordSets $R53Records = @() $R53Records = Get-AllRoute53Records | Where-Object RecordType -eq 'A' $R53Records += Get-AllRoute53Records | Where-Object RecordType -eq 'CNAME' # Filter out Wildcard Records $R53Records = $R53Records | Where-Object RecordName -notlike "`\052*" # Filter out Zones if public NS Records are not Route53 NameServers $R53RecordsToExclude = @() $R53Records | Select-Object ZoneName -Unique | ForEach-Object { $CurrentR53Record = $_ if ( (Resolve-DnsName $CurrentR53Record.ZoneName -Type NS -Server 8.8.8.8).NameHost -notlike "*awsdns*" ) { $R53RecordsToExclude += $CurrentR53Record } } $R53Records = $R53Records | Where-Object ZoneName -NotIn $R53RecordsToExclude.ZoneName # Determine which records to process $HostAndZoneList = @() if ($ZoneNames) { $NamesToMatch = $ZoneNames } if ($DomainNames) { $NamesToMatch = $DomainNames } if ($ALL) { $NamesToMatch = $R53Records.RecordName } $NamesToMatch | ForEach-Object { $CurrentName = ($_).Trim(".") # Parse the ChallengeRecordName to split root zone from hostname $splitFQDN = $CurrentName.Split(".") $DnsZoneName = "$($splitFQDN[($splitFQDN.count -2)]).$($splitFQDN[($splitFQDN.count -1)])" $DnsHost = '' $splitFQDN[0 .. ($splitFQDN.count - 3)] | ForEach-Object { $DnsHost = -join ("$DnsHost", "$_", ".") } $DnsHost = "$DnsHost".Trim(".") $HostAndZoneList += [PSCustomObject]@{ HostName = $DnsHost FQDN = $CurrentName Zone = $DnsZoneName } } $R53RecordsMatchingDomains = @() if ($ZoneNames -or $ALL) { $HostAndZoneList | Select-Object Zone -Unique | ForEach-Object { $CurrentHostOrZone = $_ $R53RecordsMatchingDomains += $R53Records | Where-Object ZoneName -like "$($CurrentHostOrZone.Zone)." } } if ($DomainNames) { $HostAndZoneList | ForEach-Object { $CurrentHostOrZone = $_ $R53RecordsMatchingDomains += $R53Records | Where-Object ZoneName -like "$($CurrentHostOrZone.Zone)." | Where-Object RecordName -like "$($CurrentHostOrZone.FQDN)." } } $R53RecordsToProcess = @() $R53RecordsToProcess = $R53RecordsMatchingDomains # Process each Domain Zone and Associated Host Records $R53RecordsToProcess | Select-Object ZoneName -Unique | ForEach-Object { Write-Output "Processing Records in Zone: $($_.ZoneName)" $CurrentZoneRecords = $R53RecordsToProcess | Where-Object ZoneName -eq $_.ZoneName # Build List of Strings for use in DomainNames Parameter $RecordNames = @() $CurrentZoneRecords | ForEach-Object { $RecordNames += ($_.RecordName).Trim(".") } Write-Output "Processing Hostnames:" Write-Output $RecordNames # Build Parameter Sets for Functions $Params = @{ DomainNames = $RecordNames Contacts = $Contacts ChallengeType = $ChallengeType } if ($Staging) { $Params += @{Staging = $true} } $Requests = Register-LetsEncryptCertificateRequest @Params Push-LetsEncryptChallengeToRoute53 -InputObject $Requests $Params2 = @{ InputObject = $Requests } if ($CertPwd) { $Params2 += @{CertPwd = $CertPwd} } if ($OutputPath) { $Params2 += @{OutputPath = $OutputPath} } Complete-LetsEncryptCertificateRequest @Params2 } } |