Record privileged action failure evidence
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0217e-8c4c-7383-be6b-f50a6e485306
This commit is contained in:
parent
c4504c6de9
commit
f579f3761c
8 changed files with 517 additions and 189 deletions
|
|
@ -112,7 +112,14 @@ def test_verify_defaults_to_every_declared_field_and_one_path_denial(
|
|||
("webhook_secret", True, False),
|
||||
("api_token", False, True),
|
||||
]
|
||||
assert len(records) == 3
|
||||
assert len(records) == 5
|
||||
assert [record[1]["result"] for record in records] == [
|
||||
"attempt",
|
||||
"positive:pass",
|
||||
"positive:pass",
|
||||
"negative:pass",
|
||||
"pass",
|
||||
]
|
||||
|
||||
|
||||
def test_live_destroy_fails_before_approval_or_backend_until_action_contract(
|
||||
|
|
|
|||
98
tests/test_privileged_cli_evidence.py
Normal file
98
tests/test_privileged_cli_evidence.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import copy
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from secrets_engine import cli
|
||||
from secrets_engine.catalog import validate_entry
|
||||
from secrets_engine.config import Config
|
||||
from secrets_engine.errors import BackendError, DecisionError
|
||||
from tests.test_catalog import VALID
|
||||
|
||||
|
||||
def _config(tmp_path):
|
||||
return Config(
|
||||
catalog_dir=tmp_path,
|
||||
policy_dir=tmp_path,
|
||||
evidence_dir=tmp_path / "evidence",
|
||||
hub_url="",
|
||||
bao_addr="http://127.0.0.1:8200",
|
||||
topic_id="test-topic",
|
||||
)
|
||||
|
||||
|
||||
def _records(tmp_path):
|
||||
path = next((tmp_path / "evidence").glob("evidence-*.jsonl"))
|
||||
return [json.loads(line) for line in path.read_text().splitlines()]
|
||||
|
||||
|
||||
def _provision_args(entry):
|
||||
return SimpleNamespace(
|
||||
catalog_id=entry.id,
|
||||
stage=entry.stage,
|
||||
field="api_token",
|
||||
generate=False,
|
||||
from_file="/tmp/test-value-file",
|
||||
bootstrap_token_file=None,
|
||||
)
|
||||
|
||||
|
||||
def test_provision_backend_exception_has_attempt_and_terminal_evidence(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
entry = validate_entry(copy.deepcopy(VALID))
|
||||
monkeypatch.setattr(cli, "get_entry", lambda *_args: entry)
|
||||
monkeypatch.setattr(cli, "_require_lane_approval", lambda *_args: None)
|
||||
monkeypatch.setattr(cli.OpenBaoClient, "resolve", lambda *_args, **_kwargs: object())
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"provision_from_file",
|
||||
lambda *_args, **_kwargs: (_ for _ in ()).throw(
|
||||
BackendError("fake-SUPER-SECRET-backend-message")
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(BackendError):
|
||||
cli.cmd_provision(_config(tmp_path), _provision_args(entry))
|
||||
|
||||
records = _records(tmp_path)
|
||||
assert [record["result"] for record in records] == [
|
||||
"attempt",
|
||||
"failed-BackendError",
|
||||
]
|
||||
assert records[-1]["detail"]["approval_status"] == "not-required"
|
||||
assert "fake-SUPER-SECRET" not in json.dumps(records)
|
||||
|
||||
|
||||
def test_provision_decision_rejection_is_recorded_before_backend(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
data = copy.deepcopy(VALID)
|
||||
data["approval"] = {
|
||||
"model": "decision",
|
||||
"decision_ref": "CCR-2026-0001",
|
||||
}
|
||||
entry = validate_entry(data)
|
||||
monkeypatch.setattr(cli, "get_entry", lambda *_args: entry)
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"_require_lane_approval",
|
||||
lambda *_args: (_ for _ in ()).throw(DecisionError("not approved")),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
cli.OpenBaoClient,
|
||||
"resolve",
|
||||
lambda *_args, **_kwargs: pytest.fail("backend must not be reached"),
|
||||
)
|
||||
|
||||
with pytest.raises(DecisionError):
|
||||
cli.cmd_provision(_config(tmp_path), _provision_args(entry))
|
||||
|
||||
records = _records(tmp_path)
|
||||
assert [record["result"] for record in records] == [
|
||||
"attempt",
|
||||
"failed-DecisionError",
|
||||
]
|
||||
assert records[-1]["detail"]["approval_status"] == "rejected"
|
||||
assert records[-1]["detail"]["decision_ref"] == "CCR-2026-0001"
|
||||
|
|
@ -2,7 +2,10 @@ import json
|
|||
import urllib.error
|
||||
from types import SimpleNamespace
|
||||
|
||||
from secrets_engine.evidence import EvidenceWriter, _scrub
|
||||
import pytest
|
||||
|
||||
from secrets_engine.evidence import EvidenceWriter, PrivilegedActionEvidence, _scrub
|
||||
from secrets_engine.errors import BackendError, DecisionError
|
||||
from secrets_engine.redact import looks_secret, redact_text
|
||||
|
||||
|
||||
|
|
@ -86,3 +89,53 @@ def test_evidence_records_hub_failure_without_raising(tmp_path, monkeypatch):
|
|||
]
|
||||
assert lines[-1]["action"] == "evidence-delivery"
|
||||
assert lines[-1]["result"] == "failed"
|
||||
|
||||
|
||||
def test_privileged_evidence_records_approval_and_backend_failure(tmp_path):
|
||||
writer = EvidenceWriter(evidence_dir=tmp_path, hub_url="")
|
||||
evidence = PrivilegedActionEvidence(
|
||||
writer,
|
||||
"provision",
|
||||
"lane",
|
||||
"prod",
|
||||
decision_ref="CCR-2026-0001",
|
||||
approval_required=True,
|
||||
)
|
||||
|
||||
with pytest.raises(BackendError, match="fake backend failure"):
|
||||
with evidence:
|
||||
evidence.mark_approved(SimpleNamespace(id="e6381a56-3e55-4fac-b22c-63ee1c152ce8"))
|
||||
raise BackendError("fake backend failure")
|
||||
|
||||
lines = [
|
||||
json.loads(line)
|
||||
for line in next(tmp_path.glob("evidence-*.jsonl")).read_text().splitlines()
|
||||
]
|
||||
assert [line["result"] for line in lines] == ["attempt", "failed-BackendError"]
|
||||
assert lines[-1]["detail"]["approval_status"] == "approved"
|
||||
assert lines[-1]["detail"]["error_type"] == "BackendError"
|
||||
assert "fake backend failure" not in json.dumps(lines)
|
||||
|
||||
|
||||
def test_privileged_evidence_records_rejected_decision_without_message(tmp_path):
|
||||
writer = EvidenceWriter(evidence_dir=tmp_path, hub_url="")
|
||||
evidence = PrivilegedActionEvidence(
|
||||
writer,
|
||||
"apply",
|
||||
"lane",
|
||||
"prod",
|
||||
decision_ref="CCR-2026-0001",
|
||||
approval_required=True,
|
||||
)
|
||||
|
||||
with pytest.raises(DecisionError):
|
||||
with evidence:
|
||||
raise DecisionError("operator prose must not enter evidence")
|
||||
|
||||
lines = [
|
||||
json.loads(line)
|
||||
for line in next(tmp_path.glob("evidence-*.jsonl")).read_text().splitlines()
|
||||
]
|
||||
assert lines[-1]["result"] == "failed-DecisionError"
|
||||
assert lines[-1]["detail"]["approval_status"] == "rejected"
|
||||
assert "operator prose" not in json.dumps(lines)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue