GetSSL-LetsEncrypt.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#requires -Modules AcmeSharp, Azure, AzureRM.Websites

<#PSScriptInfo
  
.VERSION 1.5.0
.GUID 21904884-3b46-4b37-b388-6a9958592401
.AUTHOR Dani Alonso, Lee Holmes
.COPYRIGHT Dani Alonso
.TAGS LetsEncrypt SSL Azure Linux Windows Automation
  
#>


<#
  
.DESCRIPTION
 Este script es capaz de generar y renovar automaticamente los certificados SSL en sitios alojados en Microsoft Azure. Basado en el script original de Lee Holmes, realizando una serie de correcciones y mejoras que automatiza el correcto proceso en Azure Automation. (by Dani Alonso).
  
#>
 
param(

    [Parameter(Mandatory)]
    [String] $Domain,

    [Parameter(Mandatory)]
    [String] $RegistrationEmail,

    [Parameter(Mandatory)]
    [String] $ResourceGroup,

    [Parameter(Mandatory)]
    [String] $WebApp,

    [Switch] $UseUnixFileVerification
)

Set-StrictMode -Version Latest

function GetSafeFilename
{
    param(
        $BasePath = ".",
        $Text,
        $Extension = ".txt"
    )

    $invalidChars = [IO.Path]::GetInvalidFileNameChars()
    $invalidCharsRegex = "[" + (-join ($invalidChars | % { [Regex]::Escape($_) })) + "]"
    $baseFilename = $Text -replace $invalidCharsRegex,'_'

    $reservedDeviceNames = -split "CON PRN AUX NUL COM1 COM2 COM3 COM4 COM5 COM6 COM7 COM8 COM9 LPT1 LPT2 LPT3 LPT4 LPT5 LPT6 LPT7 LPT8 LPT9"
    if($baseFilename -in $reservedDeviceNames)
    {
        $baseFilename = "_" + $baseFilename
    }

    $baseFilename = $baseFilename.Substring(0, [Math]::Min(50, $baseFilename.Length))

    $counter = 1
    $fileName = $baseFilename + $Extension
    while(Test-Path (Join-Path $BasePath $fileName))
    {
        $filename = $baseFilename + "_${counter}${Extension}"
        $counter++
    }

    $fileName.Trim()
}

function PublishWebsiteFile
{
    param(
        [Parameter(Mandatory)]
        $ResourceGroup,

        [Parameter(Mandatory)]
        $WebApp,

        [Parameter(Mandatory)]
        $PublishSettingsFile,

        [Parameter(Mandatory)]
        $RemotePath,

        [Parameter(Mandatory)]
        $FileContent
    )

    $RemotePath = $RemotePath.Trim("/\")

    $publishSettings = [xml] (Get-Content $PublishSettingsFile -Raw)
    $ftpPublishSettings = $publishSettings.publishData.publishProfile | ? publishMethod -eq MSDeploy

    $username = $ftpPublishSettings.userName
    $password = $ftpPublishSettings.userPWD
    $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))

    $apiBaseUrl = "https://$WebApp.scm.azurewebsites.net/api"
    Invoke-RestMethod -Uri "$apiBaseUrl/vfs/site/wwwroot/$RemotePath" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo); 'If-Match' = '*'} -Method PUT -Body $FileContent
}

$outputDirectory = GetSafeFilename -Text $Domain -Extension ""
if(-not (Test-Path $outputDirectory))
{
    $null = New-Item -Type Directory $outputDirectory
}

Write-Progress "Creating Let's Encrypt registration"

if(-not (Get-AcmeVault))
{
    $null = Initialize-ACMEVault -BaseURI https://acme-v01.api.letsencrypt.org/
}

$identifier = -join (([int][char]'a'..[int][char]'z') | Get-Random -Count 10 | % { [char] $_ })
$null = New-ACMERegistration -Contacts mailto:$RegistrationEmail -AcceptTos
$null = New-ACMEIdentifier -Dns $domain -Alias $identifier

Write-Progress "Receiving challenge"
$completedChallenge = Complete-ACMEChallenge -Ref $identifier -Challenge http-01 -Handler manual -Regenerate
$challengeAnswer = ($completedChallenge.Challenges | Where-Object { $_.HandlerName -eq "manual" }).Challenge
$key = $challengeAnswer.FilePath

$target = "$key/index.html"
if($UseUnixFileVerification) { $target = $key }

Write-Progress "Uploading key and challenge to $domain/$target"

$Conn = Get-AutomationConnection -Name "AzureRunAsConnection"
Add-AzureRmAccount -ServicePrincipal -Tenant $Conn.TenantID -ApplicationId $Conn.ApplicationID -CertificateThumbprint $Conn.CertificateThumbprint

$tempFile = New-TemporaryFile
try
{
    $null = Get-AzureRmWebAppPublishingProfile -ResourceGroupName $ResourceGroup -Name $WebApp -OutputFile $tempFile
    PublishWebsiteFile -ResourceGroup $ResourceGroup -WebApp $WebApp -PublishSettingsFile $tempFile -RemotePath $target -FileContent $challengeAnswer.FileContent
}
finally
{
    Remove-Item $tempFile
}

$counter = 0
Write-Progress "Waiting for challenge verification" -PercentComplete ($counter++)
$challenge = Submit-ACMEChallenge -Ref $identifier -ChallengeType http-01
while ($challenge.Status -eq "pending")
{
    Start-Sleep -m 500

    Write-Progress "Waiting for challenge verification" -PercentComplete ($counter++)
    $challenge = Update-ACMEIdentifier -Ref $identifier
}

if($challenge.Status -eq "valid")
{
    $rawPassword =  -join (([int][char]'a'..[int][char]'z') | Get-Random -Count 20 | % { [char] $_ })
    $certIdentifier = -join (([int][char]'a'..[int][char]'z') | Get-Random -Count 10 | % { [char] $_ })

    New-ACMECertificate -Identifier $identifier -Alias $certIdentifier -Generate
    $certificateInfo = Submit-ACMECertificate -Ref $certIdentifier

    Write-Progress "Waiting for IssuerSerialNumber to be issued"
    while(-not ((Test-Path variable:\certificate) -or $certificateInfo.IssuerSerialNumber))
    {
        Start-Sleep -m 500
        $certificateInfo = Update-ACMECertificate -Ref $certIdentifier
    }

    $outputFile = Join-Path $pwd cert1-all.pfx
    $null = Get-ACMECertificate -Ref $certIdentifier -ExportPkcs12 $outputFile -CertificatePassword $rawPassword
    Get-Item $outputFile

    New-AzureRmWebAppSSLBinding -ResourceGroupName $ResourceGroup -WebAppName $WebApp -CertificateFilePath $outputFile -CertificatePassword $rawPassword -Name $Domain
}
else
{
    Write-Error (("Certificate generation failed. Status is '{0}', can't continue as it is not 'valid'. " +
        "Let's Encrypt could not retrieve the expected content from '$domain/$target'") -f $challenge.Status)
    $challenge
}