Files
easyai/scripts/Test-CredentialKeys.ps1
T
easyai03 5cff1041a9 feat(deploy): 补齐凭据加密接入迁移和密钥备份恢复
首次安装默认生成独立加密密钥,更新旧部署保留原配置,通过显式开关进入迁移窗口。增加生产迁移包装入口和完整密钥环备份恢复工具,并说明旧镜像升级、严格迁移、轮换及还原流程。

已通过 Bash 安全环境及密钥备份回归;对应 PowerShell 5.1 和 7 脚本已在 Windows 原生环境各通过 49 项验收。宁波服务器验收尚未执行。
2026-10-12 22:24:16 +08:00

51 lines
3.9 KiB
PowerShell

#Requires -Version 5.1
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'Initialize-SecurityEnv.ps1')
$temp = Join-Path ([System.IO.Path]::GetTempPath()) ('easyai-key-recovery-' + [Guid]::NewGuid().ToString('N'))
[System.IO.Directory]::CreateDirectory($temp) | Out-Null
$envFile = Join-Path $temp '.env'; $keyFile = Join-Path $temp 'keys.env'; $logFile = Join-Path $temp 'result.log'
function Assert-KeyTest([bool]$Value, [string]$Label) { if (-not $Value) { throw "FAILED: $Label" } }
try {
[System.IO.File]::WriteAllText($envFile, '')
Initialize-SecurityEnv -Path $envFile -Mode new *> $logFile
$original = Get-Content $envFile -Raw
$ring = Get-EnvValue $original 'CONFIG_CREDENTIAL_ENCRYPTION_KEYRING'
$second = [Convert]::FromBase64String((New-SecuritySecret))
$second = [Convert]::ToBase64String($second[0..31])
$multi = $ring.Substring(0, $ring.Length - 1) + ',"v2":"' + $second + '"}'
$content = Set-EnvValue $original 'CONFIG_CREDENTIAL_ENCRYPTION_KEYRING' ("'" + $multi + "'")
$content = Set-EnvValue $content 'CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID' 'v2'
$content = Set-EnvValue $content 'UNRELATED_CONFIG' 'keep-me'
[System.IO.File]::WriteAllText($envFile, $content)
& (Join-Path $PSScriptRoot 'Manage-CredentialKeys.ps1') -Mode export -Path $keyFile -EnvPath $envFile *>> $logFile
Assert-KeyTest ((Get-EnvValue (Get-Content $keyFile -Raw) 'CONFIG_CREDENTIAL_ENCRYPTION_KEYRING') -ceq $multi) 'full ring exported'
$refused = $false
try { & (Join-Path $PSScriptRoot 'Manage-CredentialKeys.ps1') -Mode export -Path $keyFile -EnvPath $envFile *>> $logFile } catch { $refused = $true }
Assert-KeyTest $refused 'existing backup never overwritten'
[System.IO.File]::WriteAllText($envFile, (Set-EnvValue $original 'UNRELATED_CONFIG' 'keep-me'))
& (Join-Path $PSScriptRoot 'Manage-CredentialKeys.ps1') -Mode restore -Path $keyFile -EnvPath $envFile *>> $logFile
$restored = Get-Content $envFile -Raw
foreach ($name in @('CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY','CONFIG_JWT_SECRET')) { Assert-KeyTest ((Get-EnvValue $restored $name) -ceq (Get-EnvValue $original $name)) 'original config master and JWT retained' }
Assert-KeyTest ((Get-EnvValue $restored 'CONFIG_CREDENTIAL_ENCRYPTION_KEYRING') -ceq $multi) 'both original keys restored'
Assert-KeyTest ((Get-EnvValue $restored 'CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID') -ceq 'v2') 'active key restored'
Assert-KeyTest ((Get-EnvValue $restored 'UNRELATED_CONFIG') -ceq 'keep-me') 'unrelated config preserved'
$backups = @(Get-ChildItem $temp -Force -Filter '.env.pre-credential-restore.*')
Assert-KeyTest ($backups.Count -eq 1) 'previous env saved'
$bad = Join-Path $temp 'bad.env'
[System.IO.File]::WriteAllText($bad, "CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID=v2`nCONFIG_CREDENTIAL_ENCRYPTION_KEYRING='{}'`n")
$refused = $false
try { & (Join-Path $PSScriptRoot 'Manage-CredentialKeys.ps1') -Mode restore -Path $bad -EnvPath $envFile *>> $logFile } catch { $refused = $true }
Assert-KeyTest $refused 'invalid recovery refused'
Assert-KeyTest ((Get-Content $envFile -Raw) -ceq $restored) 'invalid recovery never mutates env'
Assert-KeyTest (-not (Get-Content $logFile -Raw).Contains($second)) 'no key leaked to log'
if ($env:OS -eq 'Windows_NT') {
$sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
foreach ($file in @($envFile, $keyFile, $backups[0].FullName)) {
$acl = Get-Acl -LiteralPath $file
Assert-KeyTest $acl.AreAccessRulesProtected 'private DACL'
foreach ($rule in $acl.Access) { Assert-KeyTest ($rule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -eq $sid) 'only current user access' }
}
}
Write-Host 'Credential key backup/restore self-test passed.'
} finally { Remove-Item -LiteralPath $temp -Recurse -Force }