Policy-as-code interview questions
Practice policy-as-code interview answers covering Rego, Conftest, Gatekeeper, Kyverno, safe rollouts, and exemption governance.
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.
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/ 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.
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.
API request → authn → authz → mutating webhooks
→ object schema → validating webhooks → etcdOPA 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.
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.
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.
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.
# 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.
# 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.
mutating webhooks → defaults/sidecars validating webhooks → allow/deny final object
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.
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.
test_denies_root if {
count(deny) > 0 with input as {
"kind": "Pod",
"spec": {"containers": [{"name": "c"}]}
}
}
# opa test policy/ -vWhat 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.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.
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.
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.
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.
opa check policy/ opa fmt --diff policy/ opa test policy/ -v
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.
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.
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.
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.
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.
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.
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.
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 ./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 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?
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.'
# 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.
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.
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.
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.
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.
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.
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.
# 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.
spec:
match:
excludedNamespaces: ["kube-system", "gatekeeper-system"]
# still monitor what you excludeInterviewer 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.
spec:
match:
namespaceSelector:
matchLabels: { tenant: "edge-devices" }
parameters:
allowHostPath: trueInterviewer 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 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.
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.
# 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?
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 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 patch K8sRequiredLabels ns-needs-owner --type=merge \
-p '{"spec":{"enforcementAction":"dryrun"}}'
# then: kubectl get constrainttemplate; check status.totalViolationsInterviewer 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.
# 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.
spec:
match:
kinds: [{ apiGroups: ["apps"], kinds: ["Deployment"] }]
namespaces: ["payments"] # not cluster-wide if avoidable
# move "compare to all live Services" checks to audit/CIInterviewer 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.
# 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.
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.
# 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.
# 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 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.
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.
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.
# 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?