feat(deploy): initialize authentication security secrets
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
function New-SecuritySecret {
|
||||
$bytes = New-Object byte[] 48
|
||||
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
||||
try {
|
||||
$rng.GetBytes($bytes)
|
||||
} finally {
|
||||
$rng.Dispose()
|
||||
}
|
||||
return [Convert]::ToBase64String($bytes)
|
||||
}
|
||||
|
||||
function Get-EnvValue {
|
||||
param([string]$Content, [string]$Key)
|
||||
$match = [regex]::Match($Content, "(?m)^$([regex]::Escape($Key))=(.*)$")
|
||||
if (-not $match.Success) { return "" }
|
||||
$value = $match.Groups[1].Value.Trim()
|
||||
if ($value.Length -ge 2) {
|
||||
$first = $value.Substring(0, 1)
|
||||
$last = $value.Substring($value.Length - 1, 1)
|
||||
if (($first -eq "'" -and $last -eq "'") -or ($first -eq '"' -and $last -eq '"')) {
|
||||
return $value.Substring(1, $value.Length - 2)
|
||||
}
|
||||
}
|
||||
return $value
|
||||
}
|
||||
|
||||
function Set-EnvValue {
|
||||
param([string]$Content, [string]$Key, [string]$Value)
|
||||
$pattern = "(?m)^$([regex]::Escape($Key))=.*(?:\r?\n)?"
|
||||
$replacement = "$Key=$Value`r`n"
|
||||
if ([regex]::IsMatch($Content, $pattern)) {
|
||||
return [regex]::Replace($Content, $pattern, $replacement, 1)
|
||||
}
|
||||
if ($Content -and -not $Content.EndsWith("`n")) { $Content += "`r`n" }
|
||||
return $Content + $replacement
|
||||
}
|
||||
|
||||
function Initialize-SecurityEnv {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path,
|
||||
[ValidateSet("new", "upgrade")][string]$Mode = "upgrade"
|
||||
)
|
||||
if (-not (Test-Path $Path)) { throw "Environment file not found: $Path" }
|
||||
|
||||
$content = Get-Content $Path -Raw -Encoding UTF8
|
||||
if ($null -eq $content) { $content = "" }
|
||||
$definitions = @(
|
||||
@{ Key = "CONFIG_JWT_SECRET"; Legacy = "this is a very secret secret"; MinLength = 32 }
|
||||
)
|
||||
if ($Mode -eq "new") {
|
||||
$definitions += @(
|
||||
@{ Key = "CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY"; Legacy = ""; MinLength = 32 },
|
||||
@{ Key = "CONFIG_OTP_HASH_SECRET"; Legacy = ""; MinLength = 32 },
|
||||
@{ Key = "CONFIG_AUDIT_HASH_PEPPER"; Legacy = ""; MinLength = 32 },
|
||||
@{ Key = "CONFIG_AUDIT_INTEGRITY_KEY"; Legacy = ""; MinLength = 32 },
|
||||
@{ Key = "CONFIG_INITIAL_ADMIN_PASSWORD"; Legacy = ""; MinLength = 12 }
|
||||
)
|
||||
}
|
||||
|
||||
foreach ($definition in $definitions) {
|
||||
$current = Get-EnvValue $content $definition.Key
|
||||
$needsInitialization = $current.Length -lt $definition.MinLength -or
|
||||
($definition.Legacy -and $current -eq $definition.Legacy)
|
||||
if (-not $needsInitialization) { continue }
|
||||
$content = Set-EnvValue $content $definition.Key (New-SecuritySecret)
|
||||
Write-Host " [OK] Initialized security secret: $($definition.Key)" -ForegroundColor Green
|
||||
}
|
||||
|
||||
[System.IO.File]::WriteAllText(
|
||||
(Resolve-Path $Path),
|
||||
$content,
|
||||
[System.Text.UTF8Encoding]::new($false)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 为本地 .env 初始化持久化安全密钥。只处理空值和已知历史默认值,
|
||||
# 已存在的自定义值保持不变;数据库中的 JWT 轮转配置仍由后端优先使用。
|
||||
|
||||
generate_security_secret() {
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl rand -base64 48 | tr -d '\r\n'
|
||||
return
|
||||
fi
|
||||
if [ -r /dev/urandom ]; then
|
||||
od -An -N48 -tx1 /dev/urandom | tr -d ' \r\n'
|
||||
return
|
||||
fi
|
||||
echo "❌ 无法生成安全随机值:需要 openssl 或 /dev/urandom" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
read_env_value() {
|
||||
local file="$1"
|
||||
local key="$2"
|
||||
local value
|
||||
value="$(awk -v key="$key" 'index($0, key "=") == 1 { print substr($0, length(key) + 2); exit }' "$file")"
|
||||
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
case "$value" in
|
||||
\"*\") value="${value#\"}"; value="${value%\"}" ;;
|
||||
\'*\') value="${value#\'}"; value="${value%\'}" ;;
|
||||
esac
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
write_env_value() {
|
||||
local file="$1"
|
||||
local key="$2"
|
||||
local value="$3"
|
||||
local tmp
|
||||
tmp="$(mktemp "${file}.tmp.XXXXXX")"
|
||||
awk -v key="$key" -v value="$value" '
|
||||
BEGIN { replaced = 0 }
|
||||
index($0, key "=") == 1 {
|
||||
if (!replaced) {
|
||||
print key "=" value
|
||||
replaced = 1
|
||||
}
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
END {
|
||||
if (!replaced) print key "=" value
|
||||
}
|
||||
' "$file" > "$tmp"
|
||||
chmod 600 "$tmp"
|
||||
mv "$tmp" "$file"
|
||||
}
|
||||
|
||||
ensure_security_secret() {
|
||||
local file="$1"
|
||||
local key="$2"
|
||||
local legacy_value="${3:-}"
|
||||
local minimum_length="${4:-32}"
|
||||
local current
|
||||
current="$(read_env_value "$file" "$key")"
|
||||
if [ "${#current}" -ge "$minimum_length" ] && { [ -z "$legacy_value" ] || [ "$current" != "$legacy_value" ]; }; then
|
||||
return 0
|
||||
fi
|
||||
write_env_value "$file" "$key" "$(generate_security_secret)"
|
||||
echo " ✓ 已初始化安全密钥: ${key}"
|
||||
}
|
||||
|
||||
init_security_env() {
|
||||
local file="${1:-.env}"
|
||||
local mode="${2:-upgrade}"
|
||||
if [ ! -f "$file" ]; then
|
||||
echo "❌ 环境配置文件不存在: $file" >&2
|
||||
return 1
|
||||
fi
|
||||
if [ "$mode" != "new" ] && [ "$mode" != "upgrade" ]; then
|
||||
echo "❌ 安全环境初始化模式无效: $mode(仅支持 new/upgrade)" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
ensure_security_secret "$file" "CONFIG_JWT_SECRET" "this is a very secret secret"
|
||||
if [ "$mode" = "new" ]; then
|
||||
ensure_security_secret "$file" "CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY"
|
||||
ensure_security_secret "$file" "CONFIG_OTP_HASH_SECRET"
|
||||
ensure_security_secret "$file" "CONFIG_AUDIT_HASH_PEPPER"
|
||||
ensure_security_secret "$file" "CONFIG_AUDIT_INTEGRITY_KEY"
|
||||
ensure_security_secret "$file" "CONFIG_INITIAL_ADMIN_PASSWORD" "" 12
|
||||
fi
|
||||
chmod 600 "$file"
|
||||
}
|
||||
|
||||
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
||||
init_security_env "${1:-.env}" "${2:-upgrade}"
|
||||
fi
|
||||
Reference in New Issue
Block a user