├── .gitignore ├── 15-conftest ├── kubernetes │ ├── run.sh │ ├── policy │ │ └── deployment.rego │ └── deploy.yaml └── docker │ ├── run.sh │ ├── Dockerfile │ └── policy │ ├── base.rego │ └── commands.rego ├── 07-encrypt-etcd ├── reencrypt-all-secrets.sh └── encrypt-config.yaml ├── README.md ├── 02-ingress ├── create-tls-secret.sh ├── remove_ingress.sh ├── setup_ingress.sh ├── mango.yaml ├── banana.yaml ├── orange.yaml ├── ingress.yaml ├── pineapple.yaml ├── cert.pem └── key.pem ├── 06-manage-secrets ├── mysecret-01.yaml ├── mysecret-02.yaml └── secret-pod.yaml ├── 18-auditing └── policy │ ├── all-metadata.yaml │ └── generic.yaml ├── 13-image-footprint ├── default │ ├── Dockerfile │ └── app.go ├── multi-stage │ ├── Dockerfile │ └── app.go ├── pkg-versions │ ├── Dockerfile │ └── app.go ├── no-root │ ├── app.go │ └── Dockerfile ├── no-shell │ ├── app.go │ └── Dockerfile └── read-only-fs │ ├── app.go │ └── Dockerfile ├── 03-rbac ├── csr │ ├── jane.conf │ ├── create-csr.sh │ └── template.yaml ├── role-rolebinding │ ├── red-get-secrets.yaml │ └── blue-get-secrets.yaml └── clusterrole-binding │ └── deploy-deleter.yaml ├── 00-cluster-prep ├── cleanup_images.sh ├── 01-calico │ ├── master │ │ ├── destroy_master.sh │ │ └── init_master.sh │ └── worker │ │ ├── destroy_worker.sh │ │ └── init_worker.sh ├── 00-weave-network │ ├── worker │ │ ├── destroy_worker.sh │ │ └── init_worker.sh │ └── master │ │ ├── destroy_master.sh │ │ └── init_master.sh ├── refresh_node.sh └── setup_node.sh ├── 12-opa ├── whitelist-registries │ ├── all_pod_must_have_trusted_images.yaml │ └── k8strustedimages_template.yaml ├── deny-all │ ├── all_pod_always_deny.yaml │ └── alwaysdeny_template.yaml ├── namespace-labels │ ├── all_ns_must_have_cks.yaml │ ├── all_pod_must_have_cks.yaml │ └── k8srequiredlabels_template.yaml ├── deployment-replica-count │ ├── all_deployment_must_have_min_replicacount.yaml │ └── k8sminreplicacount_template.yaml └── opa-gatekeeper.yaml ├── 19-upgrade-scenario ├── base │ └── 1.18.0 │ │ ├── worker │ │ ├── destroy_worker.sh │ │ └── init_worker.sh │ │ ├── master │ │ ├── destroy_master.sh │ │ └── init_master.sh │ │ └── setup_node.sh └── upgrade │ └── 1.19.6 │ ├── upgrade_master.sh │ └── upgrade_worker.sh ├── 14-networkpolicies ├── default-deny.yaml ├── db.yaml ├── default-deny-allow-dns.yaml ├── pod-selector.yaml └── merged.yaml ├── 08-runtime-class ├── runtimeclass.yaml └── gvisor.yaml ├── 04-sa ├── pod.yaml ├── disable-sa-automount.yaml └── sa.yaml ├── 16-imgpolicy-webhook ├── admission_config.yaml ├── kubeconf ├── external-cert.pem ├── apiserver-client-cert.pem ├── external-key.pem └── apiserver-client-key.pem ├── 10-psp ├── allow-priv-esc.yaml ├── priv-containers.yaml ├── example-psp.yaml ├── psp-role.yaml └── kube-apiserver.yaml ├── 09-microservices-os-level-security ├── allow-priv-esc.yaml ├── priv-containers.yaml ├── pod-user-group.yaml └── run-as-nonroot.yaml ├── 17-immutability ├── immutable-pod.yaml └── readonly-fs.yaml ├── 11-sidecar └── app.yaml ├── 05-restrict-api-access ├── disable-anonymous-api-access.yaml └── insecure-apiserver.yaml ├── 01-dashboard ├── dashboard.yaml └── dashboard-insecure.yaml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | **.key 2 | **.crt 3 | **.csr 4 | **.pem 5 | -------------------------------------------------------------------------------- /15-conftest/kubernetes/run.sh: -------------------------------------------------------------------------------- 1 | docker run --rm -v $(pwd):/project instrumenta/conftest test deploy.yaml 2 | -------------------------------------------------------------------------------- /07-encrypt-etcd/reencrypt-all-secrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | kubectl get secrets -A -o yaml | kubectl replace -f - 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cks-prep-repository 2 | Examples created for Certified Kubernetes Security Specialist (CKS) Exam 3 | -------------------------------------------------------------------------------- /15-conftest/docker/run.sh: -------------------------------------------------------------------------------- 1 | docker run --rm -v $(pwd):/project instrumenta/conftest test Dockerfile --all-namespaces 2 | -------------------------------------------------------------------------------- /02-ingress/create-tls-secret.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl create secret tls secure-ingress --key=key.pem --cert=cert.pem 4 | -------------------------------------------------------------------------------- /06-manage-secrets/mysecret-01.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | user: YWRtaW4= 4 | kind: Secret 5 | metadata: 6 | creationTimestamp: null 7 | name: mysecret 8 | -------------------------------------------------------------------------------- /18-auditing/policy/all-metadata.yaml: -------------------------------------------------------------------------------- 1 | # Log all requests at the Metadata level. 2 | apiVersion: audit.k8s.io/v1 3 | kind: Policy 4 | rules: 5 | - level: Metadata 6 | -------------------------------------------------------------------------------- /06-manage-secrets/mysecret-02.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | password: MTIzNDU2Nzg= 4 | kind: Secret 5 | metadata: 6 | creationTimestamp: null 7 | name: mysecret2 8 | -------------------------------------------------------------------------------- /02-ingress/remove_ingress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.40.2/deploy/static/provider/baremetal/deploy.yaml 4 | -------------------------------------------------------------------------------- /15-conftest/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | ARG DEBIAN_FRONTEND=noninteractive 3 | RUN apt-get update && apt-get install -y golang-go 4 | COPY app.go . 5 | RUN go build app.go 6 | CMD ["./app"] 7 | -------------------------------------------------------------------------------- /13-image-footprint/default/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | ARG DEBIAN_FRONTEND=noninteractive 3 | RUN apt-get update && apt-get install -y golang-go 4 | COPY app.go . 5 | RUN CGO_ENABLED=0 go build app.go 6 | CMD ["./app"] 7 | -------------------------------------------------------------------------------- /03-rbac/csr/jane.conf: -------------------------------------------------------------------------------- 1 | [req] 2 | default_bits = 2048 3 | distinguished_name = dn 4 | prompt = no 5 | 6 | [dn] 7 | C="IN" 8 | ST="India" 9 | L="MH" 10 | O="PNQ" 11 | OU="PNQ" 12 | emailAddress="name@mail.com" 13 | CN="jane" 14 | -------------------------------------------------------------------------------- /00-cluster-prep/cleanup_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Clean up images with a search string. 3 | # name/tag provided as initial argument to the script 4 | 5 | KUBE_VERSION=$1 6 | docker rmi $(docker images | grep $KUBE_VERSION | tr -s ' ' | cut -d ' ' -f 3) 7 | -------------------------------------------------------------------------------- /03-rbac/csr/create-csr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | openssl genrsa -out jane.key 2048 4 | openssl req -new -key jane.key -out jane.csr -config jane.conf 5 | CSR_VAL=`cat jane.csr | base64 -w 0` 6 | sed -e 's,CSR_VAL,'$CSR_VAL',g' < template.yaml > jane-csr.yaml 7 | 8 | -------------------------------------------------------------------------------- /02-ingress/setup_ingress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.40.2/deploy/static/provider/baremetal/deploy.yaml 4 | kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission 5 | -------------------------------------------------------------------------------- /00-cluster-prep/01-calico/master/destroy_master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Use this script to destroy master prior to shut down 3 | 4 | kubeadm reset -f 5 | rm -rf ~/.kube /etc/cni/net.d /etc/kubernetes /var/lib/etcd /var/lib/kubelet /var/run/kubernetes /var/lib/cni /opt/cni 6 | iptables -F 7 | -------------------------------------------------------------------------------- /00-cluster-prep/01-calico/worker/destroy_worker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Use this script to destroy worker node prior to shut down 3 | 4 | kubeadm reset -f 5 | rm -rf ~/.kube /etc/cni/net.d /etc/kubernetes /var/lib/etcd /var/lib/kubelet /var/run/kubernetes /var/lib/cni 6 | iptables -F 7 | -------------------------------------------------------------------------------- /00-cluster-prep/00-weave-network/worker/destroy_worker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Use this script to destroy worker node prior to shut down 3 | 4 | kubeadm reset -f 5 | rm -rf ~/.kube /etc/cni/net.d /etc/kubernetes /var/lib/etcd /var/lib/kubelet /var/run/kubernetes /var/lib/cni 6 | iptables -F 7 | -------------------------------------------------------------------------------- /12-opa/whitelist-registries/all_pod_must_have_trusted_images.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: constraints.gatekeeper.sh/v1beta1 2 | kind: K8sTrustedImages 3 | metadata: 4 | name: pod-trusted-images 5 | spec: 6 | match: 7 | kinds: 8 | - apiGroups: [""] 9 | kinds: ["Pod"] 10 | -------------------------------------------------------------------------------- /19-upgrade-scenario/base/1.18.0/worker/destroy_worker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Use this script to destroy worker node prior to shut down 3 | 4 | kubeadm reset -f 5 | rm -rf /etc/cni/net.d /etc/kubernetes /var/lib/etcd /var/lib/kubelet /var/run/kubernetes /var/lib/cni 6 | iptables -F 7 | init 0 8 | -------------------------------------------------------------------------------- /00-cluster-prep/00-weave-network/master/destroy_master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Use this script to destroy master prior to shut down 3 | 4 | kubeadm reset -f 5 | rm -rf /etc/cni/net.d /etc/kubernetes /var/lib/etcd /var/lib/kubelet /var/run/kubernetes /var/lib/cni ~/.kube /opt/cni 6 | iptables -F 7 | -------------------------------------------------------------------------------- /19-upgrade-scenario/base/1.18.0/master/destroy_master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Use this script to destroy master prior to shut down 3 | 4 | kubeadm reset -f 5 | rm -rf /etc/cni/net.d /etc/kubernetes /var/lib/etcd /var/lib/kubelet /var/run/kubernetes /var/lib/cni ~/.kube 6 | iptables -F 7 | init 0 8 | -------------------------------------------------------------------------------- /03-rbac/csr/template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: certificates.k8s.io/v1 2 | kind: CertificateSigningRequest 3 | metadata: 4 | name: jane 5 | spec: 6 | groups: 7 | - system:authenticated 8 | request: CSR_VAL 9 | signerName: kubernetes.io/kube-apiserver-client 10 | usages: 11 | - client auth 12 | -------------------------------------------------------------------------------- /12-opa/deny-all/all_pod_always_deny.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: constraints.gatekeeper.sh/v1beta1 2 | kind: K8sAlwaysDeny 3 | metadata: 4 | name: pod-always-deny 5 | spec: 6 | match: 7 | kinds: 8 | - apiGroups: [""] 9 | kinds: ["Pod"] 10 | parameters: 11 | message: "ACCESS DENIED!" 12 | -------------------------------------------------------------------------------- /12-opa/namespace-labels/all_ns_must_have_cks.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: constraints.gatekeeper.sh/v1beta1 2 | kind: K8sRequiredLabels 3 | metadata: 4 | name: ns-must-have-cks 5 | spec: 6 | match: 7 | kinds: 8 | - apiGroups: [""] 9 | kinds: ["Namespace"] 10 | parameters: 11 | labels: ["cks"] 12 | -------------------------------------------------------------------------------- /12-opa/namespace-labels/all_pod_must_have_cks.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: constraints.gatekeeper.sh/v1beta1 2 | kind: K8sRequiredLabels 3 | metadata: 4 | name: pod-must-have-cks 5 | spec: 6 | match: 7 | kinds: 8 | - apiGroups: [""] 9 | kinds: ["Pod"] 10 | parameters: 11 | labels: ["cks"] 12 | -------------------------------------------------------------------------------- /14-networkpolicies/default-deny.yaml: -------------------------------------------------------------------------------- 1 | # deny all incoming and outgoing traffic from all pods in namespace default 2 | apiVersion: networking.k8s.io/v1 3 | kind: NetworkPolicy 4 | metadata: 5 | name: deny 6 | namespace: default 7 | spec: 8 | podSelector: {} 9 | policyTypes: 10 | - Egress 11 | - Ingress 12 | -------------------------------------------------------------------------------- /15-conftest/docker/policy/base.rego: -------------------------------------------------------------------------------- 1 | # from https://www.conftest.dev 2 | package main 3 | 4 | denylist = [ 5 | "ubuntu" 6 | ] 7 | 8 | deny[msg] { 9 | input[i].Cmd == "from" 10 | val := input[i].Value 11 | contains(val[i], denylist[_]) 12 | 13 | msg = sprintf("unallowed image found %s", [val]) 14 | } 15 | -------------------------------------------------------------------------------- /07-encrypt-etcd/encrypt-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiserver.config.k8s.io/v1 2 | kind: EncryptionConfiguration 3 | resources: 4 | - resources: 5 | - secrets 6 | providers: 7 | - identity: {} 8 | - aescbc: 9 | keys: 10 | - name: key1 11 | secret: cGFzc3dvcmRwYXNzd29yZA== 12 | 13 | -------------------------------------------------------------------------------- /13-image-footprint/multi-stage/Dockerfile: -------------------------------------------------------------------------------- 1 | # build container stage 1 2 | FROM ubuntu 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | RUN apt-get update && apt-get install -y golang-go 5 | COPY app.go . 6 | RUN CGO_ENABLED=0 go build app.go 7 | 8 | # app container stage 2 9 | FROM alpine 10 | COPY --from=0 /app . 11 | CMD ["./app"] 12 | -------------------------------------------------------------------------------- /08-runtime-class/runtimeclass.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: node.k8s.io/v1beta1 # RuntimeClass is defined in the node.k8s.io API group 2 | kind: RuntimeClass 3 | metadata: 4 | name: gvisor # The name the RuntimeClass will be referenced by 5 | # RuntimeClass is a non-namespaced resource 6 | handler: runsc #The name of the corresponding CRI configuration 7 | -------------------------------------------------------------------------------- /13-image-footprint/pkg-versions/Dockerfile: -------------------------------------------------------------------------------- 1 | # build container stage 1 2 | FROM ubuntu 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | RUN apt-get update && apt-get install -y golang-go 5 | COPY app.go . 6 | RUN CGO_ENABLED=0 go build app.go 7 | 8 | # app container stage 2 9 | FROM alpine:3.11.6 10 | COPY --from=0 /app . 11 | CMD ["./app"] 12 | -------------------------------------------------------------------------------- /04-sa/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | run: pod 7 | name: pod 8 | spec: 9 | containers: 10 | - image: nginx 11 | name: pod 12 | resources: {} 13 | automountServiceAccountToken: true 14 | dnsPolicy: ClusterFirst 15 | restartPolicy: Always 16 | status: {} 17 | -------------------------------------------------------------------------------- /12-opa/deployment-replica-count/all_deployment_must_have_min_replicacount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: constraints.gatekeeper.sh/v1beta1 2 | kind: K8sMinReplicaCount 3 | metadata: 4 | name: deployment-must-have-min-replicas 5 | spec: 6 | match: 7 | kinds: 8 | - apiGroups: ["apps"] 9 | kinds: ["Deployment"] 10 | parameters: 11 | min: 2 12 | -------------------------------------------------------------------------------- /08-runtime-class/gvisor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | run: gvisor 7 | name: gvisor 8 | spec: 9 | runtimeClassName: gvisor 10 | containers: 11 | - image: nginx 12 | name: gvisor 13 | resources: {} 14 | dnsPolicy: ClusterFirst 15 | restartPolicy: Always 16 | status: {} 17 | -------------------------------------------------------------------------------- /04-sa/disable-sa-automount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | run: pod 7 | name: pod 8 | spec: 9 | automountServiceAccountToken: false 10 | containers: 11 | - image: nginx 12 | name: pod 13 | resources: {} 14 | dnsPolicy: ClusterFirst 15 | restartPolicy: Always 16 | status: {} 17 | -------------------------------------------------------------------------------- /15-conftest/docker/policy/commands.rego: -------------------------------------------------------------------------------- 1 | # from https://www.conftest.dev 2 | 3 | package commands 4 | 5 | denylist = [ 6 | "apk", 7 | "apt", 8 | "pip", 9 | "curl", 10 | "wget", 11 | ] 12 | 13 | deny[msg] { 14 | input[i].Cmd == "run" 15 | val := input[i].Value 16 | contains(val[_], denylist[_]) 17 | 18 | msg = sprintf("unallowed commands found %s", [val]) 19 | } 20 | -------------------------------------------------------------------------------- /16-imgpolicy-webhook/admission_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiserver.config.k8s.io/v1 2 | kind: AdmissionConfiguration 3 | plugins: 4 | - name: ImagePolicyWebhook 5 | configuration: 6 | imagePolicy: 7 | kubeConfigFile: /etc/kubernetes/admission/kubeconf 8 | allowTTL: 50 9 | denyTTL: 50 10 | retryBackoff: 500 11 | defaultAllow: false 12 | -------------------------------------------------------------------------------- /13-image-footprint/default/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "os/user" 7 | ) 8 | 9 | func main () { 10 | user, err := user.Current() 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | for { 16 | fmt.Println("user: " + user.Username + " id: " + user.Uid) 17 | time.Sleep(1 * time.Second) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /13-image-footprint/multi-stage/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "os/user" 7 | ) 8 | 9 | func main () { 10 | user, err := user.Current() 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | for { 16 | fmt.Println("user: " + user.Username + " id: " + user.Uid) 17 | time.Sleep(1 * time.Second) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /13-image-footprint/no-root/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "os/user" 7 | ) 8 | 9 | func main () { 10 | user, err := user.Current() 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | for { 16 | fmt.Println("user: " + user.Username + " id: " + user.Uid) 17 | time.Sleep(1 * time.Second) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /13-image-footprint/no-shell/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "os/user" 7 | ) 8 | 9 | func main () { 10 | user, err := user.Current() 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | for { 16 | fmt.Println("user: " + user.Username + " id: " + user.Uid) 17 | time.Sleep(1 * time.Second) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /13-image-footprint/pkg-versions/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "os/user" 7 | ) 8 | 9 | func main () { 10 | user, err := user.Current() 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | for { 16 | fmt.Println("user: " + user.Username + " id: " + user.Uid) 17 | time.Sleep(1 * time.Second) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /13-image-footprint/read-only-fs/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "os/user" 7 | ) 8 | 9 | func main () { 10 | user, err := user.Current() 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | for { 16 | fmt.Println("user: " + user.Username + " id: " + user.Uid) 17 | time.Sleep(1 * time.Second) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /19-upgrade-scenario/upgrade/1.19.6/upgrade_master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl drain cks-master --ignore-daemonsets 4 | kubectl get nodes 5 | 6 | apt-get install kubeadm=1.19.6-00 kubelet=1.19.6-00 kubectl=1.19.6-00 7 | kubeadm upgrade plan 8 | kubeadm upgrade apply 1.19.6 9 | kubectl get nodes 10 | kubectl uncordon node cks-master 11 | 12 | systemctl daemon-reload 13 | systemctl restart kubelet 14 | 15 | -------------------------------------------------------------------------------- /19-upgrade-scenario/upgrade/1.19.6/upgrade_worker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl drain cks-worker --ignore-daemonsets 4 | kubectl get nodes 5 | 6 | ssh root@cks-worker <