fix: harden DWH auth operator guidance
This commit is contained in:
@@ -26,7 +26,7 @@ trap cleanup EXIT
|
||||
temp_root=$(mktemp -d /tmp/thothii-dwh-auth-docs.XXXXXXXX) || report_fail fixture_root
|
||||
fixture_root="$temp_root/fixture"
|
||||
mkdir -p "$fixture_root/docs/install" "$fixture_root/docs/operations" \
|
||||
"$fixture_root/docs/testing/evidence" "$fixture_root/scripts"
|
||||
"$fixture_root/docs/testing/evidence" "$fixture_root/scripts" "$fixture_root/deploy/psd"
|
||||
|
||||
for relative in \
|
||||
docs/install/dwh-auth-server.md \
|
||||
@@ -45,6 +45,7 @@ for relative in \
|
||||
cp "$root/$relative" "$fixture_root/$relative"
|
||||
done
|
||||
cp -a "$root/docs/." "$fixture_root/docs/"
|
||||
cp "$root/deploy/psd/workspace-bindings.env.example" "$fixture_root/deploy/psd/workspace-bindings.env.example"
|
||||
|
||||
"$verify" --root "$fixture_root" || report_fail positive_source
|
||||
report_pass positive_source
|
||||
@@ -65,13 +66,45 @@ fake_key="thtdwh_v1.$(printf 'A%.0s' {1..16}).$(printf 'A%.0s' {1..43})"
|
||||
fake_digest="$(printf 'A%.0s' {1..43})"
|
||||
|
||||
expect_rejected credential_literal docs/install/dwh-auth-client-enrollment.md "$fake_key"
|
||||
expect_rejected credential_digest_literal docs/testing/evidence/psd-dwh-auth-rollout-report-template.md "secret_sha256: $fake_digest"
|
||||
expect_rejected opaque_legacy_header_literal docs/install/dwh-auth-client-enrollment.md "curl --header 'X-API-Key: opaque-legacy-fixture' https://example.invalid/dwh/rpc/ping"
|
||||
expect_rejected credential_digest_literal docs/testing/evidence/psd-dwh-auth-rollout-report-template.md "\"secret_sha256\": \"$fake_digest\""
|
||||
expect_rejected curl_insecure docs/install/dwh-auth-tls.md 'curl -k https://example.invalid/dwh/rpc/ping'
|
||||
expect_rejected tls_disabled docs/install/dwh-auth-tls.md 'verify_tls=false'
|
||||
expect_rejected secret_in_environment docs/install/dwh-auth-client-enrollment.md "DWH_API_KEY=$fake_key"
|
||||
expect_rejected secret_in_environment docs/install/dwh-auth-client-enrollment.md "export THT_WS_PSD_CLINICAL_DWH_API_KEY=$fake_key"
|
||||
expect_rejected secret_in_argv docs/install/dwh-auth-client-enrollment.md "curl -H 'X-API-Key: $fake_key' https://example.invalid/dwh/rpc/ping"
|
||||
expect_rejected world_readable_secret docs/install/dwh-auth-server.md 'chmod 0644 /root/dwh-auth-provision/client.key'
|
||||
expect_rejected sudo_raw_nginx_capture docs/operations/psd-dwh-auth-rollout.md 'sudo nginx -T > /tmp/nginx-full.conf'
|
||||
expect_rejected raw_diff_capture docs/operations/psd-dwh-auth-rollout.md 'sudo diff -u /etc/nginx/sites-available/policlinicosandonato /root/backup.conf'
|
||||
expect_rejected git_raw_diff_capture docs/operations/psd-dwh-auth-rollout.md 'git diff --no-index /root/old.conf /root/new.conf'
|
||||
expect_rejected raw_nginx_capture docs/operations/psd-dwh-auth-rollout.md 'nginx -T > /tmp/nginx-full.conf'
|
||||
expect_rejected compose_coupling docs/install/dwh-auth-server.md 'docker compose up dwh-auth'
|
||||
|
||||
expect_replacement_rejected() {
|
||||
local name=$1 target=$2 needle=$3 replacement=$4
|
||||
local case_root="$temp_root/$name"
|
||||
cp -a "$fixture_root" "$case_root"
|
||||
[[ $(grep -Foc -- "$needle" "$case_root/$target") -eq 1 ]] || report_fail "${name}_fixture"
|
||||
sed -i "s~$needle~$replacement~" "$case_root/$target"
|
||||
if "$verify" --root "$case_root" >/dev/null 2>&1; then report_fail "$name"; fi
|
||||
report_pass "$name"
|
||||
}
|
||||
|
||||
|
||||
expect_global_replacement_rejected() {
|
||||
local name=$1 target=$2 needle=$3 replacement=$4
|
||||
local case_root="$temp_root/$name"
|
||||
cp -a "$fixture_root" "$case_root"
|
||||
[[ $(grep -Foc -- "$needle" "$case_root/$target") -gt 0 ]] || report_fail "${name}_fixture"
|
||||
sed -i "s~$needle~$replacement~g" "$case_root/$target"
|
||||
if "$verify" --root "$case_root" >/dev/null 2>&1; then report_fail "$name"; fi
|
||||
report_pass "$name"
|
||||
}
|
||||
|
||||
expect_replacement_rejected missing_exact_gui_label docs/install/dwh-auth-client-enrollment.md "Validate workspace source" "Validate workspace"
|
||||
expect_replacement_rejected server_transport_contradiction docs/operations/psd-dwh-auth-rollout.md 'server PSD: `postgres_direct` read-only' 'server PSD: `rest_api` read-only'
|
||||
expect_replacement_rejected missing_mac_local_marker deploy/psd/workspace-bindings.env.example "Mac/local/remota" "server PSD"
|
||||
expect_replacement_rejected missing_private_ca docs/install/psd-workspace-setup.md "TLS_CA_FILE" "TLS_CA_REMOVED"
|
||||
expect_global_replacement_rejected missing_socket_path docs/install/dwh-auth-server.md "/run/dwh-auth/verify.sock" "/run/dwh-auth/other.sock"
|
||||
expect_replacement_rejected rest_transport_flag deploy/psd/workspace-bindings.env.example "THT_WS_PSD_CLINICAL_DWH_TRANSPORT=rest_api" "THT_WS_PSD_CLINICAL_DWH_TRANSPORT=postgres_direct"
|
||||
|
||||
report_pass summary
|
||||
|
||||
@@ -24,6 +24,7 @@ docs = {
|
||||
"guide": "docs/guida-utente.md",
|
||||
"index": "docs/index.md",
|
||||
"nav": "mkdocs.yml",
|
||||
"psd_template": "deploy/psd/workspace-bindings.env.example",
|
||||
}
|
||||
text = {}
|
||||
for label, relative in docs.items():
|
||||
@@ -33,11 +34,11 @@ for label, relative in docs.items():
|
||||
text[label] = path.read_text(encoding="utf-8")
|
||||
|
||||
requirements = {
|
||||
"server": ["/var/lib/dwh-auth", "root:dwh-auth", "2750", ".writer.lock", "0640", "/run/dwh-auth/verify.sock", "0660", "systemd", "key create", "key list", "key status", "key revoke", "check", "backup", "rollback", "disinstallazione", "rest_api", "postgres_direct", "ssh_tunnel"],
|
||||
"client": ["Workspace management", "Save runtime secrets", "API_KEY_FILE", "THT_WS_PSD_CLINICAL_DWH_API_KEY_FILE", "TLS_CA_FILE", "/rpc/ping", "rest_api", "postgres_direct", "ssh_tunnel", "401", "503", "rotazione", "revoca"],
|
||||
"server": ["manifest", "curl_cfg", "journalctl", "systemctl disable --now", "trap", "/var/lib/dwh-auth", "root:dwh-auth", "2750", ".writer.lock", "0640", "/run/dwh-auth/verify.sock", "0660", "systemd", "key create", "key list", "key status", "key revoke", "check", "backup", "rollback", "disinstallazione", "rest_api", "postgres_direct", "ssh_tunnel"],
|
||||
"client": ["Workspace management", "Validate workspace source", "Test workspace connections", "Save entered secrets", "Forget stored value", "API_KEY_FILE", "THT_WS_PSD_CLINICAL_DWH_API_KEY_FILE", "TLS_CA_FILE", "/rpc/ping", "rest_api", "postgres_direct", "ssh_tunnel", "401", "503", "rotazione", "revoca"],
|
||||
"tls": ["self-issued", ".it", ".com", "SAN", "TLS_CA_FILE", "openssl x509 -noout -fingerprint -sha256", "fuori banda", "rinnovo", "curl -k"],
|
||||
"rollout": ["Task 9", "Task 10", "IN_DISCUSSION", "postgres_direct", "rest_api", "legacy-shared", "nginx -t", "204", "401", "503", "Qdrant", "Ollama", "rollback"],
|
||||
"manual": ["204", "401", "503", "TLS", "registry", "postgres_direct", "ssh_tunnel"],
|
||||
"rollout": ["PASS/FAIL", "no raw diff", "Task 9", "Task 10", "IN_DISCUSSION", "postgres_direct", "rest_api", "legacy-shared", "nginx -t", "204", "401", "503", "Qdrant", "Ollama", "rollback"],
|
||||
"manual": ["credenziali reali", "sintetici", "/rpc/ping", "204", "401", "503", "TLS", "registry", "postgres_direct", "ssh_tunnel"],
|
||||
"evidence": ["ID pubblici", "owner", "mode", "timestamp", "checksum", "approvazione"],
|
||||
}
|
||||
for label, tokens in requirements.items():
|
||||
@@ -64,16 +65,27 @@ for relative in (docs["server"], docs["client"], docs["tls"], docs["rollout"], d
|
||||
corpus = "\n".join(text.values())
|
||||
for pattern, label in [
|
||||
(r"thtdwh_v1\.[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]{43}", "credential literal"),
|
||||
(r"(?mi)^\s*secret_sha256\s*[:=]\s*[A-Za-z0-9_-]{16,}", "credential digest literal"),
|
||||
(r"(?mi)^\s*[A-Z][A-Z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD)\s*=\s*(?!/|<)[^\s#]+", "secret in environment"),
|
||||
(r"(?i)(?:curl|dwh-auth)[^\n]{0,240}(?:-H\s+['\"][^'\"]*X-API-Key\s*:|--(?:api-key|token|password)\b)", "secret in argv"),
|
||||
(r"(?mi)^\s*[\"']?secret_sha256[\"']?\s*[:=]\s*[\"']?[A-Za-z0-9_-]{16,}", "credential digest literal"),
|
||||
(r"(?mi)^\s*(?:export\s+)?[A-Z][A-Z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD)\s*=\s*(?!/|<)[^\s#]+", "secret in environment"),
|
||||
(r"(?i)(?:curl|dwh-auth)[^\n]{0,240}(?:(?:-H|--header)\s+['\"][^'\"]*X-API-Key\s*:|--(?:api-key|token|password)\b)", "secret in argv"),
|
||||
(r"(?im)^(?!.*(?:non usare|mai usare)).*curl\s+(?:[^\n]*\s)?(?:-k|--insecure)\b|verify_tls\s*=\s*false|insecure_skip_verify", "TLS bypass"),
|
||||
(r"(?i)chmod\s+0?[0-7][0-7][4-7]\s+[^\n]*(?:\.key|secret|provision)", "world-readable secret"),
|
||||
(r"(?m)^\s*nginx\s+-T\b", "raw Nginx capture"),
|
||||
(r"(?m)^\s*(?:sudo\s+)?nginx\s+-T\b", "raw Nginx capture"),
|
||||
(r"(?m)^\s*(?:sudo\s+)?(?:diff\b|git\s+diff\b)", "raw diff capture"),
|
||||
(r"(?i)docker\s+compose[^\n]*\bdwh-auth\b", "Compose coupling"),
|
||||
]:
|
||||
if re.search(pattern, corpus):
|
||||
raise SystemExit(f"dwh-auth docs: forbidden {label}")
|
||||
|
||||
server_direct = re.search(r"server PSD[^\n]{0,100}postgres_direct", text["rollout"], re.I)
|
||||
psd_direct = re.search(r"server PSD[^\n]{0,100}postgres_direct", text["psd"], re.I)
|
||||
if not server_direct or not psd_direct:
|
||||
raise SystemExit("dwh-auth docs: PSD server must remain postgres_direct")
|
||||
template = text["psd_template"]
|
||||
if "tht_ws_psd_clinical_dwh_transport=rest_api" not in template.lower() or "mac/local/remota" not in template.lower() or not re.search(r"mai .*server psd", template, re.I):
|
||||
raise SystemExit("dwh-auth docs: PSD REST template must be explicitly Mac/local/remota, never server PSD")
|
||||
if "TLS_CA_FILE" not in text["psd"] or re.search(r"(?i)nessuna CA|HTTPS pubblico", text["psd"]):
|
||||
raise SystemExit("dwh-auth docs: PSD setup contradicts private CA TLS requirement")
|
||||
|
||||
print("dwh-auth documentation contract passed")
|
||||
PY
|
||||
|
||||
@@ -517,8 +517,10 @@ flow_tokens = [
|
||||
"ThothII",
|
||||
"Update workspace repository",
|
||||
"workspace-secrets",
|
||||
"Validate workspace",
|
||||
"Test connections",
|
||||
"Validate workspace source",
|
||||
"Test workspace connections",
|
||||
"Save entered secrets",
|
||||
"Forget stored value",
|
||||
]
|
||||
for guide in (local_path, server_path):
|
||||
text = guide.read_text()
|
||||
|
||||
Reference in New Issue
Block a user