# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Permission is hereby granted, free of charge, to any person obtaining a copy of this # software and associated documentation files (the "Software"), to deal in the Software # without restriction, including without limitation the rights to use, copy, modify, # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [CmdLetBinding()] Param ( [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$ServiceName = 'admission-webhook-svc', [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$SecretName = 'admission-webhook-certs', [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$Namespace = 'gMSA' ) $ErrorActionPreference = 'Stop' if (!(Get-Command -Name 'openssl' -ErrorAction:SilentlyContinue)) { throw 'openssl not found' } if (!(Get-Command -Name 'kubectl' -ErrorAction:SilentlyContinue)) { throw 'kubectl not found' } Switch ((Get-Command -Name 'Out-File').Parameters['Encoding'].ParameterType.Name) { 'String' { # PowerShell w/ Full .Net [string]$OutFileEncoding = 'ASCII' } Default { # Assumes 'Encoding' # PowerShell.Core w/ Full .netcore [System.Text.Encoding]$OutFileEncoding = [System.Text.Encoding]::GetEncoding('ASCII') } } $TempDirectoryPath = [System.IO.Path]::Combine($ENV:Temp, ([System.IO.Path]::GetRandomFileName())) $TempDirectory = New-Item -Type:Directory -Path $TempDirectoryPath Write-Verbose "Creating certificates in path: $TempDirectoryPath" $csrFilePath = [System.IO.Path]::Combine($TempDirectory, 'server.csr') $csrConfFilePath = [System.IO.Path]::Combine($TempDirectory, 'csr.conf') $csrConfFilePath = [System.IO.Path]::Combine($TempDirectory, 'csr.conf') $csrK8sConfFilePath = [System.IO.Path]::Combine($TempDirectory, 'csr.yaml') $serverCertificateKeyFilePath = [System.IO.Path]::Combine($TempDirectory, 'server-key.pem') $serverCertificateFilePath = [System.IO.Path]::Combine($TempDirectory, 'server-cert.pem') $csrName = ('{0}.{1}' -f $ServiceName, $Namespace) $serviceAddress = ('{0}.svc' -f $csrName) $csrConf = @" [req] req_extensions = v3_req distinguished_name = req_distinguished_name [req_distinguished_name] [v3_req] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names [alt_names] DNS.1 = $ServiceName DNS.2 = $csrName DNS.3 = $serviceAddress "@ Out-File -InputObject $csrConf -FilePath $csrConfFilePath -Encoding $OutFileEncoding $cmd = "openssl genrsa -out `"$serverCertificateKeyFilePath`" 2048" Write-Verbose $cmd Invoke-Expression -Command $cmd $cmd = "openssl req -new -key `"$serverCertificateKeyFilePath`" -subj `"/CN=$serviceAddress`" -out `"$csrFilePath`" -config `"$csrConfFilePath`"" Write-Verbose $cmd Invoke-Expression -Command $cmd Write-Verbose ('Converting openssl csr to base64 encoded bytes...') $csrRequestData = Invoke-Expression -Command "[Convert]::ToBase64String([System.IO.File]::ReadAllBytes(`"$csrFilePath`"))" Write-Verbose 'Cleaning up any previously created CSR' try { Invoke-Expression -Command "kubectl delete csr $csrName" 2>&1 } catch { Write-Verbose $_ } Write-Verbose 'Creating server CSR' $csrK8sConf = @" apiVersion: certificates.k8s.io/v1beta1 kind: CertificateSigningRequest metadata: name: $csrName spec: groups: - system:authenticated request: $csrRequestData usages: - digital signature - key encipherment - server auth "@ Out-File -InputObject $csrK8sConf -FilePath $csrK8sConfFilePath -Encoding $OutFileEncoding Invoke-Expression -Command "kubectl create -f `"$csrK8sConfFilePath`"" Write-Verbose 'Verifying CSR has been created' [int]$tries = 10 [int]$delay = 1 [bool]$Succeeded = $false do { try { Invoke-Expression -Command "kubectl get csr $csrName" $Succeeded = $true } catch { $Succeeded = $false } if (-not $Succeeded) { $tries-- Start-Sleep -Seconds $delay } } while (-not $Succeeded -and $tries -ge 0) if (-not $Succeeded) { throw "Failed to verify Certificate Signing Request (CSR)" } Write-Verbose 'Approving server CSR' Invoke-Expression -Command "kubectl certificate approve $csrName" Write-Verbose 'Getting signed certificate' [int]$tries = 10 [int]$delay = 1 [bool]$Succeeded = $false do { try { $serverCertificate = Invoke-Expression -Command "kubectl get csr $csrName -o jsonpath='{.status.certificate}'" $Succeeded = $true } catch { $Succeeded = $false } if (-not $Succeeded) { $tries-- Start-Sleep -Seconds $delay } } while (-not $Succeeded -and $tries -ge 0) if (-not $Succeeded -or $null -eq $serverCertificate) { throw 'Failed to get approved csr data' } Write-Verbose 'Writing signed certificate' Invoke-Expression -Command "Write-Output `"$serverCertificate`" | openssl base64 -d -A -out `"$serverCertificateFilePath`"" Write-Verbose 'Creating secret with CA certificate and server certificate' $cmd = "kubectl create secret generic $SecretName " + ` "--from-file=key.pem=`"$serverCertificateKeyFilePath`" " + ` "--from-file=cert.pem=`"$serverCertificateFilePath`" " + ` "--dry-run=client -o yaml | " + ` "kubectl -n $Namespace apply -f -" Write-Verbose $cmd Invoke-Expression -Command $cmd