param( [string]$OutputDirectory = (Join-Path (Split-Path -Parent $PSScriptRoot) "deploy/.control-plane-tls") ) $ErrorActionPreference = "Stop" if ([string]::IsNullOrWhiteSpace($OutputDirectory)) { throw "OutputDirectory is required" } if (Test-Path -LiteralPath $OutputDirectory) { $existing = @(Get-ChildItem -Force -LiteralPath $OutputDirectory) if ($existing.Count -gt 0) { throw "OutputDirectory must be empty: $OutputDirectory" } } else { New-Item -ItemType Directory -Path $OutputDirectory | Out-Null } function ConvertTo-Pem { param( [string]$Label, [byte[]]$Bytes ) $base64 = [Convert]::ToBase64String($Bytes, [Base64FormattingOptions]::InsertLineBreaks) return "-----BEGIN $Label-----`n$base64`n-----END $Label-----`n" } function Write-PemFile { param( [string]$Path, [string]$Label, [byte[]]$Bytes ) [IO.File]::WriteAllText($Path, (ConvertTo-Pem -Label $Label -Bytes $Bytes), [Text.Encoding]::ASCII) } function New-CertificateRequest { param( [string]$CommonName, [System.Security.Cryptography.RSA]$Key, [bool]$IsCertificateAuthority, [string]$ExtendedKeyUsage, [string]$SubjectAlternativeName ) $request = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new( "CN=$CommonName", $Key, [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1 ) $request.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new( $IsCertificateAuthority, $false, 0, $true )) if ($IsCertificateAuthority) { $request.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new( [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyCertSign -bor [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::CrlSign, $true )) return $request } $request.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new( [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::DigitalSignature -bor [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyEncipherment, $true )) $usage = [System.Security.Cryptography.OidCollection]::new() if ($ExtendedKeyUsage -eq "serverAuth") { $null = $usage.Add([System.Security.Cryptography.Oid]::new("1.3.6.1.5.5.7.3.1")) } else { $null = $usage.Add([System.Security.Cryptography.Oid]::new("1.3.6.1.5.5.7.3.2")) } $request.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]::new($usage, $true)) $san = [System.Security.Cryptography.X509Certificates.SubjectAlternativeNameBuilder]::new() if ($SubjectAlternativeName.StartsWith("DNS:")) { $san.AddDnsName($SubjectAlternativeName.Substring(4)) } else { $san.AddUri([Uri]$SubjectAlternativeName.Substring(4)) } $request.CertificateExtensions.Add($san.Build()) return $request } function New-LeafCertificate { param( [string]$Name, [string]$CommonName, [string]$ExtendedKeyUsage, [string]$SubjectAlternativeName ) $directory = Join-Path $OutputDirectory $Name New-Item -ItemType Directory -Path $directory | Out-Null $key = [System.Security.Cryptography.RSA]::Create(2048) try { $request = New-CertificateRequest -CommonName $CommonName -Key $key -IsCertificateAuthority $false -ExtendedKeyUsage $ExtendedKeyUsage -SubjectAlternativeName $SubjectAlternativeName $serial = [byte[]]::new(16) [System.Security.Cryptography.RandomNumberGenerator]::Fill($serial) $certificate = $request.Create($caCertificate, $notBefore, $notAfter, $serial) try { Write-PemFile -Path (Join-Path $directory "tls.crt") -Label "CERTIFICATE" -Bytes $certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) Write-PemFile -Path (Join-Path $directory "tls.key") -Label "PRIVATE KEY" -Bytes $key.ExportPkcs8PrivateKey() } finally { $certificate.Dispose() } } finally { $key.Dispose() } Copy-Item -LiteralPath $caCertificatePath -Destination (Join-Path $directory "ca.crt") } $notBefore = [DateTimeOffset]::UtcNow.AddMinutes(-5) $notAfter = $notBefore.AddDays(7) $caKey = [System.Security.Cryptography.RSA]::Create(2048) $caRequest = New-CertificateRequest -CommonName "proxy-pool-local-control-plane-ca" -Key $caKey -IsCertificateAuthority $true -ExtendedKeyUsage "" -SubjectAlternativeName "" $caCertificate = $caRequest.CreateSelfSigned($notBefore, $notAfter) $caCertificatePath = Join-Path $OutputDirectory "ca.crt" Write-PemFile -Path $caCertificatePath -Label "CERTIFICATE" -Bytes $caCertificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) Write-PemFile -Path (Join-Path $OutputDirectory "ca.key") -Label "PRIVATE KEY" -Bytes $caKey.ExportPkcs8PrivateKey() New-LeafCertificate -Name "controller" -CommonName "proxy-pool-controller" -ExtendedKeyUsage "serverAuth" -SubjectAlternativeName "DNS:controller" New-LeafCertificate -Name "gateway-a" -CommonName "proxy-pool-gateway-a" -ExtendedKeyUsage "clientAuth" -SubjectAlternativeName "URI:spiffe://proxy-pool.local/development/worker/gateway-a" New-LeafCertificate -Name "gateway-b" -CommonName "proxy-pool-gateway-b" -ExtendedKeyUsage "clientAuth" -SubjectAlternativeName "URI:spiffe://proxy-pool.local/development/worker/gateway-b" New-LeafCertificate -Name "checker-a" -CommonName "proxy-pool-checker-a" -ExtendedKeyUsage "clientAuth" -SubjectAlternativeName "URI:spiffe://proxy-pool.local/development/checker/checker-a" $caCertificate.Dispose() $caKey.Dispose() Write-Host "Generated local control-plane certificates in $OutputDirectory"