fix(security): isolate workload database identities
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m25s
CD Pipeline / build-and-deploy (push) Successful in 6m42s
CD Pipeline / post-deploy-checks (push) Successful in 1m50s

This commit is contained in:
ogt
2026-07-11 04:20:08 +08:00
parent eb976bdbd2
commit 262c9c8a7e
11 changed files with 1062 additions and 25 deletions

View File

@@ -551,7 +551,7 @@ _AI_AUTOMATION_PROGRAM_WORK_ITEMS: list[dict[str, Any]] = [
"order": 2,
"status": "in_progress",
"title": "收斂 API、Worker 與 executor 的最小權限信任邊界",
"problem": "API/Worker 的 RBACSSH mount 已拆分並由 production receipt 證明;剩餘缺口是 SSH egress policy、每 workload DB role/secret 隔離,以及首條成功 capability lifecycle 尚未同 run 關閉",
"problem": "NetworkPolicy、RBACSSH mount 與 capability lifecycle 已由 production receipt 證明;剩餘缺口只有每 workload DB identity、Secret projection 與 connection budget 隔離",
"professional_practice": "zero trust workload identity、least privilege RBAC、namespace-scoped broker、capability allowlist、short-lived credentials",
"asset_scope_ids": ["hosts", "services", "products", "tools", "observability_and_security"],
"acceptance": [
@@ -562,7 +562,7 @@ _AI_AUTOMATION_PROGRAM_WORK_ITEMS: list[dict[str, Any]] = [
"API/Worker 到 host:22 的 socket verifier 必須 denied只有 broker 可連 allowlisted SSH endpoints",
"API、Worker 與 broker 使用分離的 DB identity/secret projection 與可驗證 connection budget",
],
"next_action": "部署 broker-only SSH NetworkPolicy 與 v2 public verifier再修復 allowlisted canary 的 returncode=2完成同 run capability issue/check/apply/verify/revoke並拆分 workload DB role/secret",
"next_action": "建立 API、Worker、broker 專用 LOGIN role 與獨立 Secret projection執行 representative DB preflight、受控 rollout、live snapshot rollback 與 public-safe budget verifier",
},
{
"id": "AIA-P0-002",

View File

@@ -82,6 +82,17 @@ def _build_database_identity_boundary(
f"{workload_id}_monolithic_secret_env_from_absent"
)
),
"representative_preflight_passed": _truthy(
receipt.get(
f"{workload_id}_representative_db_preflight_passed"
)
),
"table_privilege_gap_count": _int_or_none(
receipt.get(f"{workload_id}_table_privilege_gap_count")
),
"sequence_privilege_gap_count": _int_or_none(
receipt.get(f"{workload_id}_sequence_privilege_gap_count")
),
}
secret_refs = [
@@ -119,6 +130,15 @@ def _build_database_identity_boundary(
and workloads[workload_id]["required_connection_budget"] > 0
for workload_id in _DB_WORKLOAD_IDS
)
representative_preflights_verified = bool(
all(
workloads[workload_id]["representative_preflight_passed"] is True
and workloads[workload_id]["table_privilege_gap_count"] == 0
and workloads[workload_id]["sequence_privilege_gap_count"] == 0
for workload_id in _DB_WORKLOAD_IDS
)
and _truthy(receipt.get("representative_db_preflights_verified"))
)
db_identity_isolated = bool(
distinct_secret_refs
and distinct_role_fingerprints
@@ -129,6 +149,7 @@ def _build_database_identity_boundary(
db_identity_isolated
and null_pool_verified
and workload_budget_verified
and representative_preflights_verified
and _truthy(receipt.get("connection_budget_verified"))
)
@@ -147,6 +168,14 @@ def _build_database_identity_boundary(
blockers.append(f"{workload_id}_database_role_fingerprint_unavailable")
if workload["database_null_pool"] is not True:
blockers.append(f"{workload_id}_database_null_pool_not_verified")
if (
workload["representative_preflight_passed"] is not True
or workload["table_privilege_gap_count"] != 0
or workload["sequence_privilege_gap_count"] != 0
):
blockers.append(
f"{workload_id}_representative_db_preflight_not_verified"
)
connection_limit = workload["connection_limit"]
required_budget = workload["required_connection_budget"]
if (
@@ -170,7 +199,7 @@ def _build_database_identity_boundary(
for workload_id in _DB_WORKLOAD_IDS
]
return {
"schema_version": "awoooi_workload_db_identity_budget_readback_v1",
"schema_version": "awoooi_workload_db_identity_budget_readback_v2",
"status": "verified_ready" if not blockers else "in_progress",
"receipt_ref": receipt_ref,
"verifier": receipt.get("db_identity_verifier") or None,
@@ -188,6 +217,9 @@ def _build_database_identity_boundary(
"distinct_db_role_fingerprints": distinct_role_fingerprints,
"database_null_pool_verified": null_pool_verified,
"workload_connection_budgets_verified": workload_budget_verified,
"representative_db_preflights_verified": (
representative_preflights_verified
),
},
"connection_budget": {
"observed_total": (