Files
fitme3070.pt/shared/platform-tools/bin/encrypt_secrets.php
T
andreandClaude Opus 5.5 0e8b53f620 feat(audit): platform-tools atualizado (12f26fd) e registo de auditoria ligado
- shared/platform-tools: ea3616a → 12f26fd (27 commits). Traz o módulo de
  audit (f761d99, o pin da frota), «manter sessão iniciada», o registo de
  segurança da frota (o gancho que o login já tinha passa a funcionar) e a
  correção do IP do cliente encontrada nesta auditoria (último X-Forwarded-For
  / X-Real-IP), promovida ao partilhado. client_ip() da app delega nele.
- config/functions_audit.php: audit() sobre platform_audit(); tabela
  audit_log criada no primeiro uso, segredos ocultados, nunca parte a ação.
- Registado: login, falha de login, bloqueio por excesso de tentativas,
  logout, pedidos de recuperação, mudança/definição de palavra-passe,
  registo e criação de utilizadores, convites, perfil (antes/depois), todas
  as definições (db_update_setting, com valor anterior), cada passo da
  lixeira (apagar, restaurar, eliminar de vez) e ações nas mensagens.
- Sistema → Auditoria (views/sysadmin/audit.php), só sysadmin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 07:28:13 +01:00

78 lines
2.8 KiB
PHP

<?php
// Encrypt any secret-valued row in system_settings that is still plaintext.
//
// docker exec -u www-data <container> php \
// /var/www/html/shared/platform-tools/bin/encrypt_secrets.php [--dry-run]
//
// Idempotent -- rows already carrying the enc:v1: prefix are left alone -- so it
// is safe to call from deploy.sh on every deploy. Exits non-zero if a row that
// should be encrypted could not be, so a deploy notices.
//
// Every site opens $pdo in its config/db.php; that is the whole bootstrap.
declare(strict_types=1);
$repoRoot = dirname(__DIR__, 3);
$dryRun = in_array('--dry-run', $argv, true);
$pdo = null;
if (is_file($repoRoot . '/config/db.php')) {
require_once $repoRoot . '/config/db.php'; // defines $pdo
if (is_file($repoRoot . '/config/functions.php')) {
require_once $repoRoot . '/config/functions.php';
}
}
if (!$pdo instanceof PDO) {
fwrite(STDERR, "encrypt_secrets: could not open a PDO connection for {$repoRoot}\n");
exit(2);
}
// A bootstrap with a hardcoded fallback schema would connect somewhere
// harmless-looking when the environment did not reach it, and "encrypt"
// nothing, reporting success. Refuse unless the live connection is the schema
// DB_NAME actually names.
$expected = (string) ($_ENV['DB_NAME'] ?? getenv('DB_NAME') ?: '');
$actual = (string) $pdo->query('SELECT DATABASE()')->fetchColumn();
if ($expected !== '' && $actual !== $expected) {
fwrite(STDERR, "encrypt_secrets: connected to '{$actual}' but DB_NAME is '{$expected}' -- refusing to run.\n");
exit(2);
}
require_once dirname(__DIR__) . '/functions_settings.php';
if (!platform_settings_secret_available()) {
fwrite(STDERR, "encrypt_secrets: no APP_SECRET_KEY (or BACKUP_TOKEN) configured -- refusing to run.\n");
exit(3);
}
if ($dryRun) {
// Report without writing: list secret-named rows still in plaintext.
$rows = $pdo->query("SELECT setting_key, setting_value FROM system_settings")->fetchAll(PDO::FETCH_ASSOC);
$pending = [];
foreach ($rows as $row) {
$k = (string) $row['setting_key'];
$v = (string) $row['setting_value'];
if (platform_settings_is_secret($k) && $v !== '' && !str_starts_with($v, PLATFORM_SECRET_PREFIX)) {
$pending[] = $k;
}
}
echo $pending
? "would encrypt: " . implode(', ', $pending) . "\n"
: "nothing to encrypt -- all secret rows are empty or already encrypted\n";
exit(0);
}
$report = platform_settings_encrypt_existing($pdo);
foreach ($report['encrypted'] as $k) echo "encrypted: {$k}\n";
foreach ($report['skipped'] as $k) echo "skipped: {$k}\n";
foreach ($report['failed'] as $k) fwrite(STDERR, "FAILED: {$k}\n");
printf(
"%d encrypted, %d skipped, %d failed\n",
count($report['encrypted']), count($report['skipped']), count($report['failed'])
);
exit($report['failed'] ? 1 : 0);