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 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))=(.*)$") 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 } ) } 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 } 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, [System.Text.UTF8Encoding]::new($false) ) }