cswork / libraryOpen practice workspace

Articles

OPA Rego Interview Questions: Policy Inputs, Defaults, and Authorization Tests

Practice OPA Rego interview questions with a default-deny policy, missing-input decision table, authorization tests, and enforcement checks.

An OPA Rego interview often starts with a tiny allow rule and ends with a much harder question: what happens when a request omits a tenant, policy data is stale, or the caller treats an undefined answer as permission? The useful answer is a decision trace, not a recital of syntax. Separate the request's input, OPA's data, the rule's result, and the service that actually enforces it. Then test cases that should be denied as carefully as the happy path.

This guide uses a document-read policy for a multi-tenant application. A user may read a document only when the user has a reader role, the user and document belong to the same tenant, and that tenant is active. It is deliberately small enough to evaluate by hand yet rich enough to expose missing-field behavior and the boundary between policy evaluation and enforcement. For the broader identity vocabulary, see Authentication and Authorization Interview Questions: OAuth, OIDC, JWT, and Sessions.

Evidence boundary: Official facts about Rego, defaults, tests, and the Data API come from Open Policy Agent documentation checked in September 2026. Candidate reports are not used to claim that a particular employer asks this prompt. Inference is the document-service scenario, its policy contract, and the proposed rollout checks; adapt those choices to the system you are interviewing for.

What belongs in input and what belongs in data?

OPA evaluates a query against a request-specific input document and policy/data documents. The OPA policy-language guide uses input for facts supplied with the current decision and data for policy rules and loaded documents. In our example, input describes the user, attempted action, and target document. data.active_tenants is a separate, centrally managed set of tenant states. That split is useful only if the service has a trustworthy way to populate both sides.

Imagine this request:

{
  "subject": {"id": "u-17", "tenant_id": "acme", "roles": ["reader"]},
  "action": "read",
  "resource": {"id": "doc-9", "tenant_id": "acme"}
}

The caller should not be able to nominate a different resource.tenant_id merely by changing a JSON field. The document service must obtain the resource's tenant from its trusted record, and it must derive the subject from verified authentication context. OPA can compare facts; it cannot make an attacker-controlled input trustworthy. This is a crucial interview boundary: the policy's correctness is conditional on the integrity of the facts sent to it.

data is not automatically fresh. If tenant suspension is delivered through a bundle or another distribution path, ask how activation is observed, what version each OPA instance has, and how quickly a revocation must take effect. A policy that evaluates the wrong snapshot perfectly can still grant an unacceptable request. The rule itself and the data-propagation contract both belong in the design answer.

How do you write a default-deny allow rule?

The OPA default reference explains why a default value is useful: when an allow rule does not match, the result can be false rather than undefined. Here is a Rego v1-style teaching example:

package document.authz

default allow := false

allow if {
    input.action == "read"
    input.subject.tenant_id == input.resource.tenant_id
    input.subject.roles[_] == "reader"
    data.active_tenants[input.resource.tenant_id] == true
}

Read the body as a conjunction. All four conditions must hold for allow to be true. The role expression asks whether at least one array entry is reader; it does not assert that every role is a reader. The tenant comparison checks equality of the two supplied values; the final expression checks the separate active-tenant data. If no rule body succeeds, the explicit default yields false.

The rule is intentionally narrow. It grants no write, delete, owner override, or cross-tenant administrator access. Do not add a broad admin shortcut without clarifying which administrators are allowed across which tenants and which actions they can perform. Small policies are easier to audit when each additional privilege has an explicit condition and a test that fails if the condition is removed.

This is also a good place to explain where the enforcement point lives. OPA returns a decision. The document service must query the intended policy path, validate the response shape, deny on query failure according to its risk contract, and refuse the read before fetching or returning the document. A true result in a log does not prove that all code paths actually enforced it.

What happens when an input field is missing?

If input.subject.tenant_id is absent, the equality expression cannot establish a match. With the default above, the allow decision is false. The same applies when the role list or resource tenant is missing. This is different from assuming a missing tenant means “any tenant,” which would be a dangerous application fallback. The exact request shape should be validated before policy evaluation so malformed callers get a clear operational error, but validation must never convert an absent security attribute into a permissive value.

Without default allow := false, an unmatched allow document may be undefined; the policy-language documentation demonstrates this distinction. An API client that parses an absent result as true, or only rejects literal false, can turn an otherwise restrictive policy into an authorization bypass. The fix is two-sided: give the policy a boolean default and make the enforcement code accept only an explicit true result.

Decision inputExpected resultWhy
Matching tenant, reader role, active tenantAllowEvery condition is satisfied
Missing subject tenantDenyEquality cannot establish same-tenant access
Matching tenant, no reader roleDenyNo role element satisfies the rule
Different resource tenantDenyTenant boundary fails
Matching tenant, inactive tenant dataDenyExternal state blocks access

This decision table is the first useful interview artifact. It gives the interviewer something concrete to challenge: what if roles is a string, the tenant is disabled a moment after evaluation, or the resource moves to another tenant? Answer those as input-schema, freshness, and transaction-boundary questions rather than pretending the short rule solves them all.

How do you test allowed and denied decisions?

The OPA testing guide shows tests in separate Rego files, with input as to substitute a request, and opa test to run them. A minimal pair might be:

package document.authz_test

import data.document.authz

test_reader_can_read_own_tenant if {
    authz.allow with input as {
        "action": "read",
        "subject": {"tenant_id": "acme", "roles": ["reader"]},
        "resource": {"tenant_id": "acme"}
    } with data.active_tenants as {"acme": true}
}

test_missing_tenant_is_denied if {
    not authz.allow with input as {
        "action": "read",
        "subject": {"roles": ["reader"]},
        "resource": {"tenant_id": "acme"}
    } with data.active_tenants as {"acme": true}
}

Run opa test . -v for a visible case list. In CI, the documentation notes that --fail-on-empty avoids a misleading green run when no tests are discovered. Test both false and the absence of an expected result at the integration boundary. The policy unit tests do not prove that the application called OPA with trusted fields or denied when OPA was unavailable.

A stronger matrix includes a mismatched tenant, inactive tenant, write action, missing role list, malformed role type, and tenant-status change. Add one mutation-style check: remove the tenant equality and ensure a cross-tenant denial test fails. This demonstrates that the negative test exercises the intended guard rather than passing for an unrelated reason. Coverage reports can show unvisited policy lines, but line coverage cannot tell you whether the threat model is complete.

For service integration, record the exact policy path, request schema version, and expected response shape in a contract test. Simulate an OPA timeout and a response without result. Confirm that the protected document is not returned. Then test a successful true decision with a real resource whose tenant is looked up server-side. These tests bridge the gap between a correct Rego function and a secure product behavior.

How should the service interpret the Data API response?

OPA's REST API reference documents the Data API, including POSTing a body with an input document to a policy path. A response can include the decision under result, and the service should bind that result to the path it asked for. It must not assume that HTTP 200 alone means allow. HTTP success means the query was handled, not that the authorization answer was favorable.

For this example, the application might query /v1/data/document/authz/allow, require a JSON boolean, and grant only if it is exactly true. A network failure, an absent result, a non-boolean result, or a schema mismatch should follow an explicit failure policy. For a protected document read, fail closed is the natural default. For an emergency administrative workflow, an exception might be justified only with a separately controlled process, not a silent try/except that returns allow.

OPA's decision-log documentation describes logging policy queries and warns that inputs can contain sensitive values. Keep an auditable decision ID, policy version, tenant, resource, and outcome as allowed by your privacy rules; mask secrets and personal data before export. Logs help reconstruct an incident but cannot replace enforcement tests. A missing log entry may mean logging failed, not that no access occurred.

Where do authorization designs fail beyond the Rego syntax?

The main failure modes sit at the edges. If a client can supply an arbitrary subject role, a perfect role check trusts a lie. If an application reads a document before the policy check and accidentally caches the content, denial comes too late. If policy data arrives in some instances later than others, a recently suspended tenant may be denied by one replica and allowed by another. If a service caches an allow result without including resource, action, and tenant in its key, it can reuse a decision for the wrong request.

Ask about time-of-check/time-of-use behavior. If document ownership can change between evaluation and read, the service may need a transaction, an immutable version in the input, or a post-read verification. There is no single Rego keyword that makes that race disappear. The strongest answer names the invariant—“never return a document to a different tenant”—and identifies the trusted resource lookup and enforcement point that preserve it.

Policy rollout deserves a plan too. Version a policy change, run positive and negative tests, observe which instances activate it, and compare decisions on representative requests before broad enforcement if the environment supports shadow evaluation. A shadow result is evidence about impact, not an authorization decision. During rollback, verify the active version rather than assuming that redeploying a file instantly changed every instance.

Sources and Further Reading