All resources
Policy as Code · Interview prep

Policy-as-code interview questions

Policy-as-code interview questions from fresher to senior — fundamentals, Rego and testing, Kubernetes enforcement with Gatekeeper and Kyverno, and advanced authz and supply-chain policy, tagged by experience level.

ExperienceAll levelsFresherJuniorMidSenior

Fresher 0–1y · Junior 1–3y · Mid 3–6y · Senior 6+y — every answer is tagged so you can prep for your level.

Fundamentals
What is policy as code?Fresher

Expressing rules — security, compliance, cost, operational — as versioned, testable code that a machine enforces automatically, instead of wiki pages and manual review. It makes governance repeatable and auditable.

Where does policy run — CI, admission, or runtime?Junior

All three, as layers: in CI (conftest) for fast feedback before merge, at admission (Gatekeeper/Kyverno) as the cluster backstop, and at runtime for authorization decisions. The same rules at multiple gates is defense in depth.

CI: conftest on configadmission: block bad objectsruntime: authz decisions
What is admission control in Kubernetes?Junior

A stage in the API request path (after authn/authz) where webhooks can validate or mutate objects before they persist — the hook point where policy engines block non-compliant resources or inject defaults.

API requestauthn / authzmutating -> validating webhookspersist to etcd
OPA vs Gatekeeper vs Kyverno?Mid

OPA is a general policy engine using Rego. Gatekeeper wraps OPA as a Kubernetes admission controller with CRDs. Kyverno is Kubernetes-native, writing policies as YAML (no Rego) with validate/mutate/generate — often easier for pure-k8s teams.

OPA is general-purpose (k8s, microservice authz, Terraform) but you learn Rego. Gatekeeper packages OPA for k8s admission with constraint CRDs and audit. Kyverno is k8s-only and writes policies as YAML, which most platform teams find faster to adopt — the trade is less power than Rego for complex logic.

What is Rego at a glance?Junior

OPA’s declarative query language: you write rules that produce decisions (e.g. deny messages) by querying structured input and data. It is assertion-based — rules define what must be true, and unmatched rules simply yield no result.

A tiny rule
package example
# allow is true only if the request is a GET
default allow := false
allow if input.method == "GET"
Rego & testing
How is a Rego deny rule structured?Mid

A rule in a package that appends a message to a `deny`/`violation` set when a condition on `input` holds — e.g. deny if a container runs as root. Multiple rules with the same name form a logical OR of violations.

Each deny rule is independent: if its body holds, its message joins the deny set; if not, it contributes nothing. So you write many small rules and the engine unions their messages — no if/else ladder.

Deny root containers
package k8s
deny[msg] {
  input.kind == "Pod"
  c := input.spec.containers[_]
  not c.securityContext.runAsNonRoot
  msg := sprintf("container %v must set runAsNonRoot", [c.name])
}
How does Rego evaluate a rule?Mid

Variables in brackets like [_] iterate every element; the rule body is a set of conditions that must all hold (logical AND); a default gives a value when nothing matches. There are no loops — iteration is implicit over collections.

Implicit iteration
# true if ANY container exposes port 22
ssh_exposed {
  input.spec.containers[_].ports[_].containerPort == 22
}
What is conftest used for?Mid

Running Rego policies against structured config files (Kubernetes YAML, Dockerfiles, Terraform plan JSON, HCL) in CI, so violations fail the build before anything reaches a cluster — shift-left policy.

Gate config in CI
conftest test deployment.yaml -p policy/
terraform show -json tf.plan | conftest test -
How do you test policies?Mid

Rego has a built-in test framework (`opa test`) — write test cases with sample input asserting allow/deny. Treat policies like code: unit tests, coverage, and CI so a policy change cannot silently stop enforcing.

A policy unit test
test_denies_root {
  count(deny) > 0 with input as {
    "kind": "Pod",
    "spec": {"containers": [{"name": "c"}]}
  }
}
# run: opa test policy/ -v
What is the data document vs input document?Senior

input is the request being evaluated (the object under admission); data is external context loaded into OPA (allowed registries, exemptions, org rules). Policies combine both to decide, keeping rules generic and data-driven.

Keeping the allowlist in data (not hardcoded in the rule) means updating policy is a data change, reviewable and reloadable without touching Rego. The same rule serves every team by swapping the data it reads.

Kubernetes enforcement
How do ConstraintTemplate and Constraint work in Gatekeeper?Mid

A ConstraintTemplate defines the reusable Rego and a new CRD; a Constraint instantiates it with parameters and a scope (kinds/namespaces). This separates policy logic from where and how strictly it applies.

ConstraintTemplate (Rego + CRD)Constraint (params + scope)admission enforces
Instantiate a template
kind: K8sRequiredLabels        # CRD from the template
metadata: { name: ns-needs-owner }
spec:
  match: { kinds: [{ apiGroups: [""], kinds: ["Namespace"] }] }
  parameters: { labels: ["owner"] }
Audit mode vs enforce mode?Mid

Audit reports existing violations without blocking (safe rollout, backlog visibility); enforce rejects offending requests at admission. You typically ship a policy in audit first, fix violations, then flip to enforce.

Gatekeeper enforcementAction
spec:
  enforcementAction: dryrun   # audit only; -> deny to block
# kubectl get constraint shows current violations
Write a Kyverno policy that requires non-root.Mid

Kyverno policies are YAML: a rule matches resources and a validate block asserts a pattern, with validationFailureAction deciding audit vs enforce. No Rego required.

require runAsNonRoot
kind: ClusterPolicy
spec:
  validationFailureAction: Enforce
  rules:
    - name: non-root
      match: { any: [{ resources: { kinds: [Pod] } }] }
      validate:
        message: "runAsNonRoot is required"
        pattern:
          spec:
            containers:
              - securityContext: { runAsNonRoot: true }
What can Kyverno do beyond validation?Mid

validate (block), mutate (inject sidecars, defaults, securityContext), and generate (create resources like default NetworkPolicies or RoleBindings per namespace) — plus verifyImages for signature checks, all in YAML.

generate a default NetworkPolicy
rules:
  - name: default-deny
    match: { any: [{ resources: { kinds: [Namespace] } }] }
    generate:
      kind: NetworkPolicy
      name: default-deny
      namespace: "{{request.object.metadata.name}}"
How do you handle exemptions safely?Senior

Data-driven exclusions (namespaces/labels) kept in Git and reviewed, time-bounded where possible, and audited — never ad-hoc disabling. The policy engine should log every exemption so exceptions do not quietly become the norm.

Model exemptions as data (an allowlist of namespaces/labels) committed to Git and PR-reviewed, ideally with an expiry, and make the engine log when an exemption is used. Ad-hoc disabling a policy in the cluster is invisible and becomes permanent.

Advanced
How do you roll out a new policy without breaking prod?Mid

Ship it in audit/warn first to surface existing violations, fix or exempt them, then flip to enforce — ideally scoped to a few namespaces before going cluster-wide. Never land a new blocking policy straight into enforce.

deploy in auditreview violationsfix or exemptflip to enforce (scoped -> all)
CI-time policy vs admission-time policy — why both?Senior

CI (conftest) gives fast developer feedback and blocks bad config before merge, but can be bypassed. Admission enforces at the cluster as a backstop for anything applied out-of-band. Defense in depth uses the same policies at both gates.

CI catches issues early and cheaply but a determined user can skip it (direct kubectl, another pipeline). Admission cannot be bypassed but gives feedback late. Running the same policy source at both gates gets you fast feedback and a hard backstop.

How is OPA used for application authorization?Senior

Services query OPA (as a sidecar or library) with the request context and get an allow/deny plus obligations, externalizing fine-grained authz from app code. Policies and data update independently of deploys.

App asks OPA to decide
package authz
default allow := false
allow if {
  input.method == "GET"
  input.user.roles[_] == "reader"
}
How do you distribute policy and observe decisions at scale?Senior

Bundle policies and data as versioned artifacts (OCI/bundle server) that OPAs pull, and ship decision logs to a central sink for audit and debugging. This gives one source of truth and a record of every allow/deny.

Pull a signed bundle
# OPA pulls versioned policy from a registry
opa run -s --set bundles.main.resource=oci://reg/policy:1.4
# decision_logs -> central sink for audit
How does policy enforce supply-chain security?Senior

Admission policies verify image signatures/provenance (cosign, Kyverno verifyImages, Gatekeeper + sigstore) and restrict to allowed registries, so only signed, trusted artifacts run — turning supply-chain requirements into enforced gates.

Kyverno: only signed images
rules:
  - name: verify-signature
    match: { any: [{ resources: { kinds: [Pod] } }] }
    verifyImages:
      - imageReferences: ["registry.example.com/*"]
        attestors: [{ entries: [{ keys: { publicKeys: "-----BEGIN..." } }] }]
Go deeper
Full, hands-on DevSecOps courses
Browse courses