A Generation Policy That Automates Tenant Onboarding
Goal
Write a generation policy so that default security and resource objects appear together the moment a new tenant namespace is created, and also put in place what else is needed for that policy to work in a real cluster.
Why it matters
Handing a new team a namespace usually runs on a checklist. Apply a NetworkPolicy, put a ResourceQuota on it, add a LimitRange, and copy the registry secret. When people do it, someday a step is missed, and if the missed one happens to be the default-deny policy, that namespace alone stays open for months. A generation rule binds this checklist to the namespace-creation event. You learn three things here. First, if you do not narrow the trigger scope with a label, even system namespaces become targets. Second, anything whose content must not be written in a policy file, such as a Secret, is handled by pointing only at the source with clone — because policies go into git. Third, the background controller is installed with minimal permissions, so you must attach permissions for the kind of resource to be created separately, and if the permission is missing, the request succeeds and only the resource quietly fails to appear. In this environment the controller does not run, so no actual resources are created, and grading uses the results evaluated locally with the kyverno CLI and the structure of the policy YAML.
Steps
- In
/root/policy/generate/add-networkpolicy.yaml, create akind: ClusterPolicy. Inspec.rules[0].generate, writeapiVersion: networking.k8s.io/v1,kind: NetworkPolicy, andname: default-deny, and ingenerate.data.specputpodSelector: {}andpolicyTypes, making sure to includeIngress. - Set the same rule's
match.any[0].resources.kindstoNamespace, and writelabhub.io/tenant: "true"inselector.matchLabelsso that it reacts only to tenant namespaces. - Set
generate.synchronizetotrue. And in/root/policy/generate/out/sync-note.txt, write both the behavior when it is on (if someone edits it by hand, it reverts it) and the difference when it is off (it intervenes only once at creation). - Create
/root/policy/generate/clone-registry-secret.yaml.generate.kindisSecret,generate.clone.namespaceispol-lab, andgenerate.clone.nameisregistry-creds. You must not writegenerate.data, and the secret content must not go into the policy file either. - Set
generate.namespaceofadd-networkpolicy.yamlto"{{ request.object.metadata.name }}"and stategenerate.nameexplicitly. And in/root/policy/generate/out/vars-note.txt, sum up the variables usable in a policy, making sure to includerequest.objectand a variable that holds requester information (request.operationorrequest.userInfo). - In
/root/policy/generate/background-rbac.yaml, create akind: ClusterRole. Includenetworkpoliciesinrulesand putcreateandupdateinverbs(since synchronization is on, updating is needed too). Attach an aggregation label such asrbac.kyverno.io/aggregate-to-background-controller: "true"inmetadata.labels. - Run
kyverno apply /root/policy/generate/add-networkpolicy.yaml --resource /opt/lab/fixtures/policy/resources/target-ns.yaml, and save only the generated NetworkPolicy manifest to/root/policy/generate/out/generated.txt. Do not include the execution summary line (pass: ..., error: 0 ...). - In
/root/policy/generate/tenant-onboarding.yaml, create a policy with 3 rules. Each rule generates aNetworkPolicy, aResourceQuota, and aLimitRangerespectively, the rule names must differ from one another, and all three rules must havegenerate.synchronize: true. And in/root/policy/generate/out/onboarding-report.json, puttrigger_namespaceastenant-alphaand, in thegeneratedarray, at least 3 resources that will be generated, including thekindkey (ResourceQuotamust be included).
Notes
- The trigger fixture is
/opt/lab/fixtures/policy/resources/target-ns.yaml, with the nametenant-alphaand the labellabhub.io/tenant: "true". - If the word
errorappears in the step 7 result file, grading fails. The summary line containserror: 0, so leave only the generated manifest. - You can use only one of
generate.cloneandgenerate.data. - Common mistake 1: imitating a secret value in the step 4 policy file. If strings such as
passwordortokenare in the file, the reason for using clone disappears. - Common mistake 2: copying the rule name and leaving it as it is in step 8. If the names overlap, the policy is rejected.
Write a rule that creates a default NetworkPolicy
In /root/policy/generate/add-networkpolicy.yaml, create a kind: ClusterPolicy. In spec.rules[0].generate, write apiVersion: networking.k8s.io/v1, kind: NetworkPolicy, and name: default-deny, and in generate.data.spec put podSelector: {} and policyTypes, making sure to include Ingress.
A generation rule first writes the apiVersion, kind, name, and namespace of the resource to create. There is a separate key for writing the content directly in the policy.
Narrow the trigger and target selector
Set the same rule's match.any[0].resources.kinds to Namespace, and write labhub.io/tenant: "true" in selector.matchLabels so that it reacts only to tenant namespaces.
In match, you write what creation it should react to. If you create in every namespace, system namespaces also become targets, so narrow it with a label.
Turn on synchronization and sum up what it means
Set generate.synchronize to true. And in /root/policy/generate/out/sync-note.txt, write both the behavior when it is on (if someone edits it by hand, it reverts it) and the difference when it is off (it intervenes only once at creation).
When it is on, it keeps watching even after creation. You pass only if you write up in a file how it differs from when it is off.
Write a clone rule that copies the source
Create /root/policy/generate/clone-registry-secret.yaml. generate.kind is Secret, generate.clone.namespace is pol-lab, and generate.clone.name is registry-creds. You must not write generate.data, and the secret content must not go into the policy file either.
If you write the secret content in the policy file, the policy itself becomes a leak path. There is a method that only tells where the source is, and it cannot be used together with the key that writes the content directly.
Decide the target with request context variables
Set generate.namespace of add-networkpolicy.yaml to "{{ request.object.metadata.name }}" and state generate.name explicitly. And in /root/policy/generate/out/vars-note.txt, sum up the variables usable in a policy, making sure to include request.object and a variable that holds requester information (request.operation or request.userInfo).
You take the name of the namespace that was created from the request object. You pass only if you sum up three kinds of variables usable in a policy.
Write the ClusterRole for generation permissions
In /root/policy/generate/background-rbac.yaml, create a kind: ClusterRole. Include networkpolicies in rules and put create and update in verbs (since synchronization is on, updating is needed too). Attach an aggregation label such as rbac.kyverno.io/aggregate-to-background-controller: "true" in metadata.labels.
The background controller has no permissions of its own. If you turned on synchronization, you need updating as well as creating. Do not forget the aggregation label that gathers permissions.
Run it against the trigger resource and save the generated result
Run kyverno apply /root/policy/generate/add-networkpolicy.yaml --resource /opt/lab/fixtures/policy/resources/target-ns.yaml, and save only the generated NetworkPolicy manifest to /root/policy/generate/out/generated.txt. Do not include the execution summary line (pass: ..., error: 0 ...).
What you save is the generated resource manifest. Do not include the execution summary line — grading looks for the error string.
Build an onboarding policy bundle and a result report
In /root/policy/generate/tenant-onboarding.yaml, create a policy with 3 rules. Each rule generates a NetworkPolicy, a ResourceQuota, and a LimitRange respectively, the rule names must differ from one another, and all three rules must have generate.synchronize: true. And in /root/policy/generate/out/onboarding-report.json, put trigger_namespace as tenant-alpha and, in the generated array, at least 3 resources that will be generated, including the kind key (ResourceQuota must be included).
You create the three things, network, quota, and default resources, as different rules inside one policy. The rule names must not overlap, and synchronization must be on for all of them.