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
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: <NAMESPACE>
spec:
podSelector: {}
policyTypes:
- IngressDefault Deny All Egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: <NAMESPACE>
spec:
podSelector: {}
policyTypes:
- EgressDefault Deny All (Ingress + Egress)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: <NAMESPACE>
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressAllow Specific Ingress
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
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: 53NetworkPolicy AND vs OR Logic
# 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: frontendSecurityContext Templates
Restricted Pod Security Context (Full)
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)
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALLRBAC Templates
Role (Namespaced)
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
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
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.ioClusterRoleBinding
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.ioCommon API Groups Reference
| Resource | apiGroup |
|---|---|
| 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
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
head -c 32 /dev/urandom | base64API server flag:
--encryption-provider-config=/etc/kubernetes/pki/encryption-config.yamlAudit Policy Template
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: MetadataAPI 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=100WARNING
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)
# 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=latestPod Security Standards Summary
| Level | What It Allows |
|---|---|
| privileged | Everything (no restrictions) |
| baseline | Prevents known privilege escalations (no privileged, no hostNetwork, no hostPID) |
| restricted | Heavily restricted (must runAsNonRoot, drop ALL caps, readOnlyRootFs, seccomp RuntimeDefault) |
RuntimeClass Template
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:
spec:
runtimeClassName: <RUNTIMECLASS-NAME>OPA Gatekeeper Templates
ConstraintTemplate
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
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:
sudo apparmor_parser -r /etc/apparmor.d/<profile-name>
sudo aa-status | grep <PROFILE-NAME>Pod annotation (legacy):
metadata:
annotations:
container.apparmor.security.beta.kubernetes.io/<CONTAINER-NAME>: localhost/<PROFILE-NAME>Pod security context (v1.30+):
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):
{
"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:
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/<name>.jsonWARNING
The localhostProfile path is relative to /var/lib/kubelet/seccomp/. Do NOT use the absolute path.
ImagePolicyWebhook Templates
Admission Configuration
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: falseWebhook Kubeconfig
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.keyAPI server flags:
--enable-admission-plugins=NodeRestriction,ImagePolicyWebhook
--admission-control-config-file=/etc/kubernetes/admission/image-policy-config.yamlServiceAccount Template
apiVersion: v1
kind: ServiceAccount
metadata:
name: <SA-NAME>
namespace: <NAMESPACE>
automountServiceAccountToken: falseImperative Alternative
kubectl create sa <SA-NAME> -n <NAMESPACE>
kubectl patch sa <SA-NAME> -n <NAMESPACE> \
-p '{"automountServiceAccountToken": false}'Falco Custom Rule Template
- 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