- 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>
78 lines
2.8 KiB
PHP
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);
|