diff --git a/.env.sample b/.env.sample index 0d44e7a..73e2a61 100644 --- a/.env.sample +++ b/.env.sample @@ -140,7 +140,7 @@ CONFIG_TRUSTED_PROXY= # 完成身份冲突扫描和人工处置后才能启用手机号、邮箱和微信身份唯一索引。 CONFIG_AUTH_UNIQUE_INDEXES_ENABLED=false -# 仅在首次创建 admin 账号时使用;启动脚本会生成随机值并持久化到 .env。 +# 仅在首次创建 admin 账号时使用;启动脚本会生成 20 位字母数字随机值并持久化到 .env。 # 已存在 admin 的升级环境不会使用或修改现有管理员密码。 CONFIG_INITIAL_ADMIN_PASSWORD= diff --git a/scripts/Initialize-SecurityEnv.ps1 b/scripts/Initialize-SecurityEnv.ps1 index 076cac8..c51da8b 100644 --- a/scripts/Initialize-SecurityEnv.ps1 +++ b/scripts/Initialize-SecurityEnv.ps1 @@ -9,6 +9,17 @@ function New-SecuritySecret { return [Convert]::ToBase64String($bytes) } +function New-InitialAdminPassword { + $bytes = New-Object byte[] 10 + $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() + try { + $rng.GetBytes($bytes) + } finally { + $rng.Dispose() + } + return ([BitConverter]::ToString($bytes) -replace "-", "").ToLowerInvariant() +} + function Get-EnvValue { param([string]$Content, [string]$Key) $match = [regex]::Match($Content, "(?m)^$([regex]::Escape($Key))=(.*)$") @@ -52,8 +63,7 @@ function Initialize-SecurityEnv { @{ 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 } + @{ Key = "CONFIG_AUDIT_INTEGRITY_KEY"; Legacy = ""; MinLength = 32 } ) } @@ -66,6 +76,14 @@ function Initialize-SecurityEnv { Write-Host " [OK] Initialized security secret: $($definition.Key)" -ForegroundColor Green } + if ($Mode -eq "new") { + $adminPassword = Get-EnvValue $content "CONFIG_INITIAL_ADMIN_PASSWORD" + if ($adminPassword.Length -lt 12) { + $content = Set-EnvValue $content "CONFIG_INITIAL_ADMIN_PASSWORD" (New-InitialAdminPassword) + Write-Host " [OK] Initialized admin password: CONFIG_INITIAL_ADMIN_PASSWORD" -ForegroundColor Green + } + } + [System.IO.File]::WriteAllText( (Resolve-Path $Path), $content, diff --git a/scripts/init-security-env.sh b/scripts/init-security-env.sh index d45715b..9a7ed6c 100644 --- a/scripts/init-security-env.sh +++ b/scripts/init-security-env.sh @@ -16,6 +16,20 @@ generate_security_secret() { return 1 } +generate_initial_admin_password() { + if command -v openssl >/dev/null 2>&1; then + # 10 random bytes encoded as hexadecimal: exactly 20 alphanumeric characters. + openssl rand -hex 10 | tr -d '\r\n' + return + fi + if [ -r /dev/urandom ]; then + od -An -N10 -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" @@ -67,6 +81,17 @@ ensure_security_secret() { echo " ✓ 已初始化安全密钥: ${key}" } +ensure_initial_admin_password() { + local file="$1" + local current + current="$(read_env_value "$file" "CONFIG_INITIAL_ADMIN_PASSWORD")" + if [ "${#current}" -ge 12 ]; then + return 0 + fi + write_env_value "$file" "CONFIG_INITIAL_ADMIN_PASSWORD" "$(generate_initial_admin_password)" + echo " ✓ 已初始化管理员密码: CONFIG_INITIAL_ADMIN_PASSWORD" +} + init_security_env() { local file="${1:-.env}" local mode="${2:-upgrade}" @@ -85,7 +110,7 @@ init_security_env() { 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 + ensure_initial_admin_password "$file" fi chmod 600 "$file" }