Skip to content

YAML Templates for the CKS Exam

Copy-paste ready YAML templates for all common CKS exam resources. Replace placeholder values (marked with <PLACEHOLDER>) with your actual values.

Usage Strategy

During the exam, you can use kubectl imperative commands where possible and only resort to YAML when needed. However, some resources (NetworkPolicy, EncryptionConfiguration, Audit Policy) require YAML.

Pro tip: Use kubectl run or kubectl create with --dry-run=client -o yaml to generate a base YAML, then edit it.


NetworkPolicy Templates

Default Deny All Ingress

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: <NAMESPACE>
spec:
  podSelector: {}
  policyTypes:
  - Ingress

Default Deny All Egress

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: <NAMESPACE>
spec:
  podSelector: {}
  policyTypes:
  - Egress

Default Deny All (Ingress + Egress)

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: <NAMESPACE>
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Allow Specific Ingress

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-specific-ingress
  namespace: <NAMESPACE>
spec:
  podSelector:
    matchLabels:
      app: <TARGET-APP>
  policyTypes:
  - Ingress
  ingress:
  - from:
    # From pods with specific label in specific namespace (AND logic)
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: <SOURCE-NAMESPACE>
      podSelector:
        matchLabels:
          app: <SOURCE-APP>
    ports:
    - protocol: TCP
      port: <PORT>

Allow Specific Egress with DNS

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-specific-egress
  namespace: <NAMESPACE>
spec:
  podSelector:
    matchLabels:
      app: <SOURCE-APP>
  policyTypes:
  - Egress
  egress:
  # Allow traffic to specific destination
  - to:
    - podSelector:
        matchLabels:
          app: <DEST-APP>
    ports:
    - protocol: TCP
      port: <PORT>
  # Allow DNS (always include this with deny-all egress)
  - ports:
    - protocol: TCP
      port: 53
    - protocol: UDP
      port: 53

NetworkPolicy AND vs OR Logic

yaml
# AND logic (both must match):
- from:
  - namespaceSelector:
      matchLabels:
        env: prod
    podSelector:           # Same array item = AND
      matchLabels:
        app: frontend

# OR logic (either can match):
- from:
  - namespaceSelector:     # First array item
      matchLabels:
        env: prod
  - podSelector:           # Second array item = OR
      matchLabels:
        app: frontend

SecurityContext Templates

Restricted Pod Security Context (Full)

yaml
apiVersion: v1
kind: Pod
metadata:
  name: <POD-NAME>
  namespace: <NAMESPACE>
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: <CONTAINER-NAME>
    image: <IMAGE>
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      runAsNonRoot: true
      capabilities:
        drop:
        - ALL
        add:
        - NET_BIND_SERVICE   # Only if needed
      seccompProfile:
        type: RuntimeDefault
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: cache
      mountPath: /var/cache
    resources:
      limits:
        cpu: "500m"
        memory: "256Mi"
      requests:
        cpu: "100m"
        memory: "128Mi"
  volumes:
  - name: tmp
    emptyDir: {}
  - name: cache
    emptyDir: {}

Minimal Security Context (Baseline)

yaml
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
containers:
- name: app
  securityContext:
    allowPrivilegeEscalation: false
    capabilities:
      drop:
      - ALL

RBAC Templates

Role (Namespaced)

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: <ROLE-NAME>
  namespace: <NAMESPACE>
rules:
- apiGroups: [""]
  resources: ["pods", "services", "configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get"]

ClusterRole

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: <CLUSTERROLE-NAME>
rules:
- apiGroups: [""]
  resources: ["nodes", "namespaces"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
  resources: ["networkpolicies"]
  verbs: ["get", "list", "watch", "create", "update"]

RoleBinding

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: <BINDING-NAME>
  namespace: <NAMESPACE>
subjects:
- kind: ServiceAccount
  name: <SA-NAME>
  namespace: <SA-NAMESPACE>
# For user:
# - kind: User
#   name: <USERNAME>
#   apiGroup: rbac.authorization.k8s.io
# For group:
# - kind: Group
#   name: <GROUP-NAME>
#   apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: <ROLE-NAME>
  apiGroup: rbac.authorization.k8s.io

ClusterRoleBinding

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: <BINDING-NAME>
subjects:
- kind: ServiceAccount
  name: <SA-NAME>
  namespace: <SA-NAMESPACE>
roleRef:
  kind: ClusterRole
  name: <CLUSTERROLE-NAME>
  apiGroup: rbac.authorization.k8s.io
Common API Groups Reference
ResourceapiGroup
pods, services, secrets, configmaps, serviceaccounts, namespaces, nodes"" (core/v1)
deployments, replicasets, daemonsets, statefulsets"apps"
networkpolicies"networking.k8s.io"
ingresses"networking.k8s.io"
roles, clusterroles, rolebindings, clusterrolebindings"rbac.authorization.k8s.io"
tokenreviews"authentication.k8s.io"
subjectaccessreviews"authorization.k8s.io"
podsecuritypolicies"policy"
runtimeclasses"node.k8s.io"

EncryptionConfiguration Template

yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: <KEY-NAME>
        secret: <BASE64-ENCODED-32-BYTE-KEY>
  - identity: {}

Generate a 32-byte key

bash
head -c 32 /dev/urandom | base64

API server flag:

--encryption-provider-config=/etc/kubernetes/pki/encryption-config.yaml

Audit Policy Template

yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
  - "RequestReceived"
rules:
  # Don't log system component requests
  - level: None
    users:
      - "system:kube-controller-manager"
      - "system:kube-scheduler"
      - "system:kube-proxy"

  # Don't log read-only endpoints
  - level: None
    nonResourceURLs:
      - "/healthz*"
      - "/readyz*"
      - "/livez*"

  # Log secrets at Metadata level (don't log request/response bodies)
  - level: Metadata
    resources:
      - group: ""
        resources: ["secrets", "configmaps"]

  # Log authentication at RequestResponse level
  - level: RequestResponse
    resources:
      - group: "authentication.k8s.io"

  # Log exec/attach at RequestResponse level
  - level: RequestResponse
    resources:
      - group: ""
        resources: ["pods/exec", "pods/attach"]

  # Log everything else at Request level
  - level: Request
    resources:
      - group: ""
      - group: "apps"
      - group: "networking.k8s.io"

  # Catch-all at Metadata level
  - level: Metadata

API server flags:

--audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
--audit-log-path=/var/log/kubernetes/audit/audit.log
--audit-log-maxage=7
--audit-log-maxbackup=3
--audit-log-maxsize=100

WARNING

Remember to add volumeMounts and volumes to the API server manifest for both the policy file directory and the log directory.


Pod Security Admission (Namespace Labels)

bash
# Enforce restricted standard
kubectl label namespace <NAMESPACE> \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/warn-version=latest \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/audit-version=latest
Pod Security Standards Summary
LevelWhat It Allows
privilegedEverything (no restrictions)
baselinePrevents known privilege escalations (no privileged, no hostNetwork, no hostPID)
restrictedHeavily restricted (must runAsNonRoot, drop ALL caps, readOnlyRootFs, seccomp RuntimeDefault)

RuntimeClass Template

yaml
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: <RUNTIMECLASS-NAME>
handler: <HANDLER-NAME>   # e.g., runsc, kata-runtime
# Optional: scheduling constraints
scheduling:
  nodeSelector:
    <LABEL-KEY>: <LABEL-VALUE>

Usage in a pod:

yaml
spec:
  runtimeClassName: <RUNTIMECLASS-NAME>

OPA Gatekeeper Templates

ConstraintTemplate

yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: <TEMPLATE-NAME-LOWERCASE>
spec:
  crd:
    spec:
      names:
        kind: <TemplateNameCamelCase>
      validation:
        openAPIV3Schema:
          type: object
          properties:
            <PARAM-NAME>:
              type: array
              items:
                type: string
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package <TEMPLATE-NAME-LOWERCASE>

      violation[{"msg": msg}] {
        container := input.review.object.spec.containers[_]
        # Your logic here
        msg := sprintf("Violation: %v", [container.name])
      }

Constraint

yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: <TemplateNameCamelCase>
metadata:
  name: <CONSTRAINT-NAME>
spec:
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    excludedNamespaces:
    - kube-system
    - gatekeeper-system
    namespaceSelector:
      matchExpressions:
      - key: <LABEL-KEY>
        operator: In
        values:
        - "<LABEL-VALUE>"
  parameters:
    <PARAM-NAME>:
    - "value1"
    - "value2"

AppArmor Profile Template

Profile file (on the node at /etc/apparmor.d/<profile-name>):

#include <tunables/global>

profile <PROFILE-NAME> flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>

  # Allow read access to all files
  file,

  # Deny write by default
  deny /** w,

  # Allow specific write paths
  /tmp/** rw,
  /var/log/app/** rw,

  # Allow network access
  network,
}

Load and verify:

bash
sudo apparmor_parser -r /etc/apparmor.d/<profile-name>
sudo aa-status | grep <PROFILE-NAME>

Pod annotation (legacy):

yaml
metadata:
  annotations:
    container.apparmor.security.beta.kubernetes.io/<CONTAINER-NAME>: localhost/<PROFILE-NAME>

Pod security context (v1.30+):

yaml
spec:
  containers:
  - name: <CONTAINER-NAME>
    securityContext:
      appArmorProfile:
        type: Localhost
        localhostProfile: <PROFILE-NAME>

Seccomp Profile Template

Profile file (at /var/lib/kubelet/seccomp/profiles/<name>.json):

json
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": [
    "SCMP_ARCH_X86_64",
    "SCMP_ARCH_X86",
    "SCMP_ARCH_X32"
  ],
  "syscalls": [
    {
      "names": [
        "read", "write", "open", "close", "stat", "fstat",
        "mmap", "mprotect", "munmap", "brk", "rt_sigaction",
        "rt_sigprocmask", "ioctl", "access", "pipe", "select",
        "nanosleep", "getpid", "clone", "execve", "exit",
        "wait4", "uname", "fcntl", "getcwd", "getuid",
        "getgid", "arch_prctl", "exit_group", "openat",
        "newfstatat", "futex", "epoll_create1", "epoll_ctl",
        "epoll_wait", "getrandom", "close_range"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

Pod spec:

yaml
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/<name>.json

WARNING

The localhostProfile path is relative to /var/lib/kubelet/seccomp/. Do NOT use the absolute path.


ImagePolicyWebhook Templates

Admission Configuration

yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ImagePolicyWebhook
  configuration:
    imagePolicy:
      kubeConfigFile: /etc/kubernetes/admission/image-policy-kubeconfig.yaml
      allowTTL: 50
      denyTTL: 50
      retryBackoff: 500
      defaultAllow: false

Webhook Kubeconfig

yaml
apiVersion: v1
kind: Config
clusters:
- cluster:
    server: https://<WEBHOOK-SERVICE>.<NAMESPACE>.svc:<PORT>/validate
    certificate-authority: /etc/kubernetes/pki/ca.crt
  name: image-policy-webhook
contexts:
- context:
    cluster: image-policy-webhook
    user: api-server
  name: image-policy-webhook
current-context: image-policy-webhook
users:
- name: api-server
  user:
    client-certificate: /etc/kubernetes/pki/apiserver.crt
    client-key: /etc/kubernetes/pki/apiserver.key

API server flags:

--enable-admission-plugins=NodeRestriction,ImagePolicyWebhook
--admission-control-config-file=/etc/kubernetes/admission/image-policy-config.yaml

ServiceAccount Template

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: <SA-NAME>
  namespace: <NAMESPACE>
automountServiceAccountToken: false

Imperative Alternative

bash
kubectl create sa <SA-NAME> -n <NAMESPACE>
kubectl patch sa <SA-NAME> -n <NAMESPACE> \
  -p '{"automountServiceAccountToken": false}'

Falco Custom Rule Template

yaml
- rule: <Rule Name>
  desc: <Description of what the rule detects>
  condition: >
    spawned_process and
    container and
    <additional conditions>
  output: >
    <Alert message with fields>
    (time=%evt.time container=%container.name
    user=%user.name process=%proc.name
    command=%proc.cmdline)
  priority: <EMERGENCY|ALERT|CRITICAL|ERROR|WARNING|NOTICE|INFORMATIONAL|DEBUG>
  tags: [container, <additional-tags>]

File location: /etc/falco/rules.d/custom-rules.yaml

Restart after changes: sudo systemctl restart falco

Released under the MIT License.