[CmdletBinding()] param( [Parameter()] [ValidatePattern('^https://')] [string]$BaseUrl = 'https://pulse.example.com', [Parameter()] [ValidatePattern('^http://')] [string]$HttpUrl = 'http://pulse.example.com', [Parameter()] [ValidatePattern('^https://')] [string]$ExpectedOIDCIssuer = 'https://auth.nuklearrabbit.com/application/o/itworx-pulse', [Parameter()] [ValidateRange(1, 30)] [int]$TimeoutSeconds = 10 ) $ErrorActionPreference = 'Stop' $base = [uri]$BaseUrl $plain = [uri]$HttpUrl $issuer = [uri]$ExpectedOIDCIssuer if (-not [string]::IsNullOrEmpty($base.UserInfo) -or -not [string]::IsNullOrEmpty($plain.UserInfo) -or -not [string]::IsNullOrEmpty($issuer.UserInfo)) { throw 'Production smoke URLs may not contain embedded credentials.' } function Invoke-NoRedirect([uri]$Uri) { $handler = [System.Net.Http.HttpClientHandler]::new() $handler.AllowAutoRedirect = $false $client = [System.Net.Http.HttpClient]::new($handler) $client.Timeout = [TimeSpan]::FromSeconds($TimeoutSeconds) try { $response = $client.GetAsync($Uri).GetAwaiter().GetResult() $headers = @{} foreach ($header in $response.Headers) { $headers[$header.Key] = $header.Value -join ', ' } foreach ($header in $response.Content.Headers) { $headers[$header.Key] = $header.Value -join ', ' } [pscustomobject]@{ StatusCode = [int]$response.StatusCode Headers = $headers Content = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() } } finally { $client.Dispose() $handler.Dispose() } } function Invoke-Probe([string]$Path) { Invoke-NoRedirect ([uri]($BaseUrl.TrimEnd('/') + $Path)) } function Assert-Status($Response, [int[]]$Expected, [string]$Label) { if ($Expected -notcontains [int]$Response.StatusCode) { throw "$Label returned HTTP $($Response.StatusCode); expected $($Expected -join '/')" } } function Get-ResponseText($Response) { if ($Response.Content -is [byte[]]) { return [System.Text.Encoding]::UTF8.GetString([byte[]]$Response.Content) } return [string]$Response.Content } Write-Host '== HTTPS health and security headers ==' -ForegroundColor Cyan $health = Invoke-Probe '/healthz' Assert-Status $health @(200) 'HTTPS /healthz' if ((Get-ResponseText $health).Trim() -ne 'ok' -or $health.Headers.'Content-Type' -notmatch '^text/plain') { throw 'HTTPS /healthz must return exact text/plain ok content.' } foreach ($header in @('Strict-Transport-Security', 'Content-Security-Policy', 'X-Content-Type-Options', 'X-Frame-Options')) { if ([string]::IsNullOrWhiteSpace([string]$health.Headers.$header)) { throw "HTTPS response is missing $header." } } if ([string]$health.Headers.'Strict-Transport-Security' -notmatch 'max-age=\d+') { throw 'HSTS does not contain a max-age directive.' } $ready = Invoke-Probe '/readyz' Assert-Status $ready @(200) 'HTTPS /readyz' if ($ready.Headers.'Content-Type' -notmatch '^text/plain' -or (Get-ResponseText $ready) -match '