Lab 31 - Admission policies

Lab 31 - Admission policies

Goal: enforce your own rules with a built-in ValidatingAdmissionPolicy - no extra software. Chapter: Admission policies

Rule 1: no latest

# 1 -
cd ~
cat > vap-latest.yaml <<'YAML'
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: no-latest-tag
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["deployments"]
  validations:
    - expression: >-
        object.spec.template.spec.containers.all(c,
          c.image.contains(':') && !c.image.endsWith(':latest'))
      message: "images must have an explicit tag other than latest"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: no-latest-tag
spec:
  policyName: no-latest-tag
  validationActions: ["Warn"]
  matchResources:
    namespaceSelector:
      matchLabels:
        policy: enforced
YAML
kubectl apply -f vap-latest.yaml
kubectl create ns policy-demo
kubectl label ns policy-demo policy=enforced
# 2 - Warn mode
kubectl create deploy a -n policy-demo --image nginx
kubectl create deploy b -n policy-demo --image nginx:1.27

a is created with a warning; b without. Warn first - see who would be hit.

# 3 - Deny mode
kubectl patch validatingadmissionpolicybinding no-latest-tag --type=merge \
  -p '{"spec":{"validationActions":["Deny"]}}'
kubectl create deploy c -n policy-demo --image nginx:latest     # denied
kubectl create deploy c -n default --image nginx:latest         # allowed - no label on default
kubectl delete deploy c -n default

The binding decides where (namespace selector) and how strictly.

Rule 2: every Deployment needs a team label

# 4 - write it yourself: copy vap-latest.yaml to vap-team.yaml and change
#     name, message and the expression
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: require-team-label
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["deployments"]
  validations:
    - expression: "has(object.metadata.labels) && 'team' in object.metadata.labels"
      message: "Deployments need a 'team' label"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: require-team-label
spec:
  policyName: require-team-label
  validationActions: ["Deny"]
  matchResources:
    namespaceSelector:
      matchLabels:
        policy: enforced
# 5 - test
kubectl apply -f vap-team.yaml
kubectl create deploy d -n policy-demo --image nginx:1.27               # denied
kubectl create deploy d -n policy-demo --image nginx:1.27 --dry-run=client -o yaml \
  | kubectl label -f - --local team=shop -o yaml | kubectl apply -f -   # allowed

Discovery

Infodiscover

Task A. Allow images only from registry.k8s.io and docker.io/library (written as plain nginx:...). Tip: c.image.startsWith(...), ||.

    - expression: >-
        object.spec.template.spec.containers.all(c,
          c.image.startsWith('registry.k8s.io/') || !c.image.contains('/'))
      message: "only registry.k8s.io or Docker official images"

Task B (optional). Install Kyverno and see the same rule as a report on existing objects.

helm repo add kyverno https://kyverno.github.io/kyverno/
helm install kyverno kyverno/kyverno -n kyverno --create-namespace --wait
cat <<'YAML' | kubectl apply -f -
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-team-label
spec:
  validationFailureAction: Audit
  background: true
  rules:
    - name: team-label
      match:
        any:
          - resources:
              kinds: ["Deployment"]
      validate:
        message: "Deployments need a 'team' label"
        pattern:
          metadata:
            labels:
              team: "?*"
YAML
sleep 30; kubectl get policyreports -A

Audit mode shows existing violations without blocking anything - useful before switching to enforce.

Clean up

kubectl delete ns policy-demo
kubectl delete -f vap-latest.yaml -f vap-team.yaml --ignore-not-found
helm uninstall kyverno -n kyverno 2>/dev/null; kubectl delete ns kyverno --ignore-not-found