Practical lab guide
Lab: test and introduce a MicroBank owner policy
Run explicit policy fixtures first, then verify Audit and Deny behavior in the local cluster.
On this page
Predict the fixtures before running the CLI
Read the policy as selection followed by validation. The matchConstraints and matchConditions choose the two local Deployments. The validation then checks a label inside the Pod template. This matches the label location created by the environment lab.
The fixture generator deep-copies one valid input before making each deliberate mistake. Without an independent copy, modifying one dictionary could contaminate later cases and make the expected results hard to trust. The image string is intentionally not runnable: these fixtures exist to evaluate policy, not start application Pods.
Write down the expected results: valid passes; missing, wrong-owner and parent-only fail validation. In the test suite, all four expectations should be satisfied. A successful suite therefore does not mean every resource would be admitted under Deny.
Prerequisites and version boundary
This lab begins offline. Use a Kyverno CLI release supporting policies.kyverno.io/v1 ValidatingPolicy and record its version. The current official documentation uses this API; inspect the installed CRD before the optional live stage. Do not substitute legacy ClusterPolicy fields such as validationFailureAction into this CEL policy.
The rule covers only Accounts and Ledger Deployments in namespace microbank. It is an ownership exercise, not a complete security policy or protection against arbitrary alternative workload kinds.
1. Write the policy
Save platform/policies/owner.yaml:
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: microbank-owner
spec:
validationActions:
- Audit
evaluation:
admission:
enabled: true
background:
enabled: true
matchConstraints:
resourceRules:
- apiGroups: [apps]
apiVersions: [v1]
operations: [CREATE, UPDATE]
resources: [deployments]
matchConditions:
- name: microbank-apis-only
expression: >-
object.metadata.namespace == 'microbank' &&
object.metadata.name in ['accounts', 'ledger']
validations:
- expression: >-
has(object.spec.template.metadata.labels) &&
'platform.learnwithsk.dev/owner' in object.spec.template.metadata.labels &&
object.spec.template.metadata.labels['platform.learnwithsk.dev/owner'] == 'microbank'
message: >-
Accounts and Ledger require Pod-template label
platform.learnwithsk.dev/owner=microbank. Update the reviewed platform base.
The environment lab already puts this label on Pod templates. Use offline fixtures to check the rule, then inspect current workloads separately to confirm they carry the label.
2. Generate isolated positive and negative cases
Save scripts/platform/policy_fixtures.py. It creates test manifests only; the image is an intentionally non-runnable reference because the fixture never starts a Pod. Do not apply these resources to a cluster.
import copy
import json
from pathlib import Path
root = Path('platform/policy-tests')
if root.exists():
raise SystemExit('Refusing to overwrite existing policy fixtures')
base = {
'apiVersion': 'apps/v1', 'kind': 'Deployment',
'metadata': {'name': 'accounts', 'namespace': 'microbank'},
'spec': {'replicas': 1, 'selector': {'matchLabels': {'app': 'accounts'}},
'template': {'metadata': {'labels': {
'app': 'accounts', 'platform.learnwithsk.dev/owner': 'microbank'}},
'spec': {'containers': [{'name': 'accounts',
'image': 'example.invalid/microbank/accounts:fixture'}]}}}}
for case, expected in [('valid', 'pass'), ('missing', 'fail'),
('wrong-owner', 'fail'), ('parent-only', 'fail')]:
resource = copy.deepcopy(base)
labels = resource['spec']['template']['metadata']['labels']
if case in ('missing', 'parent-only'):
labels.pop('platform.learnwithsk.dev/owner')
if case == 'wrong-owner':
labels['platform.learnwithsk.dev/owner'] = 'unassigned'
if case == 'parent-only':
resource['metadata']['labels'] = {'platform.learnwithsk.dev/owner': 'microbank'}
target = root / case
target.mkdir(parents=True)
(target / 'resource.json').write_text(json.dumps(resource, indent=2) + '\n')
(target / 'kyverno-test.yaml').write_text(f"""apiVersion: cli.kyverno.io/v1alpha1
kind: Test
metadata:
name: microbank-owner-{case}
policies:
- ../../policies/owner.yaml
resources:
- resource.json
results:
- isValidatingPolicy: true
policy: microbank-owner
kind: Deployment
resources:
- accounts
result: {expected}
""")
python3 scripts/platform/policy_fixtures.py
kyverno version
kyverno test platform/policy-tests --require-tests
Expected: four test expectations pass, including the three expected policy failures. If the engine reports skipped resources or an unknown API, investigate the version and match configuration before treating the run as a completed test. Reverse one expectation in a disposable copy and confirm the runner fails, then restore it.
Add separate scope fixtures for another namespace, a different Deployment name, and a Service. Confirm they are outside evaluation, not ownership successes. The four supplied tests establish the label contract only.
3. Evaluate the actual rendered manifest
kubectl kustomize platform/workloads/overlays/local > .local/platform-rendered/local.yaml
kyverno apply platform/policies/owner.yaml --resource .local/platform-rendered/local.yaml
Inspect the reported matching Deployments and their results. If nothing was evaluated, investigate the match conditions before proceeding. Retain the policy version, rendered revision, CLI version, and output together. This stage can later run in hosted CI without starting MicroBank or periodic synthetic users.
4. Optional live local admission stage
Use the Kyverno installation guide and compatibility matrix to select and pin a controller/chart release for the existing local Kubernetes version. Review chart values and rendered resources, install only in the disposable lab, and wait for the required controllers/webhooks. Measure memory pressure first; pause optional telemetry or the portal if needed. If you complete only the CLI stage, record live enforcement as unverified.
kubectl --context kind-microbank-advanced get crd validatingpolicies.policies.kyverno.io
kubectl --context kind-microbank-advanced apply -f platform/policies/owner.yaml
kubectl --context kind-microbank-advanced get validatingpolicy microbank-owner -o yaml
Inspect readiness/status. First confirm the valid local render is accepted with server-side dry run. For a negative test, edit a copy of the actual rendered deployment to remove only the owner label; keep the real image, immutable selector, and every other field unchanged. With Audit, the request can be admitted while the rule fails evaluation. Policy reports for existing resources come from their actual evaluation; dry-run requests do not persist a resource for a background scan.
To observe background reporting without breaking an active workload, use a separate disposable fixture namespace and a separately scoped copy of the policy, then remove those exact fixture resources afterward. Do not widen the policy to unrelated namespaces.
When Audit results and recovery access are understood, change only validationActions to [Deny], review/apply it, and repeat positive and negative server-side dry runs. The negative request should be rejected by this named owner rule; another validation error is not evidence of success. Restore Audit after the demonstration unless retaining enforcement is part of your recorded local contract.
Acceptance and cleanup
Keep CLI results distinct from live admission and reporting results. Never deploy the example.invalid fixtures. If retiring the lab policy, remove only microbank-owner through its owner. Keep existing workloads, Secrets, and databases intact. A future policy for images, identities, or security contexts needs its own positive/negative tests.
Explain a misleading green result
An empty match set cannot demonstrate ownership validation. A YAML parser accepting the file cannot demonstrate the CEL expression. A negative fixture rejected for a malformed selector cannot demonstrate the owner rule either. Read the reported resource, policy and reason, and deliberately reverse one expectation to confirm the runner notices.
Keep offline logic testing and optional live admission testing as separate results. The portal can reuse the former without claiming the latter occurred. The four supplied fixtures test label placement/value; add the scope cases described above before claiming complete scope coverage.
Sources
Kyverno ValidatingPolicy↗, Kyverno CLI↗, Kyverno installation↗, Kyverno compatibility↗, Kyverno reports↗.
Your notes and evidence
Record observations, questions, or links to your work. Keep credentials out of your notes.
Back up or restore this path
Progress and notes stay in this browser. A backup contains only this learning path.