6ed93f0311
---ci--- project: acdl phase: 7 milestone: v1.1 status: plan-as-execute persona: security-engineer task: [T-7.4, T-7.5, T-7.6, T-7.7, T-7.8] requirements.covered: [REQ-18, REQ-20, REQ-21] ---/ci--- Wave 3 (security-engineer, 5 files sequential): - T-7.4: schemas/policy_check_result.schema.json (REQ-18 schema half) — canonical shape from ARCHITECTURE.md §12.6; engine enum [checkov,kyverno,opa]; severity enum [critical,high,medium,low,info]; result enum [pass,fail,skipped,error]. Validates as Draft 2020-12; valid instance validates. - T-7.5: adapters/terraform/policy/checkov_adapter.py (REQ-18 adapter half) — Checkov JSON -> PolicyCheckResult; RULE_MAP has all 11 Checkov rule IDs (CKV_AWS_41/45/46/20/57/24/25/1/40/7/33) mapped to the 4 L2 checks + tag/naming; emits ACDL_TAG_NAMING SKIPPED per D-043; stdlib only; tolerates both Checkov JSON shapes. Synthetic fixture produces 3 records all valid against the schema. - T-7.6: platform/audit_ledger_design.md (REQ-20) — three tiers (S3 Object Lock compliance 7yr, acdl-evidence hot index, DynamoDB outbox RPO=0); spike scope (D-041) = hash chain + outbox write; v1.2 build-out = Object Lock + JWS (KMS key, quarterly rotation) + async worker + DLQ + daily checkpoints. Outbox item shape, RPO/RTO table, decision trail. - T-7.7: platform/hitl_matrix_design.md (REQ-21 design half) — pre-execution gate model; Gitea-specific mechanics (workflow_dispatch + gitea.actor per D-042, no Environments API); full 8-concern matrix verbatim from §10.4; timeout 1d warn / 2d freeze; rejection -> HELD + supersedes; CODEOWNERS routing; SoD pointer to the .py. - T-7.8: platform/separation_of_duties.py (REQ-21 impl half) — check(outbox_client, contract_id, current_prod_approver) -> (ok, reason); None outbox -> no-op; equal -> SEPARATION_OF_DUTIES_VIOLATION; distinct -> ok; route_halt_artifact stub; stdlib only (duck-typed outbox_client). All 5 SoD cases verified.
42 lines
1.8 KiB
Python
42 lines
1.8 KiB
Python
"""Check that qaApprover != prodApprover for a contract (ARCHITECTURE.md
|
|
§10.3, D-042). Reads `approver_qa` from the DynamoDB outbox for the
|
|
contractId, compares to the prod-dispatch `gitea.actor`. Blocks on
|
|
equality, emits `SEPARATION_OF_DUTIES_VIOLATION`, routes a halt artifact
|
|
to SRE on-call.
|
|
|
|
Spike scope (A-8.1): the spike is dev-only (REQ-27 contract has
|
|
environment: dev); HITL is not exercised. This module is authored to its
|
|
full v1.2 shape but the spike calls it with current_prod_approver=None
|
|
and a None outbox_client — the check returns (True, 'no QA approver
|
|
recorded (dev-only spike)').
|
|
"""
|
|
|
|
from typing import Optional, Tuple
|
|
|
|
|
|
def check(outbox_client, contract_id: str,
|
|
current_prod_approver: Optional[str]) -> Tuple[bool, str]:
|
|
"""Return (ok, reason). ok=False means block the prod promotion."""
|
|
if outbox_client is None:
|
|
return (True, "no outbox client (dev-only spike)")
|
|
item = outbox_client.get(contract_id)
|
|
if item is None:
|
|
return (True, "no prior approver (first promotion)")
|
|
qa_approver = item.get("approver_qa")
|
|
if not qa_approver:
|
|
return (True, "no QA approver recorded (dev-only spike)")
|
|
if current_prod_approver is None:
|
|
return (True, "no prod approver supplied (dev-only spike)")
|
|
if qa_approver == current_prod_approver:
|
|
return (False,
|
|
f"SEPARATION_OF_DUTIES_VIOLATION: "
|
|
f"qaApprover==prodApprover=={qa_approver}")
|
|
return (True, "distinct")
|
|
|
|
|
|
def route_halt_artifact(contract_id: str, violation_reason: str,
|
|
oncall_client) -> None:
|
|
"""Route a halt artifact to SRE on-call. Spike: stub that logs. v1.2
|
|
wires a real pager."""
|
|
print(f"[halt-artifact] contract={contract_id} reason={violation_reason} "
|
|
f"oncall={oncall_client}", flush=True) |