diff --git a/Config/Config.ps1 b/Config/Config.ps1 index 75fe773..885a45a 100644 --- a/Config/Config.ps1 +++ b/Config/Config.ps1 @@ -5,4 +5,10 @@ $GLOBAL:AcemeContact = "mailto:" $GLOBAL:ISSWebSite = "Default Web Site" # Path to the web.config for the acme-challenge. -$GLOBAL:webconfigfilename = "C:\inetpub\wwwroot\.well-known\acme-challenge\web.config" \ No newline at end of file +$GLOBAL:webconfigfilename = "C:\inetpub\wwwroot\.well-known\acme-challenge\web.config" + +# Path to the domains.txt file +$GLOBAL:DomainListFile = (Join-Path -Path (Split-Path -Path $PSCommandPath -Parent) -ChildPath 'Domains.txt') # Expects the Domains.txt in the same file as the Config.ps1 + +# Path to the hook script for successful renewals +$GLOBAL:HOOKSuccess = (Join-Path -Path (Split-Path -Path $PSCommandPath -Parent) -ChildPath 'hook.ps1') # Expects the script in the same file as the Config.ps1 \ No newline at end of file diff --git a/Config/Hook.ps1 b/Config/Hook.ps1 new file mode 100644 index 0000000..7b38a2e --- /dev/null +++ b/Config/Hook.ps1 @@ -0,0 +1,23 @@ +#Requires -Version 5 + +param( + [Parameter(Mandatory=$true,Position=1)] + [String] + $Certname +, + [Parameter(Mandatory=$true,Position=2)] + [String[]] + $Domains +) + + +if ($Domain[0] -eq "x") { + # Install Certificate + Install-ACMECertificate -CertificateRef $Certname -Installer iis -InstallerParameters @{ + WebSiteRef = $GLOBAL:ISSWebSite + BindingHost = "x" + BindingPort = 443 + CertificateFriendlyName = $Certname + Force = $true + } +} \ No newline at end of file diff --git a/Config/domains.txt b/Config/domains.txt index 5440882..1798798 100644 --- a/Config/domains.txt +++ b/Config/domains.txt @@ -1 +1,6 @@ -test.example.local \ No newline at end of file +test.example.local test2.example.local +test3.example.local test4.example.local + +# And another Test Domain + test5.example.local test6.example.local + diff --git a/Installer.bat b/Installer.bat new file mode 100644 index 0000000..07f856d --- /dev/null +++ b/Installer.bat @@ -0,0 +1,2 @@ +rem need the certificate's thumbprint as the first argument +powershell.exe -ExecutionPolicy Unrestricted -Command "Import-Module WebAdministration; Get-Item IIS:\SslBindings\* | Where-Object { $_.Store -eq 'WebHosting' } | Set-ItemProperty -Name Thumbprint -Value %1" diff --git a/Invoke-CertificateJob.ps1 b/Invoke-CertificateJob.ps1 new file mode 100644 index 0000000..f78d612 --- /dev/null +++ b/Invoke-CertificateJob.ps1 @@ -0,0 +1,90 @@ +# Import module ACMESharp (if not yet loaded) +if (! (Get-Module ACMESharp)) { + # Import ACMESharp module + Import-Module ACMESharp +} + +# Import configuration +. (Join-Path -Path (Split-Path -Path $PSCommandPath -Parent) -ChildPath 'Config\Config.ps1') + +#$ISSWebSite = "Default Web Site" +# Moved to config.ps1 + +############################## +#.SYNOPSIS +#A quick fix for the web.config +# +#.DESCRIPTION +#This is a quick fix to remove unnecessary / incorrect configuration from the web.config file. +# +############################### +function Fix-WebConfig { + #$webconfigfilename = "C:\inetpub\wwwroot\.well-known\acme-challenge\web.config" + # Moved to config.ps1 + + [XML] $webconf = Get-Content $GLOBAL:webconfigfilename + $webconf.configuration.'system.webServer'.RemoveChild($webconf.configuration.'system.webServer'.handlers) + $webconf.OuterXml.ToString() | Out-File -Encoding utf8 $GLOBAL:webconfigfilename +} + +function Import-Domains { + $FileContent = Get-Content -Path $GLOBAL:DomainListFile + $Lines = $FileContent -split "`n" | ` + ForEach-Object { $_.ToString().Trim() -replace '\s+', ' ' } | ` + Where-Object -FilterScript { $_.ToString().Trim() -ne "" -and $_.ToString().Trim() -notlike "#*" } + return $Lines +} + +$DomainFileLines = Import-Domains + +$DomainFileLines | ForEach-Object { + $Domains = $_ -split " " + + $Primary = $Domains[0] + $Certname = $Primary + "-$(get-date -format yyyy-MM-dd--HH-mm)" + + $Domains | ForEach-Object { + $Domain = $_ + $Alias = $Domain #+ "-$(get-date -format yyyy-MM-dd--HH-mm)" + + if (-not (Get-ACMEIdentifier $Alias) -or (Get-ACMEIdentifier $Alias).Status -ne "valid") { + # Create a new Identifier with Let's Encrypt + New-ACMEIdentifier -Dns $Domain -Alias $Alias + + # Handle the challenge using HTTP validation on IIS + Complete-ACMEChallenge -IdentifierRef $Alias -ChallengeType http-01 -Handler iis -HandlerParameters @{ WebSiteRef = $GLOBAL:ISSWebSite } + + # Fix web.config bug + Fix-WebConfig + + # Tell Let's Encrypt it's OK to validate now + Submit-ACMEChallenge -IdentifierRef $Alias -ChallengeType http-01 + + # Check the status of the certificate every 6 seconds until we have an answer; fail after a minute + $i = 0 + do { + $IdentifierInfo = Update-ACMEIdentifier -IdentifierRef $Alias + if($IdentifierInfo.Status.toString() -ne "pending") { + Start-Sleep 6 + $i++ + } + } until($IdentifierInfo.Status.toString() -ne "pending" -or $i -gt 10) + + if($i -gt 10) { + Write-Error "We did not receive a completed certificate after 60 seconds" + Continue + } + } + } + + # Generate Certificate + New-ACMECertificate -Generate -IdentifierRef $Primary -AlternativeIdentifierRefs $Domains -Alias $Certname + + # Submit the certificate request to Let's Encrypt + Submit-ACMECertificate -CertificateRef $Certname + + # Update in order to retrieve the CA signer's public cert + Update-ACMECertificate -CertificateRef $Certname + + . $GLOBAL:HOOKSuccess -Certname $Certname -Domains $Domains +} diff --git a/Test-Config.ps1 b/Test/Test-Config.ps1 similarity index 73% rename from Test-Config.ps1 rename to Test/Test-Config.ps1 index 996564c..0d2a979 100644 --- a/Test-Config.ps1 +++ b/Test/Test-Config.ps1 @@ -1,3 +1,3 @@ -. (Join-Path -Path (Split-Path -Path $PSCommandPath -Parent) -ChildPath 'config.ps1') +. (Join-Path -Path (Split-Path -Path $PSCommandPath -Parent) -ChildPath '..\Config\Config.ps1') Write-Host $GLOBAL:AcemeContact \ No newline at end of file diff --git a/Test/Test-LoadDomains.ps1 b/Test/Test-LoadDomains.ps1 new file mode 100644 index 0000000..80627b6 --- /dev/null +++ b/Test/Test-LoadDomains.ps1 @@ -0,0 +1,11 @@ +. (Join-Path -Path (Split-Path -Path $PSCommandPath -Parent) -ChildPath '..\Config\Config.ps1') + +function Import-Domains { + $FileContent = Get-Content -Path $GLOBAL:DomainListFile + $Lines = $FileContent -split "`n" | ` + ForEach-Object { $_.ToString().Trim() -replace '\s+', ' ' } | ` + Where-Object -FilterScript { $_.ToString().Trim() -ne "" -and $_.ToString().Trim() -notlike "#*" } + $Lines +} + +Import-Domains \ No newline at end of file