# ============================================================ # Omika Diagnostic Center - Durcissement PostgreSQL (moindre privilege) # Applique UNIQUEMENT 003_diagnostics_roles.sql (roles + ownership + grants). # ============================================================ # A executer SUR LE SERVEUR (Windows Server 2025), PowerShell administrateur, # avec un compte PostgreSQL ADMINISTRATIF (superutilisateur, ex. postgres). # # Ce script est VOLONTAIREMENT SEPARE de Install-DatabaseSchema.ps1 : # 003 transfere l'ownership et gere les roles -> contexte administrateur. # 001/002 (schema) sont appliques par Install-DatabaseSchema.ps1. # # NOTE (V2) : Install-DatabaseSchema.ps1 applique desormais TOUTES les # migrations 001 -> 016 dans l'ordre (schema + roles + grants). Ce script reste # disponible pour n'appliquer QUE 003 (transition/back-compat) ; pour une # installation complete, preferer Install-DatabaseSchema.ps1. # # Garanties : # - AUCUN mot de passe en dur, AUCUN secret (les mots de passe des roles # LOGIN sont definis MANUELLEMENT ensuite via psql : \password ). # - AUCUN DROP, AUCUNE suppression de role/objet/donnee (003 est idempotent). # - Ne touche PAS au role historique omika_diagnostics_app (transition). # # Usage : # $env:PGPASSWORD = '...' # facultatif - sinon saisie masquee demandee # .\Harden-DatabaseRoles.ps1 # .\Harden-DatabaseRoles.ps1 -DatabaseDir "C:\Omika\Deploy\phase5b2\Database" # .\Harden-DatabaseRoles.ps1 -PsqlPath "C:\Program Files\PostgreSQL\18\bin\psql.exe" # .\Harden-DatabaseRoles.ps1 -Username postgres # ============================================================ [CmdletBinding()] param( [string]$DatabaseDir, [string]$PsqlPath, [string]$PgHost = '127.0.0.1', [int]$PgPort = 5432, [string]$Database = 'omika_diagnostics', [string]$Username = 'postgres' ) $ErrorActionPreference = 'Stop' # --- Resolution de DatabaseDir APRES le bloc param (bug $PSScriptRoot). # Valeur par defaut : dossier frere "Database" du dossier du script. if (-not $DatabaseDir) { if ($PSScriptRoot) { $DatabaseDir = Join-Path (Split-Path -Parent $PSScriptRoot) 'Database' } } if (-not $DatabaseDir -or -not (Test-Path -LiteralPath $DatabaseDir)) { Write-Error "Dossier des schemas introuvable : $DatabaseDir. Fournissez -DatabaseDir '\Database'." exit 1 } # --- Detection de psql.exe (ordre : -PsqlPath, PATH, C:\Program Files\PostgreSQL\*). function Resolve-PsqlPath { param([string]$ExplicitPath) if ($ExplicitPath) { if (-not (Test-Path -LiteralPath $ExplicitPath)) { Write-Error "PsqlPath fourni introuvable : $ExplicitPath" exit 1 } return (Resolve-Path -LiteralPath $ExplicitPath).Path } $inPath = Get-Command psql.exe -ErrorAction SilentlyContinue if ($inPath) { return $inPath.Source } $candidates = @(Get-ChildItem -Path 'C:\Program Files\PostgreSQL\*\bin\psql.exe' -ErrorAction SilentlyContinue) if ($candidates.Count -gt 0) { $best = $candidates | ForEach-Object { $version = 0 $verDir = $_.Directory.Parent.Name if ($verDir -match '^\d+$') { $version = [int]$verDir } [PSCustomObject]@{ Path = $_.FullName; Version = $version } } | Sort-Object -Property Version -Descending | Select-Object -First 1 return $best.Path } return $null } $psqlExe = Resolve-PsqlPath -ExplicitPath $PsqlPath if (-not $psqlExe) { Write-Error "Client 'psql.exe' introuvable. Fournissez -PsqlPath ou installez le client PostgreSQL." exit 1 } Write-Host "psql utilise : $psqlExe" # --- Mot de passe : priorite a PGPASSWORD deja present ; sinon saisie masquee. $useExistingPgPassword = -not [string]::IsNullOrEmpty($env:PGPASSWORD) if (-not $useExistingPgPassword) { $secure = Read-Host "Mot de passe PostgreSQL (role '$Username', base '$Database')" -AsSecureString $plain = [Runtime.InteropServices.Marshal]::PtrToStringAuto( [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)) $env:PGPASSWORD = $plain $plain = $null } $sqlPath = Join-Path $DatabaseDir '003_diagnostics_roles.sql' if (-not (Test-Path -LiteralPath $sqlPath)) { throw "Migration de securite introuvable : $sqlPath" } try { Write-Host "Application du durcissement : 003_diagnostics_roles.sql (${PgHost}:${PgPort}/${Database}, role $Username) ..." & $psqlExe ` -h $PgHost ` -p $PgPort ` -U $Username ` -d $Database ` -v ON_ERROR_STOP=1 ` -f $sqlPath if ($LASTEXITCODE -ne 0) { throw "psql a echoue (exit $LASTEXITCODE) sur 003_diagnostics_roles.sql." } Write-Host '' Write-Host "Durcissement applique (idempotent)." -ForegroundColor Green Write-Host "Roles : omika_diagnostics_owner (NOLOGIN), omika_diagnostics_worker, omika_diagnostics_web." Write-Host "Worker : INSERT diagnostic_event + USAGE sequence. Web : SELECT diagnostic_event." Write-Host '' Write-Host "ETAPES MANUELLES SUIVANTES (jamais automatisees ici) :" -ForegroundColor Yellow Write-Host " 1. Definir les mots de passe (psql connecte comme administrateur PostgreSQL) :" Write-Host " \password omika_diagnostics_worker" Write-Host " \password omika_diagnostics_web" Write-Host " (jamais dans un script, un log, une commande en clair, ni PowerShell history)." Write-Host " 2. Pointer Worker : Username=omika_diagnostics_worker ; Web : Username=omika_diagnostics_web" Write-Host " (ConnectionStrings__DiagnosticsDatabase, secret injecte SUR LE SERVEUR uniquement)." Write-Host " 3. Valider Worker (INSERT) et Web (SELECT)." Write-Host " 4. SEULEMENT APRES validation : desactiver l'ancien role (action administrateur explicite) :" Write-Host " ALTER ROLE omika_diagnostics_app NOLOGIN;" } finally { if (-not $useExistingPgPassword) { Remove-Item Env:PGPASSWORD -ErrorAction SilentlyContinue } }