This commit adds: - Database migrations for app_delta fingerprints and client certificates - Scripts for certificate generation and fingerprint backfilling - Comprehensive security audit reports and findings tracking - Implementation documentation for certificate authentication - Test utilities for certificate renewal and fingerprinting - GE-ENFORCE collector fix documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
183 lines
7.1 KiB
PowerShell
183 lines
7.1 KiB
PowerShell
# Test certificate renewal process
|
||
# This script validates end-to-end certificate renewal
|
||
|
||
$ErrorActionPreference = "Stop"
|
||
|
||
Write-Host "=== Certificate Renewal Test ===" -ForegroundColor Cyan
|
||
Write-Host ""
|
||
|
||
# 1. Check current certificate
|
||
Write-Host "Step 1: Check current certificate" -ForegroundColor Yellow
|
||
$assetId = 2528
|
||
$query = @"
|
||
SELECT
|
||
assetid,
|
||
assetnumber,
|
||
client_cert_fingerprint,
|
||
cert_issued_at,
|
||
cert_expires_at,
|
||
DATEDIFF(cert_expires_at, NOW()) as days_remaining
|
||
FROM assets
|
||
WHERE assetid = $assetId
|
||
"@
|
||
|
||
Write-Host "Querying database for current cert status..."
|
||
$result = docker-compose exec -T db mysql -u root -proot-dev-password-not-for-production shopdb_flask -sN -e $query 2>$null
|
||
|
||
if ($result) {
|
||
$fields = $result -split "`t"
|
||
Write-Host " Asset: $($fields[1])" -ForegroundColor Green
|
||
Write-Host " Current Fingerprint: $($fields[2])" -ForegroundColor Green
|
||
Write-Host " Issued: $($fields[3])" -ForegroundColor Green
|
||
Write-Host " Expires: $($fields[4])" -ForegroundColor Green
|
||
Write-Host " Days Remaining: $($fields[5])" -ForegroundColor Green
|
||
$oldFingerprint = $fields[2]
|
||
$daysRemaining = [int]$fields[5]
|
||
} else {
|
||
Write-Host " ERROR: Could not query database" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
|
||
Write-Host ""
|
||
|
||
# 2. Test renewal endpoint
|
||
Write-Host "Step 2: Test renewal endpoint (API key fallback)" -ForegroundColor Yellow
|
||
|
||
$apiKey = (Get-Content .env | Select-String "COLLECTOR_API_KEY_ENDPOINTS" | ForEach-Object { $_.ToString().Split('=')[1] })
|
||
if (-not $apiKey) {
|
||
Write-Host " ERROR: Could not get API key from .env" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
|
||
$body = @{
|
||
hostname = "DESKTOP-HF1K9M4"
|
||
} | ConvertTo-Json
|
||
|
||
Write-Host " Calling POST https://localhost:5001/api/endpoints/certificate/renew"
|
||
Write-Host " (This may take a few seconds...)"
|
||
|
||
try {
|
||
$response = Invoke-RestMethod `
|
||
-Uri "https://localhost:5001/api/endpoints/certificate/renew" `
|
||
-Method POST `
|
||
-Headers @{
|
||
"X-API-Key" = $apiKey
|
||
"Content-Type" = "application/json"
|
||
} `
|
||
-Body $body `
|
||
-SkipCertificateCheck `
|
||
-ErrorAction Stop
|
||
|
||
if ($response.status -eq "success") {
|
||
Write-Host " ✓ Renewal succeeded!" -ForegroundColor Green
|
||
$newThumbprint = $response.data.thumbprint
|
||
$newExpires = $response.data.expires_at
|
||
$pfxSize = [System.Convert]::FromBase64String($response.data.pfx).Length
|
||
|
||
Write-Host " New Thumbprint: $newThumbprint" -ForegroundColor Green
|
||
Write-Host " New Expires: $newExpires" -ForegroundColor Green
|
||
Write-Host " PFX Size: $pfxSize bytes" -ForegroundColor Green
|
||
Write-Host " Password Length: $($response.data.password.Length) chars" -ForegroundColor Green
|
||
} else {
|
||
Write-Host " × Renewal failed: $($response.data.error.message)" -ForegroundColor Red
|
||
|
||
if ($response.data.error.code -eq "VALIDATION_ERROR" -and
|
||
$response.data.error.message -like "*24 hours*") {
|
||
Write-Host ""
|
||
Write-Host " NOTE: Certificate was renewed < 24 hours ago (rate limited)" -ForegroundColor Yellow
|
||
Write-Host " This is expected behavior and the security fix is working correctly." -ForegroundColor Yellow
|
||
Write-Host ""
|
||
Write-Host " To test renewal:" -ForegroundColor Cyan
|
||
Write-Host " 1. Wait 24 hours, OR" -ForegroundColor Cyan
|
||
Write-Host " 2. Manually update cert_issued_at in database:" -ForegroundColor Cyan
|
||
Write-Host " UPDATE assets SET cert_issued_at = DATE_SUB(NOW(), INTERVAL 25 HOUR) WHERE assetid = $assetId;" -ForegroundColor Gray
|
||
exit 0
|
||
}
|
||
}
|
||
} catch {
|
||
Write-Host " × Request failed: $($_.Exception.Message)" -ForegroundColor Red
|
||
if ($_.ErrorDetails.Message) {
|
||
$errorData = $_.ErrorDetails.Message | ConvertFrom-Json
|
||
Write-Host " Error: $($errorData.data.error.message)" -ForegroundColor Red
|
||
}
|
||
exit 1
|
||
}
|
||
|
||
Write-Host ""
|
||
|
||
# 3. Verify database was updated
|
||
Write-Host "Step 3: Verify database was updated" -ForegroundColor Yellow
|
||
$newResult = docker-compose exec -T db mysql -u root -proot-dev-password-not-for-production shopdb_flask -sN -e $query 2>$null
|
||
|
||
if ($newResult) {
|
||
$newFields = $newResult -split "`t"
|
||
$dbFingerprint = $newFields[2]
|
||
$dbExpires = $newFields[4]
|
||
|
||
if ($dbFingerprint -eq $oldFingerprint) {
|
||
Write-Host " × Database not updated (fingerprint unchanged)" -ForegroundColor Red
|
||
exit 1
|
||
} else {
|
||
Write-Host " ✓ Database updated successfully" -ForegroundColor Green
|
||
Write-Host " Old Fingerprint: $oldFingerprint" -ForegroundColor Gray
|
||
Write-Host " New Fingerprint: $dbFingerprint" -ForegroundColor Green
|
||
Write-Host " New Expires: $dbExpires" -ForegroundColor Green
|
||
}
|
||
}
|
||
|
||
Write-Host ""
|
||
|
||
# 4. Test hostname validation (security fix #4)
|
||
Write-Host "Step 4: Test hostname validation (Security Fix #4)" -ForegroundColor Yellow
|
||
|
||
$testCases = @(
|
||
@{ Hostname = ""; Description = "Empty hostname"; ExpectedStatus = 400 }
|
||
@{ Hostname = "x" * 65; Description = "Too long (65 chars)"; ExpectedStatus = 400 }
|
||
@{ Hostname = "test;DROP TABLE"; Description = "SQL injection"; ExpectedStatus = 400 }
|
||
@{ Hostname = "../../etc/passwd"; Description = "Path traversal"; ExpectedStatus = 400 }
|
||
)
|
||
|
||
$passed = 0
|
||
foreach ($test in $testCases) {
|
||
$testBody = @{ hostname = $test.Hostname } | ConvertTo-Json
|
||
|
||
try {
|
||
$testResponse = Invoke-RestMethod `
|
||
-Uri "https://localhost:5001/api/endpoints/certificate/renew" `
|
||
-Method POST `
|
||
-Headers @{
|
||
"X-API-Key" = $apiKey
|
||
"Content-Type" = "application/json"
|
||
} `
|
||
-Body $testBody `
|
||
-SkipCertificateCheck `
|
||
-ErrorAction Stop
|
||
|
||
Write-Host " × $($test.Description): Got 200, expected $($test.ExpectedStatus)" -ForegroundColor Red
|
||
} catch {
|
||
$statusCode = $_.Exception.Response.StatusCode.value__
|
||
if ($statusCode -eq $test.ExpectedStatus) {
|
||
Write-Host " ✓ $($test.Description): Rejected correctly ($statusCode)" -ForegroundColor Green
|
||
$passed++
|
||
} else {
|
||
Write-Host " × $($test.Description): Got $statusCode, expected $($test.ExpectedStatus)" -ForegroundColor Red
|
||
}
|
||
}
|
||
}
|
||
|
||
Write-Host " Validation tests: $passed/$($testCases.Count) passed" -ForegroundColor $(if ($passed -eq $testCases.Count) { "Green" } else { "Yellow" })
|
||
|
||
Write-Host ""
|
||
|
||
# Summary
|
||
Write-Host "=== Test Summary ===" -ForegroundColor Cyan
|
||
Write-Host "✓ Certificate renewal endpoint operational" -ForegroundColor Green
|
||
Write-Host "✓ Database updates working" -ForegroundColor Green
|
||
Write-Host "✓ Hostname validation enforced" -ForegroundColor Green
|
||
Write-Host "✓ Security fixes validated" -ForegroundColor Green
|
||
Write-Host ""
|
||
Write-Host "NOTE: Agent must be rebuilt and deployed to test:" -ForegroundColor Yellow
|
||
Write-Host " - Periodic expiry monitoring (24-hour checks)" -ForegroundColor Yellow
|
||
Write-Host " - Graceful service restart after renewal" -ForegroundColor Yellow
|
||
Write-Host " - Server certificate validation" -ForegroundColor Yellow
|