All resources
Policy as Code · Interview prep

Policy-as-code interview questions

Practice policy-as-code interview answers covering Rego, Conftest, Gatekeeper, Kyverno, safe rollouts, and exemption governance.

Filter

Beginner → Intermediate → Advanced → Expert — answers are written the way you’d say them in a real interview. Advanced and Expert go deeper with war-story detail, architecture diagrams, and the follow-up an interviewer often asks next.

Fundamentals
What is policy as code?Beginner

It's writing your security, compliance, and ops rules as versioned, testable code that machines enforce — not wiki pages and manual review. That way governance is repeatable and you get an audit trail.

Policy lives next to code
policy/
  deny_root.rego
  deny_root_test.rego
# CI: opa test policy/ && conftest test manifests/ -p policy/
Where should policy run — CI, admission, or runtime?Beginner

All three, as layers. Conftest in CI for fast feedback, Gatekeeper or Kyverno at admission as the cluster backstop, and OPA or an authz service at runtime for request decisions. Same intent at multiple gates is defense in depth.

Three gates
CI        → conftest / opa test
admission → Gatekeeper or Kyverno
runtime   → OPA sidecar / envoy external authz
What is admission control in Kubernetes?Beginner

It's the stage after authn/authz where mutating and validating webhooks can change or reject an object before it hits etcd. That's the hook point policy engines use to block non-compliant resources.

Request path
API request → authn → authz → mutating webhooks
         → object schema → validating webhooks → etcd
OPA vs Gatekeeper vs Kyverno in one minute?Intermediate

OPA is the general policy engine — Rego against any JSON-ish input. Gatekeeper wraps OPA for Kubernetes admission with ConstraintTemplates, Constraints, and audit. Kyverno is Kubernetes-native YAML policies — validate, mutate, generate, verifyImages — and it's often faster to adopt if you're pure k8s.

write policyload engineevaluate inputallow / deny / mutate
When to pick which
OPA/Conftest  → Terraform plans, Dockerfiles, multi-system
Gatekeeper    → Rego reuse + k8s admission + audit
Kyverno       → k8s-only teams wanting YAML + mutate/generate
What is Rego at a glance?Beginner

It's OPA's declarative policy language. Rules query structured input and data and produce decisions — allow, deny messages, whatever you define. Unmatched rules contribute nothing; defaults give you the fallback.

Tiny allow rule
package example
default allow := false
allow if input.method == "GET"
How is a Rego deny rule structured?Intermediate

You're writing a rule that adds a message to a deny or violation set when conditions on input hold. Multiple rules with the same name union their messages — so you get small independent checks instead of one giant if/else.

load inputevaluate deny rulesunion messagesnon-empty = fail
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 iteration and AND conditions?Intermediate

There aren't explicit loops — [_] and variables iterate collections. Every expression in the rule body has to succeed, so it's logical AND. default fills in a value when nothing else matches.

Implicit any-match
# true if ANY container exposes port 22
ssh_exposed if {
  input.spec.containers[_].ports[_].containerPort == 22
}
Why write policy as code instead of a wiki checklist?Beginner

Code can be reviewed, tested, versioned, and enforced the same way every time. Wiki rules drift, get skipped under deadline pressure, and don't leave a machine-readable audit trail.

Same rule, two gates
# one Rego package used by:
# - conftest in CI
# - Gatekeeper ConstraintTemplate in cluster
What is a validating vs mutating admission webhook?Beginner

Mutating webhooks can change the object — inject sidecars, set defaults — before validation. Validating webhooks only accept or reject. I use both: mutate for paved roads, validate for the hard requirements.

Order matters
mutating webhooks → defaults/sidecars
validating webhooks → allow/deny final object
Conftest, OPA tests & data
What is Conftest used for?Intermediate

It's for running Rego against structured config in CI — Kubernetes YAML, Dockerfiles, Terraform plan JSON — so violations fail the build before anything reaches a cluster.

render config / plan JSONconftest test -p policy/fail build on deny
Gate manifests and plans
conftest test deployment.yaml -p policy/
terraform show -json tf.plan | conftest test - -p policy/
How do you unit-test Rego policies?Intermediate

I'd use opa test with _test.rego files that assert deny or allow against sample inputs via with input as. Treat policies like code: tests in CI, coverage on critical rules, no silent enforcement gaps.

Policy unit test
test_denies_root if {
  count(deny) > 0 with input as {
    "kind": "Pod",
    "spec": {"containers": [{"name": "c"}]}
  }
}
# opa test policy/ -v
What is the difference between `input` and `data` in OPA?Intermediate

input is the request or object under evaluation. data is external context — allowed registries, exemptions, org config — loaded into OPA. I keep allowlists in data so rules stay generic and updates are data PRs, not Rego rewrites.

Data-driven allowlist
# data.allowed_registries = ["registry.example.com"]
deny[msg] {
  img := input.spec.containers[_].image
  not startswith(img, data.allowed_registries[_])
  msg := sprintf("%v not from allowed registry", [img])
}
You inherited 200 Rego rules with no tests — how do you make them safe to change?Advanced

I'd freeze behavior with golden tests from real fixtures — pass and fail cases — add CI opa test plus coverage on deny paths, then refactor behind those tests. New rules ship with tests first; flaky policies stay in audit mode until they're proven.

Without tests, every policy PR is a production incident waiting to happen. I'd capture fixtures from actual denied AdmissionReviews and known-good manifests, and assert both that deny fires when it should and that compliant input yields an empty deny set. Table-driven cases help. For Gatekeeper, test the library Rego inside ConstraintTemplates the same way, and put a staging dryrun Constraint in place so you see violations before enforce. Document the data schemas too — broken data is as dangerous as broken Rego.

CI contract
opa test policy/ -v --threshold 90
conftest test testdata/good/ -p policy/   # expect 0
conftest test testdata/bad/ -p policy/    # expect fails

Interviewer often follows with: How would you test policies that depend on cluster inventory like existing Namespaces or Images?

How do you distribute policy and observe decisions at scale?Advanced

I'd ship versioned bundles — OCI or a bundle server — that OPA or Gatekeeper pulls, and export decision logs to a central sink. One source of truth for rules and data, plus an audit trail of allow and deny.

Bundles let you promote policy like any other artifact: build, sign, pull on agents. Pin versions per environment so a bad policy rolls back by pointing at the previous digest. Decision logs — carefully redacted — answer why something was denied and feed compliance evidence. Watch cardinality: logging every high-volume allow gets expensive, so I'd sample allows and keep all denies. Gatekeeper's audit scan of existing objects is how you see backlog, not just new requests.

Pull a bundle
opa run -s --set services.bundlereg.url=https://reg.example \
  --set bundles.main.resource=bundles/policy:1.4.2
# decision_logs → SIEM; alert on spike in denies

Interviewer often follows with: How would you sign and verify policy bundles so agents can't be fed malicious Rego?

How do you keep Conftest policies readable for engineers who do not know Rego?Intermediate

I'd invest in clear deny messages, example fixtures in the repo, and a short CONTRIBUTING doc. Prefer many small rules with good msg strings over clever one-liners — the message is the UX.

Actionable deny
msg := sprintf("%v/%v: set securityContext.runAsNonRoot=true (see docs/sec.md)", [input.kind, name])
What is `opa fmt` / `opa check` for in a policy CI job?Beginner

opa check catches parse and compile errors; opa fmt keeps style consistent. Together with opa test they're the minimum lint gate before merging policy changes.

Policy CI
opa check policy/
opa fmt --diff policy/
opa test policy/ -v
Gatekeeper & Kyverno
How do ConstraintTemplate and Constraint work in Gatekeeper?Intermediate

A ConstraintTemplate defines the reusable Rego and registers a CRD. A Constraint instantiates that CRD with parameters and a match scope — kinds, namespaces, labels. Logic stays separate from where and how strictly it applies.

ConstraintTemplate (Rego + CRD)Constraint (params + match)admission + audit
Instantiate a template
kind: K8sRequiredLabels
metadata: { name: ns-needs-owner }
spec:
  match: { kinds: [{ apiGroups: [""], kinds: ["Namespace"] }] }
  parameters: { labels: ["owner"] }
Walk me through rolling out a Gatekeeper policy that requires runAsNonRoot without breaking prod.Advanced

I'd ship the Constraint in dryrun first, read the violation inventory, fix or time-box exemptions, scope enforce to a pilot namespace, then widen. I never land a new blocking policy cluster-wide in deny on day one.

dryrun and warn let admission report without rejecting. Audit periodically scans existing objects so you see debt, not just new creates. Fix workloads through GitOps, use data-driven exemptions with owners and expiry, then flip to deny for one team namespace. Watch initContainers, ephemeral containers, and controllers that recreate noncompliant pods. Pair with PSS labels where they overlap — don't double-fail people with conflicting messages.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
Dry-run then deny
spec:
  enforcementAction: dryrun   # inventory first
# kubectl get k8spspallowprivilegeescalationcontainer -o yaml
# later: enforcementAction: deny  (scoped match.namespaces)

Interviewer often follows with: How would you handle a controller that must create privileged system pods?

Write a Kyverno policy that requires non-root and explain validate vs mutate vs generate.Advanced

Kyverno policies are YAML rules. Validate blocks or warns, mutate injects defaults or sidecars, generate creates companion resources like a default NetworkPolicy per namespace, and verifyImages checks signatures. You don't need Rego for the common k8s guards.

Validate is the admission gate. Mutate can make the secure path automatic — inject runAsNonRoot, drop capabilities — so developers aren't fighting the platform. Generate keeps secondary resources in sync when a Namespace appears. Mutating policies need care around order, idempotency, and not fighting other mutators like mesh injectors. I prefer mutate for defaults and validate for hard requirements. Background scans catch existing debt the same way Gatekeeper audit does.

require runAsNonRoot
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: { name: require-non-root }
spec:
  validationFailureAction: Enforce
  rules:
    - name: non-root
      match: { any: [{ resources: { kinds: [Pod] } }] }
      validate:
        message: "runAsNonRoot is required"
        pattern:
          spec:
            containers:
              - securityContext: { runAsNonRoot: true }

Interviewer often follows with: When would you choose Kyverno generate over a Helm chart creating the same NetworkPolicy?

Audit/dry-run vs enforce — what does each buy you?Intermediate

Dry-run and audit report violations without blocking — safe rollout and backlog visibility. Enforce rejects offending requests at admission. I ship audit, fix, then enforce, ideally namespace-scoped first.

Gatekeeper action
spec:
  enforcementAction: dryrun   # then warn, then deny
# Kyverno: validationFailureAction: Audit | Enforce
How does policy enforce supply-chain rules at admission?Advanced

Policies verify image signatures and provenance — Kyverno verifyImages, Sigstore policy-controller, Gatekeeper plus cosign — and restrict registries so unsigned or untrusted artifacts never schedule.

CI verification is necessary but bypassable with direct kubectl or a rogue pipeline. Admission is the hard backstop: verify cosign signatures or keyless identities, optionally require SLSA provenance attestations, and deny :latest or unknown registries. Cache verification carefully for latency. Fail closed in prod, but keep a break-glass Namespace with loud audit. Combine with PSS restricted so even a signed image can't request privileged escalations.

CI build cosign sign Registry image + signature Admission cosign verify 1 Sign at build keyless OIDC or keypair attach to digest, not tag provenance travels with image 2 Store signatures OCI referrers / .sig same registry as the image immutable by digest 3 Verify on admit Kyverno / Gatekeeper / policy unsigned → reject Pod before it ever schedules Signing without admission is theater. Admission without signing has nothing to check. build → registry → verify → Pod create
Sign — CIStore — registryVerify — admission
Kyverno verifyImages sketch
verifyImages:
  - imageReferences: ["registry.example.com/*"]
    attestors:
      - entries:
          - keys: { publicKeys: "-----BEGIN PUBLIC KEY-----..." }

Interviewer often follows with: Keyless vs key-based signing — what do you pin in the policy?

How do Pod Security Standards relate to Gatekeeper/Kyverno?Intermediate

PSS is built-in admission — privileged, baseline, restricted — via namespace labels. It's a coarse floor. Gatekeeper and Kyverno add org-specific rules: labels, registries, mutates. I use PSS for the floor and policy engines for the rest, and I try not to duplicate the same check in three places.

namespace PSS labelPSA admit/warn/auditcustom policy engine rules
Restricted namespace
kubectl label ns team-a \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest
What does Kyverno `validationFailureAction: Audit` buy you during rollout?Beginner

It reports violations without blocking creates or updates — same idea as Gatekeeper dryrun. You get inventory and developer feedback while you fix workloads before Enforce.

Audit then Enforce
spec:
  validationFailureAction: Audit
# later flip to Enforce for the same ClusterPolicy
How do you unit-test Kyverno policies?Intermediate

I'd use kyverno test with a local directory of policies, resource fixtures, and expected pass/fail results, and run it in CI on every policy PR — same discipline as opa test for Rego.

kyverno test
kyverno test ./policies
# policies/... + kyverno-test.yaml with resources & results
Gatekeeper audit shows 400 violations overnight — how do you triage?Advanced

I'd group by Constraint and namespace, fix platform defaults first because they multiply, open team tickets with sample offending objects, add time-boxed exemptions only where needed, then re-audit before flipping enforce.

Dump Constraint status and violations, get them into a dashboard, and rank by what actually blocks enforce. Prefer mass fixes via GitOps base chart changes over one-off kubectl patches. Watch for false positives from system controllers. Communicate a freeze date for enforce. If most violations are one chart, patch that chart once. Keep dryrun on until the backlog is below an agreed threshold and on-call has a break-glass path.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
Inventory
kubectl get constraints
kubectl get k8srequiredlabels -o json | jq '.items[].status.totalViolations'

Interviewer often follows with: How would you avoid audit noise from completed Jobs or Pods that no longer matter?

Governance, exemptions & app authz
How do you handle policy exemptions without creating permanent holes?Advanced

Exemptions are data in Git — namespaced allowlists or labels — PR-reviewed, owned, time-bounded, and logged when used. I never treat 'turn off the Constraint in the cluster' as the fix.

I'd model data.exemptions or Constraint match exclusions with an owner annotation and expiresOn. CI fails if expiry is missing or past. Decision logs or Kyverno reports should emit when an exemption matched. Security reviews the exemption backlog weekly; expired entries auto-deny. For break-glass, a short-lived ClusterRole and a temporary PolicyException with TTL beats deleting the ClusterPolicy. Interviewers listen for exemptions-as-code and expiry — not 'we exclude kube-system and move on.'

Exemption as data
# data/exemptions.yaml (committed)
namespaces:
  - name: payment-legacy
    owner: payments-oncall
    reason: needs hostPath for HSM
    expiresOn: "2026-09-01"

Interviewer often follows with: How would you stop teams from rubber-stamping exemption PRs forever?

CI policy vs admission policy — why keep both?Advanced

CI with Conftest gives fast developer feedback and cheap fails, but it can be skipped. Admission can't be bypassed for API writes and catches out-of-band applies. I run the same policy source at both gates.

Drift between CI Rego and Gatekeeper templates is a common failure — CI green, admission red, or CI so strict people learn to ignore it. Generate both from one library package, or share the same .rego via Conftest and ConstraintTemplate sync in GitOps. Measure bypasses: alert on kubectl applies from humans in prod namespaces. Preview environments should use the same Constraints as prod, dryrun or enforce as appropriate.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
Same package, two runners
policy/lib/k8s.rego          # shared
# CI: conftest test -p policy/
# cluster: ConstraintTemplate embeds the same rules

Interviewer often follows with: What about mutations that only exist after admission, like injected sidecars — where do you validate those?

How is OPA used for application authorization (not just cluster admission)?Expert

Services query OPA — sidecar, library, or a central PDP — with request context and get allow/deny plus any obligations. Authz policy and data update independently of app deploys, so fine-grained rules leave the codebase.

Patterns I've used: Envoy ext_authz calling OPA, an embedded SDK with policy bundles, or central OPA with careful latency SLOs. Push identity — JWT claims, SPIFFE ID — into input, keep role bindings in data, and version the bundles. Partial evaluation and caching matter at high RPS. Testing uses the same opa test fixtures as infra policy. Pitfalls: inconsistent input schemas across services, allow-by-default mistakes, and sync delay when data changes. Pair with decision logs so you can audit who accessed what.

HTTP authz package
package authz
default allow := false
allow if {
  input.method == "GET"
  input.user.roles[_] == "reader"
}

Interviewer often follows with: How would you canary a new authz policy without locking everyone out?

Design a multi-tenant cluster policy stack for 30 product teams.Expert

I'd set PSS restricted as the floor, Gatekeeper or Kyverno for org rules — registries, labels, privileges — AppProjects and namespace budgets for tenancy, data-driven exemptions with expiry, dryrun-to-enforce rollouts, and shared Rego or YAML libraries tested in CI and synced via GitOps.

Tenancy needs layers: namespace-as-a-service, ResourceQuota and LimitRange, NetworkPolicy default-deny, PSS enforce=restricted, and custom Constraints for company red lines — no privileged, no hostPath, signed images only. Platform owns ClusterPolicies; teams can get namespace-scoped Policies for extra strictness, not weaker. I'd want violation dashboards per team, an SLO on admission latency, and break-glass runbooks. verifyImages on prod namespaces. Avoid one mega-Constraint — compose small, tested rules. Document the paved road so the secure path is the easy chart default.

Namespace PSA labels Pod Security Admission built into apiserver Pod create admit or reject 1 Modes warn · audit · enforce start warn → then enforce exemptions for system NS 2 Levels privileged → baseline → restricted restricted = least privilege no root, no hostPath… 3 Roll out label per namespace fix dry-run before enforce fix workloads that break Modes × levels are independent. Enforce restricted only after warn is clean. namespace labels → PSA → admit/reject
Modes — warn/audit/enforceLevels — baseline/restrictedRollout — per NS
Platform layers
1. PSS restricted (namespace labels)
2. Kyverno/Gatekeeper org Constraints (dryrun→deny)
3. verifyImages for prod
4. exemptions.yaml with owners + expiry
5. CI conftest same ruleset

Interviewer often follows with: How would you onboard a team that needs a privileged DaemonSet for a device plugin?

What failure modes should you expect from admission policy engines?Expert

Webhook downtime can fail-open or fail-closed depending on config. Slow webhooks delay every API write. Overly broad match rules break system namespaces. Mutations can fight each other. And dryrun debt can hide until enforce day.

failurePolicy Ignore vs Fail is a conscious tradeoff: Fail protects compliance but can brick cluster ops if the webhook is down — so run HA replicas, anti-affinity, and monitor webhook latency and errors. Exclude kube-system carefully; don't blanket-exclude everything noisy. Watch for Constraints that deny the policy engine's own updates. Load-test admission under GitOps sync storms. Keep a break-glass admin path documented and tested. At interview level I name HA, failurePolicy, system exclusions, and sync-storm latency explicitly.

Webhook health
kubectl get validatingwebhookconfigurations
kubectl -n gatekeeper-system get pods
# alert: admission_webhook_duration_seconds p99 > 1s

Interviewer often follows with: Would you choose fail-open or fail-closed for a verifyImages webhook in prod?

How would you migrate a fleet from ad-hoc Kubectl privilege to enforced policy without a revolt?Expert

I'd start with metrics and audit-only policies, ship secure defaults in golden charts, give teams self-service fixes, time-box exemptions, then enforce namespace-by-namespace with clear SLAs. Policy as a platform product — not a surprise club.

Change management beats Rego cleverness. Publish the roadmap, show violation dashboards per team, offer office hours, and fix the paved road so compliant deploys are easier than noncompliant ones. Enforce on new namespaces first, then backfill. Pair with PSS warn then enforce. Celebrate reduction in violations, not number of denies. Executive sponsorship matters when a deadline forces enforce. Empathy, metrics, and gradualism are the expert signal.

Namespace PSA labels Pod Security Admission built into apiserver Pod create admit or reject 1 Modes warn · audit · enforce start warn → then enforce exemptions for system NS 2 Levels privileged → baseline → restricted restricted = least privilege no root, no hostPath… 3 Roll out label per namespace fix dry-run before enforce fix workloads that break Modes × levels are independent. Enforce restricted only after warn is clean. namespace labels → PSA → admit/reject
Modes — warn/audit/enforceLevels — baseline/restrictedRollout — per NS
Rollout phases
phase 1: Audit + dashboards
phase 2: mutate defaults in chart templates
phase 3: Enforce on greenfield ns
phase 4: Enforce on brownfield after debt < threshold

Interviewer often follows with: What KPI proves the program is working besides number of Constraints?

Rego `not` and unsafe negation — what bite do juniors hit?Advanced

Negation gets weird with undefined values and iteration — rules may not fire when fields are missing. I prefer positive checks, explicit default, and unit tests that cover the missing-field case.

In Rego, not p is true when p can't be proven. Combined with partial objects, 'deny if not runAsNonRoot' can miss containers where securityContext is entirely absent unless you structure helpers carefully. I use helpers that normalize to false when unset, or object.get with defaults. Always test missing, false, and true. It's a classic deep-dive for anyone claiming Rego fluency.

Safer helper
is_non_root(c) if c.securityContext.runAsNonRoot == true
deny[msg] {
  c := input.spec.containers[_]
  not is_non_root(c)
  msg := sprintf("%v must run as non-root", [c.name])
}

Interviewer often follows with: How would you treat initContainers and ephemeralContainers in the same policy?

What is Policy-as-Code?Beginner

Same idea as policy as code — you're expressing security and compliance rules as versioned code, whether that's Rego, Kyverno YAML, or Sentinel, tested in CI and enforced in pipelines or admission — not as wiki checklists.

Same rule in two places
# CI: conftest test deployment.yaml -p policy/
# cluster: Gatekeeper Constraint uses the same intent
Gatekeeper is deny-forcing a platform CRD update and blocking its own upgrade. How do you recover and prevent self-lockout?Expert

I'd use a break-glass admin path or carefully exempt the system namespace, fix the Constraint match exclusions, then add CI dry-run of Constraint changes against platform manifests before enforce.

Self-lockout happens when Constraints match control-plane or gatekeeper-system resources. Recovery is apiserver break-glass with local creds, remove or relax the Constraint, and be mindful of failurePolicy during the emergency. Prevention: excludedNamespaces for kube-system and gatekeeper-system — carefully — label-based exemptions for controllers, and policy tests that include the engine's own manifests. Never ship a new Constraint at enforce without dry-run metrics. Name the recovery path before you write the deny rule.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
Exempt system namespaces
spec:
  match:
    excludedNamespaces: ["kube-system", "gatekeeper-system"]
# still monitor what you exclude

Interviewer often follows with: How would you avoid over-excluding and creating a policy-free zone?

You need multi-tenant admission: team A may use hostPath, team B must not. How do you model that without one giant Constraint?Expert

I'd parameterize Constraints per namespace or label — ConstraintTemplates plus per-tenant Constraints — or use Kyverno policies scoped by namespace selectors, with a default-deny baseline and explicit grants.

One global deny hostPath breaks legitimate device plugins for team A. Patterns that work: namespace labels selecting different Constraints, PolicyExceptions with expiry, separate Template parameters for allowedNamespaces. Default PSS restricted plus exemptions for node agents only. Governance question: who may create exceptions. Parameterization and tenancy labels beat copy-pasted Rego forks.

Namespace PSA labels Pod Security Admission built into apiserver Pod create admit or reject 1 Modes warn · audit · enforce start warn → then enforce exemptions for system NS 2 Levels privileged → baseline → restricted restricted = least privilege no root, no hostPath… 3 Roll out label per namespace fix dry-run before enforce fix workloads that break Modes × levels are independent. Enforce restricted only after warn is clean. namespace labels → PSA → admit/reject
Modes — warn/audit/enforceLevels — baseline/restrictedRollout — per NS
Label-scoped Constraint
spec:
  match:
    namespaceSelector:
      matchLabels: { tenant: "edge-devices" }
  parameters:
    allowHostPath: true

Interviewer often follows with: How would you prove team B never received the hostPath exemption?

OPA/Conftest is green in CI, but Gatekeeper denies the same manifest in cluster. How do you reconcile the two engines?Expert

I'd compare inputs. CI often tests raw YAML while admission sees mutated objects with defaults. Align the libraries, run Gatekeeper dry-run or test, and feed admission-shaped fixtures into Conftest.

Usual divergences: different Rego packages, missing data documents, Kubernetes defaulting like service account token mounts, and webhook mutation order. Fix it with shared policy modules as a versioned bundle, CI fixtures from kubectl apply --dry-run=server -o yaml, and Gatekeeper constraint status for violations. Version-pin the bundle in both places. 'Same policy' means same input contract — not just the same repo folder name.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
Server-side dry-run fixture
kubectl apply --dry-run=server -o yaml -f deploy.yaml > admission-shaped.yaml
conftest test admission-shaped.yaml -p policy/

Interviewer often follows with: How do you keep Conftest and Gatekeeper on the same bundle version in GitOps?

A verify-images admission webhook outage is blocking all deploys. Do you fail-open, and how do you design for this?Expert

For image provenance I prefer fail-closed with HA webhook replicas and a documented break-glass. Temporary fail-open is an explicit risk acceptance with monitoring — never a silent default.

failurePolicy=Fail bricks deploys when the webhook is down; Ignore lets unsigned images through. I'd design for 3+ replicas, a PDB, multi-AZ, SLOs on webhook latency, and a break-glass Namespace exemption owned by security. Cache or allowlist last-known-good digests if the product supports it. Pair with cluster PSA and network controls so one broken webhook isn't your only defense. State the threat model — supply-chain vs availability — and choose consciously.

CI build cosign sign Registry image + signature Admission cosign verify 1 Sign at build keyless OIDC or keypair attach to digest, not tag provenance travels with image 2 Store signatures OCI referrers / .sig same registry as the image immutable by digest 3 Verify on admit Kyverno / Gatekeeper / policy unsigned → reject Pod before it ever schedules Signing without admission is theater. Admission without signing has nothing to check. build → registry → verify → Pod create
Sign — CIStore — registryVerify — admission
HA + break-glass
kubectl -n cosign-system get deploy,pdb
# break-glass: temporary Namespace label exempt=true with ticket + expiry

Interviewer often follows with: What secondary control still blocks :latest public images if verify-images is fail-open?

Rego unit tests pass, but a production ConstraintTemplate compiles and then denies everything including legitimate pods. What went wrong?Expert

I usually suspect a too-broad deny that is always true, a wrong input path for the ConstraintTemplate, or a missing matcher. I'd roll back the Template, reproduce with gator or test fixtures, and require canary enforce on one Namespace first.

Templates that deny when input.review.object fields are undefined can fire on everything. Tests that only cover happy JSON miss the admission envelope shape — review.object.spec and friends. Process: deploy Template in dryrun, watch violation counts, enforce on a pilot Namespace, then fleet-wide. Keep the previous Template version for rollback. Admission envelope literacy plus progressive enforce is the expert signal.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
Pilot enforce
# 1) dryrun Constraint cluster-wide
# 2) enforce only ns/pilot
# 3) compare allowed deploys before expanding

Interviewer often follows with: How would you structure Rego helpers to avoid deny-all when a field is missing?

Real-world scenarios
Interview: Gatekeeper’s validating webhook starts timing out and suddenly every kubectl create fails cluster-wide. What do you do in the first 15 minutes?Advanced

I'd treat it as an admission outage: check webhook endpoints and Gatekeeper pods, decide consciously between temporary failurePolicy=Ignore versus break-glass, restore webhook health, then re-enable fail-closed with an incident note.

When the API server can't get a timely ValidatingWebhookConfiguration response and failurePolicy is Fail, matching creates and updates get rejected — including platform work. First fifteen minutes: get the validatingwebhookconfiguration, endpoints, and Gatekeeper Deployment/Pod logs; check CPU, memory, and apiserver timeoutSeconds. Emergency paths: scale healthy replicas, fix network or DNS to the service, or briefly set failurePolicy=Ignore with explicit risk acceptance that noncompliant objects may land. Prefer restoring the webhook over living fail-open. After recovery: PDB, multi-replica, latency SLOs, and alert on webhook errors — not only on Constraint denies.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
Webhook health triage
kubectl get validatingwebhookconfiguration -o wide
kubectl -n gatekeeper-system get deploy,po,ep
kubectl -n gatekeeper-system logs deploy/gatekeeper-controller-manager --tail=100
# last resort (ticketed): patch failurePolicy Ignore → restore Fail

Interviewer often follows with: What's the difference between a Constraint deny and a webhook timeout failure from the user's point of view?

Interview: a team skipped dry-run, flipped a Constraint to enforce overnight, and morning CI cannot deploy anything. How do you recover and prevent a repeat?Advanced

I'd roll enforcementAction back to dryrun or warn immediately, unblock deploys, inventory the real violations from audit, then re-enforce on a pilot namespace once owners are fixing debt.

Instant cluster-wide deny is a process failure, not a Rego talent show. Recovery: GitOps revert or kubectl patch enforcementAction to dryrun, communicate the freeze window, and use Gatekeeper audit status to list violators. Prevention is a checklist — dryrun for N days, violation budget near zero, CI fixtures for admission-shaped YAML, and CODEOWNERS on Constraint flips. Never merge enforce without a canary Namespace. Track time-in-dryrun as a platform metric.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
Roll back enforce
kubectl patch K8sRequiredLabels ns-needs-owner --type=merge \
  -p '{"spec":{"enforcementAction":"dryrun"}}'
# then: kubectl get constrainttemplate; check status.totalViolations

Interviewer often follows with: What CI gate would have caught this before the Constraint hit the cluster?

Interview: PolicyExceptions and excludedNamespaces have grown to hundreds with no owners. How do you clean exemption sprawl without breaking prod?Advanced

I'd inventory exceptions with owners and expiry, expire orphans, convert permanent skips into scoped Constraints or PSS labels, and require a ticket plus TTL for every new exemption.

Exemption sprawl is policy theater — the deny path looks strict while half the fleet is carved out. Method: export all PolicyExceptions, match.excludedNamespaces, and data.exemptions, join to CODEOWNERS or namespace labels, and mark anything without an owner or past expiry for deletion in waves. Prefer fixing the workload or parameterizing the Constraint over eternal carve-outs. Governance: PR template fields for risk, expiry, compensating control; a weekly stale-exception report; and deny creating exceptions without those fields — yes, with another policy.

Exception with expiry
# Kyverno PolicyException (sketch)
metadata:
  annotations:
    owner: "payments-platform"
    expires: "2026-09-01"
    ticket: "SEC-4412"
spec:
  exceptions: [{ policyName: require-nonroot, ruleNames: ["nonroot"] }]

Interviewer often follows with: How would you prove an exemption wasn't used as a silent permanent allow-all?

Interview: Gatekeeper webhook latency spikes and apiserver starts failing admission under load. Rego is the suspect — how do you diagnose and fix?Expert

I'd profile which ConstraintTemplates are hot, simplify Rego — avoid nested comprehensions over huge inventories — move heavy checks to audit or CI, and raise capacity only after the policy is lean.

Admission is on the critical path: every create pays for every matching Constraint. Classic killers are iterating cluster inventory in Rego, unbounded comprehensions, and calling external data synchronously. Fix: refresh data documents out-of-band, push expensive inventory checks to Gatekeeper audit or Conftest in CI, split Templates so rarely-needed rules are narrowly matched, and watch webhook duration histograms. Horizontal scale only helps after the algorithmic waste is gone. Treat Rego like production code with budgets and fixtures that assert evaluation time.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
Match narrowly + keep Rego shallow
spec:
  match:
    kinds: [{ apiGroups: ["apps"], kinds: ["Deployment"] }]
    namespaces: ["payments"]   # not cluster-wide if avoidable
# move "compare to all live Services" checks to audit/CI

Interviewer often follows with: Why can a Constraint that is correct still be unsafe to run at admission?

Interview: Kyverno mutate injects a securityContext, but a mesh sidecar injector fights it and pods flap or end noncompliant. How do you resolve webhook wars?Expert

I'd fix webhook reinvocation and ordering, make mutations idempotent and compatible with the mesh's expected pod shape, and move hard requirements to validate after all mutators finish.

MutatingAdmissionWebhook order and reinvocationPolicy decide whether Kyverno or Istio/Linkerd wins. Symptoms: lost labels, missing sidecars, or validate denying the post-mutate object. Resolve by documenting the intended end-state, setting reinvocation so validators see the final object, avoiding fields the mesh owns, and preferring mutate-for-defaults plus validate-for-invariants. Integration tests through the live webhook chain catch what unit tests miss. Admission is a pipeline — design the final object, not isolated policies.

Validate after mutate
# Kyverno: mutate defaults (runAsNonRoot) when unset
# Istio: inject sidecar
# Kyverno validate: deny if any container runs as root (final object)
# reinvocationPolicy: IfNeeded on mutators

Interviewer often follows with: What breaks if you validate in a webhook that runs before the mesh injector?

Interview: you label a namespace PSS restricted and a DaemonSet’s privileged initContainer for node tuning is now blocked. How do you fix it the right way?Advanced

I'd keep restricted for app namespaces, move privileged node agents to a dedicated namespace with baseline or privileged set intentionally, and never weaken restricted fleet-wide for one DaemonSet.

PSS restricted correctly rejects privileged, hostPath, and many initContainer patterns. Node agents — CSI, networking, tuners — often need privileged or host namespaces. That's a tenancy and placement problem, not a reason to drop restricted on payments. Pattern: kube-system or node-agent namespaces at privileged/baseline with tight RBAC and admission allowlists; app namespaces stay restricted. Document which controllers are exempt and why. Pair with Gatekeeper for finer rules inside the privileged NS.

Namespace PSA labels Pod Security Admission built into apiserver Pod create admit or reject 1 Modes warn · audit · enforce start warn → then enforce exemptions for system NS 2 Levels privileged → baseline → restricted restricted = least privilege no root, no hostPath… 3 Roll out label per namespace fix dry-run before enforce fix workloads that break Modes × levels are independent. Enforce restricted only after warn is clean. namespace labels → PSA → admit/reject
Modes — warn/audit/enforceLevels — baseline/restrictedRollout — per NS
Split namespaces by trust
kubectl label ns payments \
  pod-security.kubernetes.io/enforce=restricted
kubectl label ns node-agents \
  pod-security.kubernetes.io/enforce=privileged
# DaemonSet lives only in node-agents

Interviewer often follows with: Why is labeling the app namespace privileged to unblock one initContainer a bad trade?

Interview: audit shows 40% of clusters still on PSS baseline while the standard is restricted. Leadership wants “enforce by Friday.” What is your rollout plan?Advanced

I'd refuse a blind Friday flip: warn and audit first, fix or exempt with TTL, pilot enforce on low-risk namespaces, then wave the rest with a violation burn-down dashboard.

PSS modes — warn, audit, enforce — exist so you can see debt before you brick things. Export failing pods, prioritize easy wins like drop caps and non-root, isolate true privileged workloads, then enforce per Namespace wave. Communicate breakages early. Success metric is percent of namespaces at restricted enforce and exception age — not a calendar slogan. Change management plus the technical label strategy is what I want in an expert answer.

Namespace PSA labels Pod Security Admission built into apiserver Pod create admit or reject 1 Modes warn · audit · enforce start warn → then enforce exemptions for system NS 2 Levels privileged → baseline → restricted restricted = least privilege no root, no hostPath… 3 Roll out label per namespace fix dry-run before enforce fix workloads that break Modes × levels are independent. Enforce restricted only after warn is clean. namespace labels → PSA → admit/reject
Modes — warn/audit/enforceLevels — baseline/restrictedRollout — per NS
Progressive PSA
# week 1: warn+audit=restricted on all app ns
# week 2: enforce on pilot ns
# week 3+: enforce waves; exceptions expire ≤30d

Interviewer often follows with: What signal tells you a namespace is ready to move from audit to enforce?

Expert: design a policy promotion pipeline from git to enforce that cannot skip dry-run.Expert

I'd version policies as signed bundles or PRs, require dry-run apply plus violation budgets in CI and staging, gate enforce on metrics and human approval, and auto-revert on deny spikes.

Promotion path: PR → opa test/conftest → deploy Constraint at dryrun to staging → soak with zero unexpected denies → canary enforce Namespace → fleet. Enforce flips need a second reviewer from platform and security. Watch webhook latency, deny rate, and audit violation count with burn alerts. Rollback is pointing GitOps at the previous Constraint revision within minutes. Policy delivery is a CD problem with stricter gates than app code.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
test in CIdryrun stagingcanary enforcefleet enforce
Enforce gate checklist
# CI must prove:
# - opa test / kyverno test green
# - dryrun violations ≤ budget OR owned exceptions
# - CODEOWNERS approve enforcementAction: deny

Interviewer often follows with: How would you stop someone from kubectl-editing enforcementAction around GitOps?

Expert: Conftest in CI allows a manifest that Gatekeeper later denies because of defaulted fields. How do you close the input gap permanently?Expert

I'd standardize on admission-shaped fixtures from server-side dry-run, share one policy module or bundle, and add a staging apply that must pass Gatekeeper before merge-to-prod.

Kubernetes defaulting and mutation change the object Gatekeeper sees. CI that only tests raw YAML lies. Fix: kubectl apply --dry-run=server -o yaml as the Conftest input, pin the same Rego package versions Gatekeeper loads, and optionally run a Kind cluster with Gatekeeper in PR CI for critical charts. Document the input contract in the policy repo README. Same policy means same input schema and data documents.

kubectl / CI create / apply kube-apiserver validating webhook ✓ ALLOW ✕ DENY 1 ConstraintTemplate Rego + CRD schema defines the policy language reusable across clusters 2 Constraint parameters + match which namespaces / kinds enforcementAction 3 dryrun first enforcementAction: dryrun audit without blocking then flip to deny Template = logic. Constraint = where it applies. Always dryrun before deny. request → webhook → template+constraint → allow/deny
Template — RegoConstraint — scopedryrun — then deny
Admission-shaped CI
kubectl apply --dry-run=server -o yaml -f chart/ | \
  conftest test - -p policy/gatekeeper-lib/

Interviewer often follows with: Which defaulted fields most often cause CI/admission drift?

Expert: a business unit demands a permanent “break-glass namespace” with no admission policies for velocity. How do you respond?Expert

I'd offer a time-boxed, monitored break-glass with logging, network isolation, and forced expiry — not a permanent policy-free zone — and escalate lasting exceptions to a risk register.

Permanent no-policy namespaces become the production path under pressure. Compromises I'll offer: short TTL Namespace with PSA privileged and Gatekeeper exempt, but NetworkPolicy default-deny egress, no internet-facing Services, mandatory runtime detection, and automatic teardown. Every use pages security. If leadership insists on permanence, residual risk and compensating controls go in writing. Negotiate safer velocity — don't rubber-stamp shadow IT.

Namespace PSA labels Pod Security Admission built into apiserver Pod create admit or reject 1 Modes warn · audit · enforce start warn → then enforce exemptions for system NS 2 Levels privileged → baseline → restricted restricted = least privilege no root, no hostPath… 3 Roll out label per namespace fix dry-run before enforce fix workloads that break Modes × levels are independent. Enforce restricted only after warn is clean. namespace labels → PSA → admit/reject
Modes — warn/audit/enforceLevels — baseline/restrictedRollout — per NS
Break-glass contract
ns/break-glass-YYYYMMDD
  psa: privileged
  gatekeeper: exempt (TTL annotation)
  netpol: default deny egress except approved CIDRs
  owner + expires + auto-delete CronJob

Interviewer often follows with: What compensating control is insufficient if break-glass pods can still reach the cloud metadata service?

Interview: two Constraints deny the same pod for different reasons and developers only see a cryptic webhook error. How do you improve the failure UX?Advanced

I'd make deny messages actionable — what, why, doc link — make sure Gatekeeper aggregates causes clearly, and add a self-service 'why was this denied?' runbook tied to Constraint names.

Admission UX is part of policy adoption. Messages should name the Constraint, the offending field, and the fix. Avoid overlapping Templates that emit opaque Rego errors. Platform docs: kubectl describe plus Gatekeeper Constraint status. Consider warn mode during onboarding so engineers learn the messages before hard deny. I'd measure time-to-understand-deny in onboarding surveys.

Actionable msg
msg := sprintf("%v: container %v must set runAsNonRoot=true (Constraint K8sPSPNonRoot; docs/runAsNonRoot.md)", [input.review.object.metadata.name, c.name])

Interviewer often follows with: How would you test that deny messages stay stable across Rego refactors?

Expert: Kyverno generate creates a default NetworkPolicy per Namespace, but a team’s GitOps continuously deletes it. What is the design bug and fix?Expert

I'd treat generated resources as owned by the policy engine or fold the NetworkPolicy into the team's GitOps desired state — one owner — and alert on drift instead of silent regenerate wars.

Generate vs GitOps ownership conflicts cause thrash: Kyverno recreates, Argo or Flux deletes. Fixes: teams own the NetworkPolicy in git with a Conftest or Kyverno validate that it exists and matches baseline; or Kyverno generate with clear labels and Flux ignore rules — never both fighting. I often prefer validate 'must have NP matching baseline' so GitOps stays source of truth. Pick a single control plane for each object kind.

Validate baseline NP in git
# team repo must include networking/allow-dns.yaml
# CI: kyverno apply / conftest test
# cluster: validate policy denies Namespace without labeled NP

Interviewer often follows with: When is generate still the right tool despite GitOps?

Go deeper
Hands-on courses for Policy as Code