Read-only scope and the deny paths
Objective
Explain how a read-only scope (the one this course models) shapes which actions a tool can decide, and extend the lab policy so a mutating action is denied under that model.
Concept
For this course we model a Herald tool that holds a read-only scope. Treat that as a teaching model, a deliberate containment choice we adopt for the lab, not a stated product tier or a specific OAuth grant. Under this model a Herald tool can answer authorization questions about read-style access (read, query) but is not authorized to decide, or effect, anything that mutates. An agent can ask "may I read this dataset" and get a governed answer; it cannot get the tool to bless a write, an export, or a delete, because in our model the token behind the tool never carried write scope in the first place.
There are two layers of "no" here, and they are worth separating:
- The scope layer. In our model the tool holds a read-only scope, so a write-style action is out of bounds before any policy runs. The tool simply is not authorized to act on mutations.
- The policy layer. Even for read-style actions, the policy still enforces the federated conditions from lesson 2. Read-only scope is not read-anything: an active analyst can still be denied a restricted read because the SIEM risk is high or the ITSM ticket is missing.
The lab reflects both layers. The policy defines a set of read actions and only ever allows those; a mutating action falls straight through to the default deny, which is the policy-level echo of the read-only scope. This is the honest way to model the scope in Rego: we do not invent an enforceauth run or a scope flag; we model that the tool's decisions are confined to read-style actions and everything else denies.
Default deny does the heavy lifting. Every path that is not an explicit, fully-satisfied allow is a deny. That is what makes the deny tests the important ones: they prove the policy withholds access when any required signal is missing, and they are what catch a false allow if someone loosens a rule.
Hands-on lab
Open ../lab/policy.rego. It ships as a starter: the two allow rules are stubbed out behind a # TODO(learner) block, so nothing is allowed yet and three tests fail. Your job is to author the allow paths until the suite is green.
Run the suite from the cloned university-labs repo:
git clone https://github.com/EnforceAuth/university-labs.git
cd university-labs
opa test courses/evaluate-with-herald -v
On the starter you will see FAIL: 3/8 (the three allow tests). Implement the TODO in policy.rego:
- A non-restricted read rule: allow a read-action,
active, authorized subject on apublicorinternaldataset. - A restricted read rule: the same, but for a
restricteddataset also require alow/mediumSIEM risk level and anapprovedITSM change ticket.
Re-run until you reach PASS: 8/8. Keep iterating until opa test is green. The path for the grader and README is courses/evaluate-with-herald.
Note test_write_action_denied_read_only_scope in ../lab/policy_test.rego: it sends a fully legitimate subject (active, in an authorized group) but with a mutating action, and asserts the decision is deny. If your allow rules are written correctly it stays green, because a mutating action never satisfies the read-action guard. That test is the policy-level stand-in for the read-only scope we model.
Once you are green, extend the lab:
- Add an
exportaction to the scenario and write a deny test proving that even an active, authorized analyst is deniedexport, because it is not a read action. - Add a second read action name your team uses (for example
search) to the policy's read-action set, then add one allow test for asearchon aninternaldataset by an active analyst. - Keep the suite green. If a change lets a mutating action through, a deny test must fail. That is the point: the deny tests are the guardrail on the read-only scope.
Do not add a rule that allows a write or export to pass; that would break the read-only scope we model. The exercise is to widen the read surface, not to break containment.
Check for understanding
- Under the read-only scope this course models, an agent asks a Herald tool to approve a data
export. What happens, and which layer (scope or policy) is the primary reason? - "Read-only scope means any read is allowed." Why is that wrong, using the restricted-dataset rule as your example?
- In the lab, how does default deny make a mutating action fail without any explicit "deny write" rule?