Decision logging, masking, and replay in operations
Objective
Operate a continuous-authorization deployment: log every decision, keep sensitive fields out of the log with decision_logs.mask, and use replay to de-risk a change before it is promoted. Then prove in the lab that the field the policy flags for redaction is the field the mask removes.
Concept
The first three lessons got a decision made and enforced. This lesson is about running the system over time. Three operational practices carry a continuous-authorization deployment: log every decision, mask what must not be logged, and replay before you promote.
Log every decision, not just denials
The Unified Audit layer records every decision the PDP makes, allow and deny alike. Logging only denials is a common mistake: it tells you what you blocked but not what you permitted, and "who was allowed to do this, and why" is exactly the question an auditor or an incident responder asks. A complete decision log, with the reason and obligations attached to each decision, is what makes the deployment answerable after the fact. In the lab, the structured decision object (outcome, policy_id, reason, obligations) is the shape worth logging, richer than a bare boolean.
Mask what must not be logged: decision_logs.mask
The decision log's value is that it captures input, and input is where the sensitive data lives: a caller's token, a patient identifier, a prompt. Masking removes named fields from the event before it ships, so you keep the evidence without copying the secret into every downstream log store.
You saw both mechanisms in the prerequisite course. Open-source OPA evaluates a rule at data.system.log.mask over each event and removes the JSON pointers it returns; the original input sits at input.input. Enterprise OPA offers the same through decision_logs.mask as a managed pipeline stage. Wiring is one line, mask_decision: /system/log/mask in the config (see ../lab/opa-config.yaml).
What this course adds is the operating discipline of making the policy and the mask agree. It is not enough that a mask removes some fields; the fields the policy considers sensitive and the fields the audit layer scrubs must be the same set, or they drift and a secret leaks. The lab enforces this: the policy emits a redaction obligation naming /input/context/api_token, the mask removes exactly that pointer, and a test (test_every_redaction_obligation_is_masked) fails if the two ever diverge. Design masking as a contract between the policy and the log pipeline, and test that contract.
Keep the two rules of thumb from the prerequisite: mask the identifiers, keep the context (over-masking destroys the log's evidence value), and prefer redaction to not logging input at all (dropping input makes replay and investigation impossible).
Replay before you promote
A decision log records what already happened; replay uses those recorded inputs to test a candidate policy before it goes live. Writ replays a captured stream of real request inputs against a proposed bundle and produces a diff of decisions that would change: a request that was allowed and would now be denied, or one that was denied and would now be allowed. That diff is a regression test against real production traffic, so you learn the blast radius of a policy change before any user hits it. Run replay as the gate before promoting a bundle from staging to prod. Combined with Writ's signed, byte-for-byte promotion (the same artifact you tested is the one that ships), replay is how a change reaches production with its behavior already known.
prod decision logs --(recorded inputs)--> Writ replay against candidate bundle
|
v
diff of changed decisions
(allow->deny, deny->allow)
|
review, then promote signed bundle
Text version: recorded production inputs from the decision log are replayed against the candidate bundle; the diff of decisions that would change is the regression report; if it is acceptable, the signed bundle is promoted unchanged.
The operating loop
Put the four lessons together. Identity, context, and the PDP produce a decision (Lessons 1 and 2); the PEP enforces it with an explicit fail mode (Lesson 3); the decision is logged, masked, and available for replay (this lesson); and the next policy change is replayed against those logs before promotion. Writ governs the whole loop from the center without ever being on the request path. That is a continuous-authorization deployment in steady state.
Hands-on lab
If you have not finished the decision rules from Lesson 3, do that first so the suite is green. Then focus on the masking-aware tests in ../lab/policy_test.rego:
test_decision_does_not_leak_tokenproves thedecisionobject never carries the caller's secret or anycontextfield, so the decision itself is safe to log.test_policy_flags_token_for_redactionproves the policy names the field to redact (redact:/input/context/api_token).test_mask_selects_api_tokenandtest_mask_redacts_token_from_eventprove the mask both selects and actually removes that field from a shipped event.test_every_redaction_obligation_is_maskedis the contract test: every field the policy flags is a field the mask removes.
Then run the "Going further" exercise in ../lab/README.md that breaks the contract on purpose: remove the token pointer from mask.rego but leave the obligation in policy.rego, and watch test_every_redaction_obligation_is_masked fail. That failure is the operating discipline made mechanical: a policy cannot claim a field is redacted when the audit layer never scrubs it.
git clone https://github.com/EnforceAuth/university-labs.git
cd university-labs
opa test courses/reference-architectures -v
Check for understanding
- Why is logging only denied decisions a mistake? What question can you not answer without the allows?
- What does Writ replay give you that a plain decision log does not, and at what point in the promotion flow do you run it?
- In the lab, what does
test_every_redaction_obligation_is_maskedprotect against, and why is "the policy and the mask agree on what is sensitive" an operating property worth a dedicated test?