Implement SECRETS-WP-0008 unblocked layer-model obligations
Load pep-stance.yaml as the live unreachable-engine gate and record named stance fields on privileged evidence. Classify evidence, queue load-bearing records in a local outbox, and add heartbeat/drain commands that never sit on a mutation path. Publish proposed SSH-CA and secret-use evidence contracts without adding an OpenBao SSH-CA write. T02 (access-engine decision records) and T06 (no standing credential) stay wait on external endpoints. Assistant: grok Assistant-Session: 01a04cea-cb33-7c63-bad7-c1b0f9f0076b
This commit is contained in:
parent
57f6c4fa65
commit
3cd9955ac9
16 changed files with 1041 additions and 77 deletions
|
|
@ -17,6 +17,7 @@ from pathlib import Path
|
|||
from typing import Any
|
||||
|
||||
from secrets_engine.errors import DecisionError, SecretsEngineError
|
||||
from secrets_engine.evidence_class import KIND_ATTRIBUTIVE, classify
|
||||
from secrets_engine.redact import looks_secret, redact_text
|
||||
|
||||
# Keys that must never carry a value into evidence regardless of nesting.
|
||||
|
|
@ -64,6 +65,21 @@ class EvidenceWriter:
|
|||
with path.open("a", encoding="utf-8") as fh:
|
||||
fh.write(json.dumps(record, sort_keys=True) + "\n")
|
||||
|
||||
def outbox_dir(self) -> Path:
|
||||
path = self.evidence_dir / "outbox"
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
return path
|
||||
|
||||
def _queue_outbox(self, record: dict[str, Any]) -> str:
|
||||
"""Durably queue a load-bearing record. Never talks to audit-core."""
|
||||
record_id = str(record.get("record_id") or uuid.uuid4())
|
||||
path = self.outbox_dir() / f"{record_id}.json"
|
||||
tmp = path.with_suffix(".tmp")
|
||||
tmp.write_text(json.dumps(record, sort_keys=True) + "\n", encoding="utf-8")
|
||||
os.chmod(tmp, 0o600)
|
||||
tmp.replace(path)
|
||||
return record_id
|
||||
|
||||
def record(
|
||||
self,
|
||||
action: str,
|
||||
|
|
@ -75,9 +91,18 @@ class EvidenceWriter:
|
|||
detail: dict[str, Any] | None = None,
|
||||
hub: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
"""Append one non-secret evidence record. Returns the stored record."""
|
||||
"""Append one non-secret evidence record. Returns the stored record.
|
||||
|
||||
Load-bearing records are queued locally first. An audit-core outage
|
||||
cannot occur here because this method never contacts audit-core.
|
||||
Completeness is never claimed. Presence or absence of a record is
|
||||
not consulted as a permission.
|
||||
"""
|
||||
record_id = str(uuid.uuid4())
|
||||
hub_requested = bool(hub and self.hub_url)
|
||||
evidence_class = classify(action, stage)
|
||||
hub_requested = bool(
|
||||
hub and self.hub_url and evidence_class.kind == KIND_ATTRIBUTIVE
|
||||
)
|
||||
record = {
|
||||
"record_id": record_id,
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
|
|
@ -89,7 +114,13 @@ class EvidenceWriter:
|
|||
"decision_id": decision_id,
|
||||
"detail": _scrub(detail or {}),
|
||||
"hub_delivery_requested": hub_requested,
|
||||
"evidence_kind": evidence_class.kind,
|
||||
"evidence_rule": evidence_class.rule_id,
|
||||
"completeness_claimed": False,
|
||||
}
|
||||
if evidence_class.queued_locally:
|
||||
self._queue_outbox(record)
|
||||
record["outbox_queued"] = True
|
||||
self._append_local(record)
|
||||
if hub_requested:
|
||||
delivery = self._post_hub(
|
||||
|
|
@ -205,6 +236,7 @@ class PrivilegedActionEvidence:
|
|||
decision_id: str = ""
|
||||
approval_status: str = "pending"
|
||||
completed: bool = False
|
||||
stance: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not self.approval_required:
|
||||
|
|
@ -218,6 +250,8 @@ class PrivilegedActionEvidence:
|
|||
"decision_ref": self.decision_ref,
|
||||
}
|
||||
)
|
||||
if self.stance:
|
||||
merged.update(self.stance)
|
||||
if extra:
|
||||
merged.update(extra)
|
||||
return merged
|
||||
|
|
@ -239,6 +273,17 @@ class PrivilegedActionEvidence:
|
|||
self.decision_id = str(getattr(decision, "id", ""))
|
||||
self.approval_status = "approved"
|
||||
|
||||
def mark_stance(self, stance: object | None) -> None:
|
||||
if stance is None:
|
||||
return
|
||||
if hasattr(stance, "as_evidence"):
|
||||
payload = stance.as_evidence()
|
||||
elif isinstance(stance, dict):
|
||||
payload = stance
|
||||
else:
|
||||
return
|
||||
self.stance = {key: value for key, value in payload.items() if value != ""}
|
||||
|
||||
def finish(
|
||||
self, result: str, *, detail: dict[str, Any] | None = None
|
||||
) -> dict[str, Any]:
|
||||
|
|
@ -259,6 +304,8 @@ class PrivilegedActionEvidence:
|
|||
return False
|
||||
if isinstance(exc, DecisionError):
|
||||
self.approval_status = "rejected"
|
||||
if getattr(exc, "stance", None):
|
||||
self.mark_stance(exc.stance)
|
||||
if isinstance(exc, (KeyboardInterrupt, SystemExit)):
|
||||
result = "interrupted"
|
||||
elif isinstance(exc, SecretsEngineError):
|
||||
|
|
@ -274,3 +321,76 @@ class PrivilegedActionEvidence:
|
|||
detail=self._detail({"error_type": type(exc).__name__}),
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def write_heartbeat(writer: EvidenceWriter, *, stage: str = "prod") -> dict[str, Any]:
|
||||
"""Positive claim that can go missing. Not a permission and not silence."""
|
||||
queued = 0
|
||||
outbox = writer.evidence_dir / "outbox"
|
||||
if outbox.is_dir():
|
||||
queued = sum(1 for path in outbox.glob("*.json") if path.is_file())
|
||||
return writer.record(
|
||||
"evidence-heartbeat",
|
||||
result="nothing-to-report",
|
||||
stage=stage,
|
||||
detail={
|
||||
"form": "heartbeat",
|
||||
"outbox_depth": queued,
|
||||
"completeness_claimed": False,
|
||||
},
|
||||
hub=False,
|
||||
)
|
||||
|
||||
|
||||
def drain_outbox(
|
||||
writer: EvidenceWriter,
|
||||
*,
|
||||
audit_core_url: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""Best-effort delivery of queued load-bearing records.
|
||||
|
||||
Never called from a mutation path. An audit-core outage leaves files in
|
||||
place and does not raise into a revoke/destroy handler.
|
||||
"""
|
||||
outbox = writer.evidence_dir / "outbox"
|
||||
if not outbox.is_dir():
|
||||
return {
|
||||
"queued": 0,
|
||||
"delivered": 0,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"completeness_claimed": False,
|
||||
}
|
||||
files = sorted(path for path in outbox.glob("*.json") if path.is_file())
|
||||
queued = len(files)
|
||||
if not audit_core_url:
|
||||
return {
|
||||
"queued": queued,
|
||||
"delivered": 0,
|
||||
"failed": 0,
|
||||
"skipped": queued,
|
||||
"completeness_claimed": False,
|
||||
}
|
||||
delivered = 0
|
||||
failed = 0
|
||||
for path in files:
|
||||
try:
|
||||
payload = path.read_text(encoding="utf-8").encode()
|
||||
req = urllib.request.Request(
|
||||
audit_core_url.rstrip("/") + "/v1/events",
|
||||
data=payload,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
urllib.request.urlopen(req, timeout=3)
|
||||
path.unlink()
|
||||
delivered += 1
|
||||
except (urllib.error.URLError, OSError, ValueError):
|
||||
failed += 1
|
||||
return {
|
||||
"queued": queued,
|
||||
"delivered": delivered,
|
||||
"failed": failed,
|
||||
"skipped": 0,
|
||||
"completeness_claimed": False,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue