fix(recovery): fail closed dr escrow placeholders
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 2m53s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 2m53s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
This commit is contained in:
@@ -41,11 +41,11 @@ def evidence_item(item_id: str) -> dict[str, Any]:
|
||||
"last_reviewed_at",
|
||||
"contains_secret_value=false",
|
||||
],
|
||||
"accepted_ref_examples": [
|
||||
f"vault-item-id-for-{item_id}",
|
||||
f"sealed-envelope-id-for-{item_id}",
|
||||
f"recovery-checklist-id-for-{item_id}",
|
||||
f"ticket-id-for-{item_id}",
|
||||
"accepted_ref_classes": [
|
||||
"password_manager_item_id",
|
||||
"sealed_envelope_id",
|
||||
"recovery_checklist_id",
|
||||
"review_ticket_id",
|
||||
],
|
||||
"rejected_values": [
|
||||
"passwords",
|
||||
@@ -84,13 +84,13 @@ def owner_response_skeleton() -> dict[str, Any]:
|
||||
"responses": [
|
||||
{
|
||||
"gate_id": GATE_ID,
|
||||
"owner_role": "backup_dr_owner",
|
||||
"owner_team": "platform_security",
|
||||
"owner_role": "owner_role_here",
|
||||
"owner_team": "owner_team_here",
|
||||
"decision": "pending",
|
||||
"decision_reason": "fill_only_after_all_redacted_refs_are_present",
|
||||
"decision_reason": "decision_reason_here",
|
||||
"affected_scope": "P0-005 DR credential escrow evidence",
|
||||
"redacted_evidence_refs": ["<ONE_PARENT_TICKET_OR_REVIEW_REF>"],
|
||||
"followup_owner": "backup_dr_owner",
|
||||
"redacted_evidence_refs": ["redacted_evidence_ref_here"],
|
||||
"followup_owner": "followup_owner_here",
|
||||
"runtime_action_requested": False,
|
||||
"runtime_action_authorized": False,
|
||||
"host_write_requested": False,
|
||||
@@ -102,10 +102,10 @@ def owner_response_skeleton() -> dict[str, Any]:
|
||||
"escrow_items": [
|
||||
{
|
||||
"item_id": item_id,
|
||||
"non_secret_evidence_ref": f"<NON_SECRET_REF_FOR_{item_id}>",
|
||||
"recovery_owner": "backup_dr_owner",
|
||||
"reviewer": "security_reviewer",
|
||||
"last_reviewed_at": "YYYY-MM-DD",
|
||||
"non_secret_evidence_ref": "non_secret_evidence_ref_here",
|
||||
"recovery_owner": "owner_role_here",
|
||||
"reviewer": "reviewer_here",
|
||||
"last_reviewed_at": "pending",
|
||||
"contains_secret_value": False,
|
||||
}
|
||||
for item_id in ITEMS
|
||||
|
||||
@@ -172,7 +172,22 @@ def normalized(value: Any) -> str:
|
||||
|
||||
|
||||
def is_placeholder(value: Any) -> bool:
|
||||
return normalized(value).lower() in PLACEHOLDER_VALUES
|
||||
text = normalized(value)
|
||||
lower = text.lower()
|
||||
if lower in PLACEHOLDER_VALUES:
|
||||
return True
|
||||
if re.fullmatch(r"<[^<>]+>", text):
|
||||
return True
|
||||
if lower in {"yyyy-mm-dd", "yyyy/mm/dd"}:
|
||||
return True
|
||||
return lower.startswith(
|
||||
(
|
||||
"vault-item-id-for-",
|
||||
"sealed-envelope-id-for-",
|
||||
"recovery-checklist-id-for-",
|
||||
"ticket-id-for-",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def collect_strings(value: Any, path: str = "$") -> list[tuple[str, str]]:
|
||||
|
||||
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
SCRIPT = ROOT / "scripts" / "reboot-recovery" / "dr-escrow-evidence-checklist.py"
|
||||
PREFLIGHT_SCRIPT = ROOT / "scripts" / "reboot-recovery" / "post-reboot-owner-response-preflight.py"
|
||||
|
||||
ITEMS = {
|
||||
"restic_repository_password",
|
||||
@@ -28,6 +29,28 @@ def load_checklist() -> dict:
|
||||
return json.loads(result.stdout)
|
||||
|
||||
|
||||
def run_preflight(tmp_path: Path, owner_packet: dict, response: dict) -> dict:
|
||||
packet_path = tmp_path / "owner-packet.json"
|
||||
response_path = tmp_path / "owner-response.json"
|
||||
packet_path.write_text(json.dumps(owner_packet, indent=2) + "\n", encoding="utf-8")
|
||||
response_path.write_text(json.dumps(response, indent=2) + "\n", encoding="utf-8")
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(PREFLIGHT_SCRIPT),
|
||||
"--owner-packet-file",
|
||||
str(packet_path),
|
||||
"--response-file",
|
||||
str(response_path),
|
||||
"--json",
|
||||
],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
return json.loads(result.stdout)
|
||||
|
||||
|
||||
def test_checklist_is_single_p0_005_intake_packet() -> None:
|
||||
payload = load_checklist()
|
||||
|
||||
@@ -73,3 +96,57 @@ def test_checklist_outputs_marker_dry_run_commands_only() -> None:
|
||||
assert " --evidence-id <NON_SECRET_REF_FOR_" in command
|
||||
assert "--token" not in command
|
||||
assert "password=" not in command.lower()
|
||||
|
||||
|
||||
def test_unfilled_skeleton_fails_closed_against_preflight(tmp_path: Path) -> None:
|
||||
payload = load_checklist()
|
||||
|
||||
preflight = run_preflight(
|
||||
tmp_path,
|
||||
payload["owner_packet"],
|
||||
payload["owner_response_skeleton"],
|
||||
)
|
||||
|
||||
assert preflight["status"] == "blocked_waiting_owner_response_content"
|
||||
assert preflight["owner_response_received_count"] == 0
|
||||
assert preflight["owner_response_accepted_count"] == 0
|
||||
assert preflight["runtime_action_authorized"] is False
|
||||
assert preflight["secret_value_collection_allowed"] is False
|
||||
blockers = "\n".join(preflight["blockers"])
|
||||
assert "credential_escrow_evidence.owner_role_missing" in blockers
|
||||
assert "credential_escrow_evidence.restic_repository_password.non_secret_evidence_ref_missing" in blockers
|
||||
|
||||
|
||||
def test_angle_bracket_placeholder_refs_cannot_be_accepted(tmp_path: Path) -> None:
|
||||
payload = load_checklist()
|
||||
response = payload["owner_response_skeleton"]
|
||||
item = response["responses"][0]
|
||||
item.update(
|
||||
{
|
||||
"owner_role": "backup_dr_owner",
|
||||
"owner_team": "platform_security",
|
||||
"decision": "accepted",
|
||||
"decision_reason": "reviewed redacted evidence refs only",
|
||||
"redacted_evidence_refs": ["review-ticket-20260629"],
|
||||
"followup_owner": "backup_dr_owner",
|
||||
}
|
||||
)
|
||||
for escrow_item in item["escrow_items"]:
|
||||
item_id = escrow_item["item_id"]
|
||||
escrow_item.update(
|
||||
{
|
||||
"non_secret_evidence_ref": f"<NON_SECRET_REF_FOR_{item_id}>",
|
||||
"recovery_owner": "backup_dr_owner",
|
||||
"reviewer": "security_reviewer",
|
||||
"last_reviewed_at": "2026-06-29",
|
||||
}
|
||||
)
|
||||
|
||||
preflight = run_preflight(tmp_path, payload["owner_packet"], response)
|
||||
|
||||
assert preflight["status"] == "blocked_waiting_owner_response_content"
|
||||
assert preflight["owner_response_received_count"] == 0
|
||||
assert preflight["owner_response_accepted_count"] == 0
|
||||
blockers = "\n".join(preflight["blockers"])
|
||||
for item_id in ITEMS:
|
||||
assert f"credential_escrow_evidence.{item_id}.non_secret_evidence_ref_missing" in blockers
|
||||
|
||||
Reference in New Issue
Block a user