"""Scripted solutions: what a candidate would type on the task's host, as a script. `./ksim audit` runs a task's script as `candidate` in an interactive shell on its host (so the host's aliases apply) after grading the task untouched, and grades it again: full marks prove the task can be solved as its text describes, on a cluster of its own. %{param}, {WS} and {CP}.. are substituted as in the task. A task without a script is only audited for a clean seed. The audit grades the instant a script ends, so the scripts wait where a person would simply look again: for a static Pod to come back, for a NetworkPolicy to reach the node. SOLVE: task id -> bash """ # Helpers for the scripts below. H = r""" set +H M=/etc/kubernetes/manifests # medit FILE 'YQ EXPRESSION': edit a static Pod manifest through a copy outside the manifests directory medit() { sudo cp $M/$1 /tmp/$1 && sudo yq -i "$2" /tmp/$1 && sudo cp /tmp/$1 $M/$1; } # upwith PROCESS PATTERN: wait until PROCESS runs with PATTERN on its command line and the API answers upwith() { local i for i in $(seq 1 80); do pgrep -af "^$1 " | grep -q -- "$2" && kubectl get --raw=/readyz >/dev/null 2>&1 && return 0; sleep 3 done echo "$1 did not come back with $2" >&2; return 1 } # restart_api: restart the API server (it reads files such as an audit policy only when it starts) by # moving its manifest out of the directory and back, and wait until the new one answers restart_api() { local old i p; old=$(pgrep -o -x kube-apiserver) sudo mv $M/kube-apiserver.yaml /tmp/ka.yaml for i in $(seq 1 40); do pgrep -x kube-apiserver >/dev/null || break; sleep 2; done sudo mv /tmp/ka.yaml $M/kube-apiserver.yaml for i in $(seq 1 80); do p=$(pgrep -o -x kube-apiserver); [ -n "$p" ] && [ "$p" != "$old" ] && kubectl get --raw=/readyz >/dev/null 2>&1 && return 0; sleep 3 done; return 1 } # setflag FILE NAME VALUE: set --NAME=VALUE in a static Pod manifest, replacing the flag if it is there setflag() { medit "$1" "(.spec.containers[0].command) |= (map(select(test(\"^--$2=\") | not)) + [\"--$2=$3\"])"; } """ SOLVE = { # ---- 1-9: the first tasks of the track (questions.py) 1: r""" cat < {WS}/q2/before.txt setflag kube-apiserver.yaml profiling false upwith kube-apiserver '--profiling=false' echo false > {WS}/q2/after.txt sleep 20 """, 3: r""" kubectl delete clusterrolebinding %{sa}-god kubectl -n %{ns} create role %{sa}-view --verb=get,list,watch --resource=pods,pods/log kubectl -n %{ns} create rolebinding %{sa}-view --role=%{sa}-view --serviceaccount=%{ns}:%{sa} """, 4: r""" kubectl -n %{ns} patch sa default -p '{"automountServiceAccountToken": false}' kubectl -n %{ns} create sa %{dep}-sa kubectl -n %{ns} set serviceaccount deploy %{dep} %{dep}-sa kubectl -n %{ns} rollout status deploy %{dep} --timeout=180s sleep 35 """, 5: r""" cat < {WS}/q8/denied.txt 2>&1 """, 9: H + r""" sudo mkdir -p /var/log/kubernetes cat </dev/null apiVersion: audit.k8s.io/v1 kind: Policy omitStages: - RequestReceived rules: - level: Metadata resources: - group: "" resources: ["secrets"] - level: Metadata EOF medit kube-apiserver.yaml '.spec.containers[0].command += ["--audit-policy-file=/etc/kubernetes/audit-policy.yaml", "--audit-log-path=/var/log/kubernetes/audit.log", "--audit-log-maxage=7", "--audit-log-maxbackup=5"] | .spec.containers[0].volumeMounts += [{"name": "audit-policy", "mountPath": "/etc/kubernetes/audit-policy.yaml", "readOnly": true}, {"name": "audit-log", "mountPath": "/var/log/kubernetes"}] | .spec.volumes += [{"name": "audit-policy", "hostPath": {"path": "/etc/kubernetes/audit-policy.yaml", "type": "File"}}, {"name": "audit-log", "hostPath": {"path": "/var/log/kubernetes", "type": "DirectoryOrCreate"}}]' upwith kube-apiserver '--audit-policy-file=' kubectl get secrets -A >/dev/null sleep 3 sudo grep secrets /var/log/kubernetes/audit.log | tail -1 > {WS}/q9/audit-line.json """, # ---- 10-24: Cluster Setup (bank_setup.py) 10: r""" cat < {WS}/q13/after.txt """, 14: H + r""" medit etcd.yaml '(.spec.containers[0].command[] | select(. == "--client-cert-auth=false")) = "--client-cert-auth=true" | (.spec.containers[0].command[] | select(. == "--peer-client-cert-auth=false")) = "--peer-client-cert-auth=true"' sudo useradd -r -s /usr/sbin/nologin etcd sudo chmod 700 /var/lib/etcd && sudo chown -R etcd:etcd /var/lib/etcd upwith etcd '--peer-client-cert-auth=true' """, 15: H + r""" sudo kube-bench run --targets master | grep -E '^\[FAIL\] 1\.[34]\.' | awk '{print $2}' > {WS}/q15/failed.txt for c in kube-controller-manager kube-scheduler; do setflag $c.yaml bind-address 127.0.0.1; setflag $c.yaml profiling false; done upwith kube-controller-manager '--profiling=false'; upwith kube-scheduler '--profiling=false' sleep 20 """, 16: r""" kubectl -n %{ns} create secret tls shop-tls --cert={WS}/q16/tls.crt --key={WS}/q16/tls.key kubectl -n %{ns} create ingress shop --class=nginx --rule="shop.%{dom}.test/*=shop:80,tls=shop-tls" """, 17: r""" cd {WS}/q17 openssl req -x509 -newkey rsa:2048 -nodes -days 365 -keyout tls.key -out tls.crt -subj "/CN=portal.%{dom}.test" -addext "subjectAltName=DNS:portal.%{dom}.test" 2>/dev/null kubectl -n %{ns} create secret tls portal-tls --cert=tls.crt --key=tls.key --dry-run=client -o yaml | kubectl apply -f - kubectl -n %{ns} patch ingress portal --type=json -p '[{"op": "replace", "path": "/spec/tls/0/hosts", "value": ["portal.%{dom}.test"]}]' """, 18: r""" cd {WS}/q18 for c in cert-1.pem cert-2.pem; do host=$(openssl x509 -in $c -noout -subject | sed 's/.*CN *= *//') for k in key-1.pem key-2.pem; do if [ "$(openssl x509 -in $c -noout -pubkey | sha256sum)" = "$(openssl pkey -in $k -pubout | sha256sum)" ]; then kubectl -n %{ns} create secret tls ${host%%.*}-tls --cert=$c --key=$k fi done done kubectl -n %{ns} annotate ingress edge nginx.ingress.kubernetes.io/force-ssl-redirect=true kubectl -n %{ns} patch ingress edge --type=merge -p '{"spec": {"tls": [{"hosts": ["api.%{dom}.test"], "secretName": "api-tls"}, {"hosts": ["www.%{dom}.test"], "secretName": "www-tls"}]}}' """, 19: r""" kubectl -n %{ns} exec web -- wget -qO- http://169.254.169.254/latest/meta-data/iam/security-credentials/node-role > {WS}/q19/leak.txt cat < {WS}/q20/status.txt """, 21: H + r""" kubectl -n kube-system get cm kube-proxy -o yaml | sed 's/metricsBindAddress: 0.0.0.0:10249/metricsBindAddress: 127.0.0.1:10249/' | kubectl apply -f - kubectl -n kube-system rollout restart ds kube-proxy kubectl -n kube-system rollout status ds kube-proxy --timeout=180s setflag etcd.yaml listen-metrics-urls http://127.0.0.1:2381 upwith etcd '--listen-metrics-urls=http://127.0.0.1:2381' sleep 5 """, 22: r""" cd {WS}/q22/release sha512sum -c ../SHA512SUMS 2>/dev/null | grep FAILED | cut -d: -f1 > ../tampered.txt xargs rm -f < ../tampered.txt """, 23: r""" cd {WS}/q23 ssh {W1} sudo sha512sum -c - < reference.sha512 2>/dev/null | grep FAILED | cut -d: -f1 > tampered.txt for f in $(cat tampered.txt); do scp -q $f {W1}:/tmp/genuine && ssh -n {W1} "sudo install -o root -g root -m 755 /tmp/genuine $f && rm -f /tmp/genuine" done """, 24: r""" sudo sha512sum /proc/$(pgrep -o -x kube-apiserver)/exe | cut -d' ' -f1 > {WS}/q24/apiserver.sha512 if diff -q {WS}/q24/apiserver.sha512 {WS}/q24/reference.sha512 >/dev/null; then echo match; else echo mismatch; fi > {WS}/q24/verdict.txt """, # ---- 25-39: Cluster Hardening (bank_harden.py) 25: r""" cat < {WS}/q28/can-read.txt kubectl delete clusterrolebinding %{ns}-secret-audit """, 29: r""" kubectl -n %{ns} create sa vault-client cat < {WS}/q30/legacy.txt kubectl -n %{ns} delete secret $(cat {WS}/q30/legacy.txt) kubectl -n %{ns} create token ci --duration=30m > {WS}/q30/ci.token """, 31: r""" kubectl -n %{ns} create sa reporter cat < {WS}/q32/before.json printf 'monitoring-anon\nlegacy-discovery\nguest-access\n' > {WS}/q32/bindings.txt kubectl delete clusterrolebinding monitoring-anon legacy-discovery kubectl -n %{ns} delete rolebinding guest-access """, 33: H + r""" sudo mkdir -p /etc/kubernetes/auth cat </dev/null apiVersion: apiserver.config.k8s.io/v1 kind: AuthenticationConfiguration anonymous: enabled: true conditions: - path: /livez - path: /readyz - path: /healthz EOF medit kube-apiserver.yaml '.spec.containers[0].command += ["--authentication-config=/etc/kubernetes/auth/authn.yaml"] | .spec.containers[0].volumeMounts += [{"name": "authn", "mountPath": "/etc/kubernetes/auth", "readOnly": true}] | .spec.volumes += [{"name": "authn", "hostPath": {"path": "/etc/kubernetes/auth", "type": "DirectoryOrCreate"}}]' upwith kube-apiserver '--authentication-config=' curl -sk -o /dev/null -w '%{http_code}\n' https://localhost:6443/version > {WS}/q33/version.txt """, 34: H + r""" medit kube-apiserver.yaml '(.spec.containers[0].command[] | select(. == "--authorization-mode=AlwaysAllow")) = "--authorization-mode=Node,RBAC" | .spec.containers[0].command += ["--enable-admission-plugins=NodeRestriction"]' upwith kube-apiserver '--authorization-mode=Node,RBAC' ssh -n {W1} sudo kubectl --kubeconfig /etc/kubernetes/kubelet.conf label node {CP} audit=failed > {WS}/q34/denied.txt 2>&1 """, 35: H + r""" medit kube-apiserver.yaml '.spec.containers[0].command |= map(select(test("^--kubernetes-service-node-port=") | not))' for i in $(seq 1 80); do pgrep -af '^kube-apiserver ' | grep -q . && ! pgrep -af '^kube-apiserver ' | grep -q -- '--kubernetes-service-node-port' && kubectl get --raw=/readyz >/dev/null 2>&1 && break; sleep 3 done kubectl delete svc kubernetes sleep 10 """, 36: r""" cd {WS}/q36 openssl genrsa -out %{user}.key 2048 2>/dev/null openssl req -new -key %{user}.key -subj "/CN=%{user}/O=auditors" -out %{user}.csr cat < %{user}.crt kubectl -n %{ns} create rolebinding auditors-view --clusterrole=view --group=auditors """, 37: r""" V=$(kubectl get node {CP} -o jsonpath='{.status.nodeInfo.kubeletVersion}' | tr -d v) kubectl drain {W1} --ignore-daemonsets --delete-emptydir-data --force ssh -n {W1} "sudo sed -i 's|/v[0-9.]*/deb/|/v${V%.*}/deb/|' /etc/apt/sources.list.d/kubernetes.list; sudo apt-get update -qq; sudo apt-mark unhold kubeadm kubelet kubectl >/dev/null; sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq kubeadm=$V-1.1 >/dev/null && sudo kubeadm upgrade node && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq kubelet=$V-1.1 kubectl=$V-1.1 >/dev/null; sudo apt-mark hold kubeadm kubelet kubectl; sudo systemctl daemon-reload; sudo systemctl restart kubelet" kubectl uncordon {W1} kubectl -n %{ns} rollout status deploy web --timeout=180s sleep 15 """, 38: r""" sudo apt-get update -qq V=$(apt-cache madison kubeadm | head -1 | awk '{print $3}') sudo apt-mark unhold kubeadm kubelet kubectl >/dev/null sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq kubeadm=$V >/dev/null sudo kubeadm upgrade plan | tail -5 sudo kubeadm upgrade apply -y v${V%%-*} | tail -3 for i in $(seq 1 60); do kubectl get --raw=/readyz >/dev/null 2>&1 && break; sleep 3; done kubectl drain {CP} --ignore-daemonsets --delete-emptydir-data --force sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq kubelet=$V kubectl=$V >/dev/null sudo apt-mark hold kubeadm kubelet kubectl sudo systemctl daemon-reload; sudo systemctl restart kubelet for i in $(seq 1 60); do kubectl get --raw=/readyz >/dev/null 2>&1 && break; sleep 3; done kubectl uncordon {CP} sleep 40 """, 39: r""" T=$(kubectl get node {CP} -o jsonpath='{.status.nodeInfo.kubeletVersion}') kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name} {.status.nodeInfo.kubeletVersion}{"\n"}{end}' | grep -v " $T$" | cut -d' ' -f1 > {WS}/q39/outdated.txt kubectl cordon $(cat {WS}/q39/outdated.txt) sudo apt-get update -qq apt-cache madison kubeadm | head -1 | awk '{print $3}' > {WS}/q39/target.txt sudo kubeadm upgrade plan > {WS}/q39/plan.txt 2>&1 """, # ---- 40-55: System Hardening (bank_sys.py) 40: r""" PID=$(sudo ss -tlnpH | grep ':%{port} ' | sed 's/.*pid=\([0-9]*\).*/\1/') sudo readlink /proc/$PID/exe > {WS}/q40/binary.txt UNIT=$(ps -o unit= -p $PID | tr -d ' ') echo $UNIT > {WS}/q40/unit.txt sudo systemctl disable --now $UNIT sudo rm -f /etc/systemd/system/$UNIT $(cat {WS}/q40/binary.txt) sudo systemctl daemon-reload """, 41: r""" PID=$(sudo ss -tlnpH | grep ':21 ' | sed 's/.*pid=\([0-9]*\).*/\1/') dpkg -S "$(sudo readlink /proc/$PID/exe)" | cut -d: -f1 > {WS}/q41/package.txt sudo systemctl disable --now vsftpd sudo DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq vsftpd >/dev/null """, 42: r""" sudo find /usr/local /opt -xdev -type f -perm -4000 > {WS}/q42/setuid.txt sudo chmod u-s $(cat {WS}/q42/setuid.txt) sudo userdel -r tmpadmin 2>/dev/null """, 43: r""" printf 'blacklist sctp\nblacklist dccp\ninstall sctp /bin/false\ninstall dccp /bin/false\n' | sudo tee /etc/modprobe.d/disable-protocols.conf >/dev/null sudo modprobe -n -v sctp > {WS}/q43/sctp.txt 2>&1 """, 44: r""" ssh -n {W1} "sudo ss -tlnpH | grep ':1338 '" | sed 's/.*(("\([^"]*\)".*/\1/' > {WS}/q44/unit.txt ssh -n {W1} "sudo sed -i '/^\[metrics\]/,+1d' /etc/containerd/config.toml && sudo systemctl restart containerd" sleep 10 """, 45: r""" echo 'deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart kubelet, /usr/local/bin/crictl ps' | sudo tee /etc/sudoers.d/deploy >/dev/null sudo visudo -c >/dev/null sudo gpasswd -d intern sudo """, 46: r""" ssh -n {W1} "printf 'PermitRootLogin no\nPasswordAuthentication no\nAllowUsers candidate\n' | sudo tee /etc/ssh/sshd_config.d/00-legacy.conf >/dev/null && sudo sshd -t && sudo systemctl reload ssh" sleep 2 """, 47: r""" sudo grep -rIl 'client-key-data' /home /tmp /var/tmp /opt /srv 2>/dev/null | grep -v '^/home/candidate/' > {WS}/q47/copies.txt sudo rm -f $(cat {WS}/q47/copies.txt) sudo chmod 600 /etc/kubernetes/admin.conf /etc/kubernetes/pki/ca.key sudo usermod -L -s /usr/sbin/nologin svc-backup """, 48: r""" printf 'admin-ui\nmetrics\ncache\n' > {WS}/q48/exposed.txt kubectl -n %{ns} patch svc admin-ui -p '{"spec":{"type":"ClusterIP"}}' kubectl -n %{ns} patch svc metrics -p '{"spec":{"type":"ClusterIP"}}' kubectl -n %{ns} patch svc cache --type=json -p '[{"op":"remove","path":"/spec/externalIPs"}]' sleep 5 """, 49: r""" CPIP=$(kubectl get node {CP} -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}') ssh -n {W1} "sudo iptables -A INPUT -i lo -p tcp --dport 9400 -j ACCEPT && sudo iptables -A INPUT -p tcp --dport 9400 -s $CPIP -j ACCEPT && sudo iptables -A INPUT -p tcp --dport 9400 -j DROP" """, 50: r""" cat < {WS}/q51/exposed.txt kubectl -n %{ns} patch deploy gateway --type=json -p '[{"op":"remove","path":"/spec/template/spec/containers/0/ports/0/hostPort"}]' kubectl -n %{ns} patch deploy debug-agent --type=json -p '[{"op":"remove","path":"/spec/template/spec/hostNetwork"}]' kubectl -n %{ns} rollout status deploy gateway --timeout=180s kubectl -n %{ns} rollout status deploy debug-agent --timeout=180s sleep 5 """, 52: r""" scp -q {WS}/q52/deny-chmod.json {W1}:/tmp/ ssh -n {W1} 'sudo mkdir -p /var/lib/kubelet/seccomp/profiles && sudo cp /tmp/deny-chmod.json /var/lib/kubelet/seccomp/profiles/' kubectl -n %{ns} patch deploy %{dep} -p '{"spec":{"template":{"spec":{"securityContext":{"seccompProfile":{"type":"Localhost","localhostProfile":"profiles/deny-chmod.json"}}}}}}' kubectl -n %{ns} rollout status deploy %{dep} --timeout=180s sleep 3 kubectl -n %{ns} exec deploy/%{dep} -- chmod 600 /etc/hostname > {WS}/q52/chmod.txt 2>&1 """, 53: r""" sudo cat /var/lib/kubelet/seccomp/profiles/no-mkdir.json | ssh {W1} 'sudo mkdir -p /var/lib/kubelet/seccomp/profiles && sudo tee /var/lib/kubelet/seccomp/profiles/no-mkdir.json >/dev/null' kubectl -n %{ns} get pod %{pod} -o yaml | sed 's#localhostProfile: no-mkdir.json#localhostProfile: profiles/no-mkdir.json#' > /tmp/pod.yaml kubectl replace --force -f /tmp/pod.yaml kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=120s """, 54: r""" cat > /tmp/no-links.json < {WS}/q55/seccomp.txt """, # ---- 56-70: Minimize Microservice Vulnerabilities (bank_micro.py) 56: r""" printf 'agent\ncollector\ntools\n' > {WS}/q56/violators.txt kubectl label ns %{ns} pod-security.kubernetes.io/enforce=baseline pod-security.kubernetes.io/enforce-version=latest pod-security.kubernetes.io/warn=restricted pod-security.kubernetes.io/audit=restricted kubectl -n %{ns} delete pod agent collector tools --grace-period=1 """, 57: H + r""" sudo mkdir -p /etc/kubernetes/psa cat </dev/null apiVersion: apiserver.config.k8s.io/v1 kind: AdmissionConfiguration plugins: - name: PodSecurity configuration: apiVersion: pod-security.admission.config.k8s.io/v1 kind: PodSecurityConfiguration defaults: enforce: baseline enforce-version: latest audit: restricted audit-version: latest warn: restricted warn-version: latest exemptions: usernames: [] runtimeClasses: [] namespaces: [kube-system] EOF medit kube-apiserver.yaml '.spec.containers[0].command += ["--admission-control-config-file=/etc/kubernetes/psa/admission.yaml"] | .spec.containers[0].volumeMounts += [{"name": "psa", "mountPath": "/etc/kubernetes/psa", "readOnly": true}] | .spec.volumes += [{"name": "psa", "hostPath": {"path": "/etc/kubernetes/psa", "type": "DirectoryOrCreate"}}]' upwith kube-apiserver '--admission-control-config-file=' """, 58: r""" kubectl -n %{ns} patch deploy %{dep} --type=json -p '[ {"op": "add", "path": "/spec/template/spec/securityContext", "value": {"runAsNonRoot": true, "runAsUser": 1000, "seccompProfile": {"type": "RuntimeDefault"}}}, {"op": "replace", "path": "/spec/template/spec/volumes", "value": [{"name": "data", "emptyDir": {}}]}, {"op": "replace", "path": "/spec/template/spec/containers/0/securityContext", "value": {"allowPrivilegeEscalation": false, "capabilities": {"drop": ["ALL"]}}}]' kubectl -n %{ns} rollout status deploy %{dep} --timeout=180s sleep 8 """, 59: r""" for s in a b c; do n=%{ns}-$s; level=privileged for l in baseline restricted; do kubectl label --dry-run=server --overwrite ns $n pod-security.kubernetes.io/enforce=$l 2>&1 | grep -qi warning || level=$l [ $level = $l ] || break done kubectl label ns $n pod-security.kubernetes.io/enforce=$level >/dev/null echo "$n $level" done > {WS}/q59/levels.txt """, 60: H + r""" sudo mkdir -p /etc/kubernetes/enc cat </dev/null apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: [secrets] providers: - aescbc: keys: - name: key1 secret: $(head -c 32 /dev/urandom | base64) - identity: {} EOF medit kube-apiserver.yaml '.spec.containers[0].command += ["--encryption-provider-config=/etc/kubernetes/enc/enc.yaml"] | .spec.containers[0].volumeMounts += [{"name": "enc", "mountPath": "/etc/kubernetes/enc", "readOnly": true}] | .spec.volumes += [{"name": "enc", "hostPath": {"path": "/etc/kubernetes/enc", "type": "DirectoryOrCreate"}}]' upwith kube-apiserver '--encryption-provider-config=' kubectl -n %{ns} get secrets -o json | kubectl replace -f - sudo ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key get /registry/secrets/%{ns}/db-credentials | hexdump -C > {WS}/q60/etcd.txt """, 61: r""" E="sudo ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key" $E get /registry/secrets/%{ns} --prefix --keys-only | grep '/%{name}$' > {WS}/q61/key.txt $E get "$(cat {WS}/q61/key.txt)" --print-value-only | strings | grep -o 'tok-[A-Z0-9]*' | head -1 > {WS}/q61/token.txt """, 62: r""" kubectl -n %{ns} patch deploy %{dep} --type=json -p '[ {"op": "replace", "path": "/spec/template/spec/containers/0/env", "value": [{"name": "SYNC_INTERVAL", "value": "30"}]}, {"op": "add", "path": "/spec/template/spec/volumes", "value": [{"name": "creds", "secret": {"secretName": "api-creds", "defaultMode": 256}}]}, {"op": "add", "path": "/spec/template/spec/containers/0/volumeMounts", "value": [{"name": "creds", "mountPath": "/etc/creds", "readOnly": true}]}]' kubectl -n %{ns} rollout status deploy %{dep} --timeout=180s sleep 35 """, 63: r""" kubectl -n %{ns} create secret generic app-secrets --from-literal=DB_PASSWORD="$(kubectl -n %{ns} get cm app-config -o jsonpath='{.data.DB_PASSWORD}')" --from-literal=API_TOKEN="$(kubectl -n %{ns} get cm app-config -o jsonpath='{.data.API_TOKEN}')" kubectl -n %{ns} patch cm app-config --type=json -p '[{"op": "remove", "path": "/data/DB_PASSWORD"}, {"op": "remove", "path": "/data/API_TOKEN"}]' kubectl -n %{ns} patch deploy %{dep} --type=json -p '[{"op": "add", "path": "/spec/template/spec/containers/0/envFrom/-", "value": {"secretRef": {"name": "app-secrets"}}}]' kubectl -n %{ns} rollout status deploy %{dep} --timeout=180s sleep 35 """, 64: H + r""" F=/etc/kubernetes/enc/enc.yaml K2=$(head -c 32 /dev/urandom | base64) sudo yq -i "(.resources[0].providers[] | select(has(\"aescbc\")) | .aescbc.keys) |= [{\"name\": \"key2\", \"secret\": \"$K2\"}] + ." $F restart_api kubectl get secrets -A -o json | kubectl replace -f - >/dev/null sudo yq -i '(.resources[0].providers[] | select(has("aescbc")) | .aescbc.keys) |= map(select(.name != "key1"))' $F restart_api sudo ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key get /registry/secrets/%{ns}/vault-pass | strings > {WS}/q64/etcd.txt """, 65: r""" cat < {WS}/q65/dmesg.txt """, 66: r""" kubectl -n tenant-%{t} create quota tenant-quota --hard=pods=5,requests.cpu=1,requests.memory=1Gi,limits.cpu=2,limits.memory=2Gi cat < {WS}/q67/breakers.txt kubectl -n %{ns} patch deploy log-shipper --type=json -p '[ {"op": "replace", "path": "/spec/template/spec/volumes/0/hostPath/path", "value": "/var/log"}, {"op": "add", "path": "/spec/template/spec/containers/0/volumeMounts/0/readOnly", "value": true}]' kubectl -n %{ns} scale deploy metrics-agent ci-runner net-tool --replicas=0 kubectl -n %{ns} rollout status deploy log-shipper --timeout=180s """, 68: r""" kubectl label ns %{ns} istio-injection=enabled kubectl -n %{ns} rollout restart deploy kubectl -n %{ns} rollout status deploy orders --timeout=240s kubectl -n %{ns} rollout status deploy payments --timeout=240s cat < Dockerfile podman build -q -t localhost/%{img}:hardened . >/dev/null """, 72: r""" cd {WS}/q72/src printf 'FROM golang:1.23-alpine AS build\nWORKDIR /src\nCOPY main.go .\nRUN CGO_ENABLED=0 go build -o /render-worker main.go\n\nFROM scratch\nCOPY --from=build /render-worker /render-worker\nUSER 65532\nENTRYPOINT ["/render-worker"]\n' > Dockerfile podman build -q -t localhost/%{img}:slim . >/dev/null """, 73: r""" cd {WS}/q73 sed -i 's/^FROM nginx$/FROM nginx:1.27/; /^ENV DB_PASSWORD/d; /^USER root$/d' Dockerfile yq -i '.spec.template.spec.containers[0].securityContext = {"runAsNonRoot": true, "runAsUser": 101} | (.spec.template.spec.containers[0].env[] | select(.name == "DB_PASSWORD")) = {"name": "DB_PASSWORD", "valueFrom": {"secretKeyRef": {"name": "storefront-db", "key": "password"}}}' deployment.yaml """, 74: r""" cd {WS}/q74 trivy image -q --format spdx-json --output nginx.spdx.json nginx:1.27-alpine trivy image -q --format cyclonedx --output redis.cdx.json redis:7.2-alpine jq -r '.packages[] | select(.name == "busybox") | .versionInfo' nginx.spdx.json > busybox.txt """, 75: r""" cd {WS}/q75 for d in $(kubectl -n %{ns} get deploy -o name | cut -d/ -f2); do i=$(kubectl -n %{ns} get deploy $d -o jsonpath='{.spec.template.spec.containers[0].image}') if trivy image -q --format cyclonedx $i 2>/dev/null | jq -e '.components[] | select(.name == "libssl1.1" and .version == "1.1.1d-0+deb10u3")' >/dev/null; then echo $d > affected.txt trivy image -q --format cyclonedx --output sbom.json $i kubectl -n %{ns} scale deploy $d --replicas=0 fi done """, 76: r""" cd {WS}/q76 trivy sbom -q --severity CRITICAL --format json --output report.json app.cdx.json jq '[.Results[].Vulnerabilities[]?] | length' report.json > critical-count.txt jq -r '.Results[].Vulnerabilities[]? | select(.PkgName == "libssl1.1") | .VulnerabilityID' report.json | sort -u > libssl.txt """, 77: r""" cd {WS}/q77 for d in frontend sessions docs; do echo "$d $(kubectl -n %{ns} get pod -l app=$d -o jsonpath='{.items[0].status.containerStatuses[0].imageID}' | sed 's/.*@//')" done > running.txt while read -r d dg; do if grep -qx "$dg" approved-digests.txt; then repo=$(kubectl -n %{ns} get deploy $d -o jsonpath='{.spec.template.spec.containers[0].image}' < /dev/null | cut -d: -f1) kubectl -n %{ns} set image deploy/$d "*=$repo@$dg" < /dev/null else kubectl -n %{ns} scale deploy $d --replicas=0 < /dev/null fi done < running.txt for d in frontend sessions docs; do kubectl -n %{ns} rollout status deploy $d --timeout=180s; done sleep 5 """, 78: r""" cd {WS}/q78 bom generate --image-archive app.tar --output image.spdx 2>/dev/null bom generate --dirs src --output src.spdx 2>/dev/null bom document outline image.spdx > outline.txt 2>/dev/null """, 79: H + r""" sudo sed -i 's/defaultAllow: true/defaultAllow: false/' /etc/kubernetes/policywebhook/admission_config.yaml sudo sed -i 's#https://CHANGE-ME/image_policy#https://localhost:1323/image_policy#' /etc/kubernetes/policywebhook/kubeconf medit kube-apiserver.yaml '.spec.containers[0].command |= map(select(test("^--enable-admission-plugins=") | not)) | .spec.containers[0].command += ["--enable-admission-plugins=NodeRestriction,ImagePolicyWebhook", "--admission-control-config-file=/etc/kubernetes/policywebhook/admission_config.yaml"] | .spec.containers[0].volumeMounts += [{"name": "policywebhook", "mountPath": "/etc/kubernetes/policywebhook", "readOnly": true}] | .spec.volumes += [{"name": "policywebhook", "hostPath": {"path": "/etc/kubernetes/policywebhook", "type": "DirectoryOrCreate"}}]' upwith kube-apiserver 'ImagePolicyWebhook' kubectl run test --image=nginx:latest > {WS}/q79/denied.txt 2>&1 """, 80: r""" cat <<'EOF' | kubectl apply -f - apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicy metadata: {name: no-latest} spec: failurePolicy: Fail matchConstraints: resourceRules: - {apiGroups: [""], apiVersions: ["v1"], operations: [CREATE, UPDATE], resources: [pods]} validations: - expression: >- (object.spec.containers + (has(object.spec.initContainers) ? object.spec.initContainers : [])).all(c, c.image.contains('@sha256:') || (c.image.matches('^.*:[^/:]+$') && !c.image.endsWith(':latest'))) message: every image needs a tag other than latest, or a digest --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicyBinding metadata: {name: no-latest} spec: policyName: no-latest validationActions: [Deny] matchResources: namespaceSelector: matchLabels: {images: pinned} EOF sleep 20 """, 81: r""" kubectl -n %{ns} create secret docker-registry regcred --docker-server=registry.%{dom}.internal:5000 --docker-username=puller --docker-password="$(cat {WS}/q81/password.txt)" kubectl -n %{ns} patch sa default -p '{"imagePullSecrets": [{"name": "regcred"}]}' kubectl -n %{ns} delete pod -l app=app --wait=false sleep 8 """, 82: r""" cd {WS}/q82/release : > ../rejected.txt for f in payments.yaml catalog.yaml search.yaml; do if cosign verify-blob --key cosign.pub --bundle $f.bundle --insecure-ignore-tlog=true $f >/dev/null 2>&1; then kubectl apply -f $f else echo $f >> ../rejected.txt fi done """, 83: r""" cd {WS}/q83 cat > pod.yaml < report.json kubectl apply -f pod.yaml kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=120s """, 84: r""" cd {WS}/q84 kube-linter lint manifests/deployment.yaml > before.txt 2>&1 yq -i '.spec.template.spec.containers[0].image = "busybox:1.36" | .spec.template.spec.containers[0].securityContext = {"privileged": false, "allowPrivilegeEscalation": false, "readOnlyRootFilesystem": true, "runAsNonRoot": true, "runAsUser": 10001} | .spec.template.spec.containers[0].resources = {"requests": {"cpu": "50m", "memory": "32Mi"}, "limits": {"cpu": "100m", "memory": "64Mi"}}' manifests/deployment.yaml kubectl apply -f manifests/deployment.yaml kubectl -n %{ns} rollout status deploy %{dep} --timeout=180s """, 85: r""" cd {WS}/q85 : > critical.txt for d in $(kubectl -n %{ns} get deploy -o name | cut -d/ -f2); do i=$(kubectl -n %{ns} get deploy $d -o jsonpath='{.spec.template.spec.containers[0].image}') n=$(trivy image -q --severity CRITICAL --format json $i 2>/dev/null | jq '[.Results[].Vulnerabilities[]?] | length') if [ "$n" -gt 0 ]; then echo $d >> critical.txt; kubectl -n %{ns} scale deploy $d --replicas=0; fi done """, 86: r""" cd {WS}/q86 trivy config -q --severity HIGH,CRITICAL --format json deployment.yaml 2>/dev/null | jq '[.Results[].Misconfigurations[]?] | length' > count.txt yq -i 'del(.spec.template.spec.hostNetwork) | del(.spec.template.spec.hostPID) | .spec.template.spec.securityContext = {"runAsNonRoot": true, "runAsUser": 10001, "seccompProfile": {"type": "RuntimeDefault"}} | .spec.template.spec.containers[0].securityContext = {"privileged": false, "readOnlyRootFilesystem": true, "allowPrivilegeEscalation": false, "runAsNonRoot": true, "runAsUser": 10001, "capabilities": {"drop": ["ALL"]}}' deployment.yaml kubectl apply -f deployment.yaml kubectl -n %{ns} rollout status deploy %{dep} --timeout=180s """, # ---- 87-102: Monitoring, Logging and Runtime Security (bank_runtime.py) 87: r""" PID=$(ssh -n {W1} "pgrep -x %{proc}" | head -1) echo $PID > {WS}/q87/pid.txt CID=$(ssh -n {W1} "cat /proc/$PID/cgroup" | sed 's/.*cri-containerd-\([0-9a-f]*\)\.scope.*/\1/') ssh -n {W1} "sudo crictl inspect $CID" | jq -r '.status.labels["io.kubernetes.pod.namespace"] + "/" + .status.labels["io.kubernetes.pod.name"]' > {WS}/q87/pod.txt POD=$(cut -d/ -f2 {WS}/q87/pod.txt) kubectl -n %{ns} scale deploy "$(kubectl -n %{ns} get pod $POD -o jsonpath='{.metadata.labels.app}')" --replicas=0 """, 88: r""" for d in api billing search; do f=$(kubectl -n %{ns} exec deploy/$d -- sh -c 'find / -xdev -type f -perm -4000 2>/dev/null' | head -1) if [ -n "$f" ]; then echo $d > {WS}/q88/deployment.txt; echo $f > {WS}/q88/added.txt kubectl -n %{ns} patch deploy $d --type=json -p '[{"op": "add", "path": "/spec/template/spec/containers/0/securityContext", "value": {"readOnlyRootFilesystem": true}}]' kubectl -n %{ns} rollout status deploy $d --timeout=180s fi done sleep 35 """, 89: r""" IP=$(ssh -n {W1} "sudo conntrack -L -d 198.51.100.77 2>/dev/null" | sed -n 's/.* src=\([0-9.]*\) dst=198\.51\.100\.77 .*/\1/p' | head -1) kubectl get pod -A -o jsonpath="{range .items[?(@.status.podIP=='$IP')]}{.metadata.namespace}/{.metadata.name}{end}" > {WS}/q89/pod.txt NS=$(cut -d/ -f1 {WS}/q89/pod.txt); POD=$(cut -d/ -f2 {WS}/q89/pod.txt) APP=$(kubectl -n $NS get pod $POD -o jsonpath='{.metadata.labels.app}') cat < {WS}/q90/source.txt ssh -n {W1} "sudo rm -f $(cat {WS}/q90/source.txt)" echo staticPodPath > {WS}/q90/setting.txt sleep 20 """, 91: r""" cd {WS}/q91 jq -r 'select(.objectRef.resource == "secrets" and .objectRef.name == "%{secret}" and .responseStatus.code == 200) | .user.username' audit.log | sort -u > actor.txt A=$(cat actor.txt) jq -r --arg a "$A" 'select(.user.username == $a and .objectRef.name == "%{secret}") | .sourceIPs[0]' audit.log | sort -u > ip.txt jq -r --arg a "$A" 'select(.user.username == $a and .objectRef.resource == "clusterrolebindings" and .responseStatus.code == 201) | .objectRef.name' audit.log > binding.txt kubectl delete clusterrolebinding "$(cat binding.txt)" """, 92: r""" kubectl get deploy,ds,sts,cronjob,job,cm -A -o json | jq -r '.items[] | select(tostring | test("198\\.51\\.100\\.66([^0-9]|$)")) | (.kind | ascii_downcase) + "/" + .metadata.namespace + "/" + .metadata.name' > {WS}/q92/found.txt while IFS=/ read -r k n name; do kubectl -n $n delete $k $name < /dev/null; done < {WS}/q92/found.txt """, 93: r""" F=/var/log/kubernetes/audit/audit.log sudo jq -c 'select(.objectRef.subresource == "exec" and .objectRef.name == "%{pod}")' $F | tail -1 > {WS}/q93/event.json jq -r .user.username {WS}/q93/event.json > {WS}/q93/user.txt kubectl -n %{ns} patch role %{sa}-debug --type=json -p '[{"op": "remove", "path": "/rules/2"}]' """, 94: r""" kubectl -n %{ns} auth can-i get secrets --as=system:serviceaccount:%{ns}:ci-bot > {WS}/q94/secrets.txt kubectl -n %{ns} delete sa ci-bot kubectl -n %{ns} create sa ci-bot sleep 6 """, 95: r""" kubectl -n %{ns} patch deploy %{dep} --type=json -p '[ {"op": "add", "path": "/spec/template/spec/containers/0/securityContext", "value": {"readOnlyRootFilesystem": true, "allowPrivilegeEscalation": false}}, {"op": "add", "path": "/spec/template/spec/volumes", "value": [{"name": "tmp", "emptyDir": {}}, {"name": "log", "emptyDir": {}}]}, {"op": "add", "path": "/spec/template/spec/containers/0/volumeMounts", "value": [{"name": "tmp", "mountPath": "/tmp"}, {"name": "log", "mountPath": "/var/log"}]}]' kubectl -n %{ns} rollout status deploy %{dep} --timeout=180s sleep 35 """, 96: r""" kubectl -n %{ns} get pod -o json | jq -r '.items[] | select(any(.spec.containers[]; .securityContext.readOnlyRootFilesystem != true) or any(.spec.containers[]; .securityContext.privileged == true) or any(.spec.volumes[]?; .hostPath)) | .metadata.name' > {WS}/q96/mutable.txt kubectl -n %{ns} delete pod $(cat {WS}/q96/mutable.txt) --wait=false sleep 3 """, 97: r""" cat <<'EOF' | kubectl apply -f - apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicy metadata: {name: immutable-pods} spec: failurePolicy: Fail matchConstraints: resourceRules: - {apiGroups: [""], apiVersions: ["v1"], operations: [CREATE, UPDATE], resources: [pods]} validations: - expression: >- (object.spec.containers + (has(object.spec.initContainers) ? object.spec.initContainers : [])).all(c, has(c.securityContext) && has(c.securityContext.readOnlyRootFilesystem) && c.securityContext.readOnlyRootFilesystem && !(has(c.securityContext.privileged) && c.securityContext.privileged)) message: every container needs readOnlyRootFilesystem true and must not be privileged --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicyBinding metadata: {name: immutable-pods} spec: policyName: immutable-pods validationActions: [Deny] matchResources: namespaceSelector: matchLabels: {immutable: "true"} EOF sleep 20 """, 98: r""" kubectl -n %{ns} patch cm app-settings -p '{"immutable": true}' kubectl -n %{ns} patch secret app-keys -p '{"immutable": true}' kubectl -n %{ns} patch role developer --type=json -p '[{"op": "remove", "path": "/rules/2"}]' kubectl -n %{ns} patch cm app-settings -p '{"data": {"mode": "test"}}' > {WS}/q98/immutable.txt 2>&1 """, 99: H + r""" cat </dev/null apiVersion: audit.k8s.io/v1 kind: Policy omitStages: - RequestReceived rules: - level: Metadata resources: - group: "" resources: [secrets] - level: None verbs: [get, list, watch] - level: RequestResponse resources: - group: "" resources: [configmaps] namespaces: [%{ns}] - level: Metadata EOF restart_api """, 100: r""" F=/var/log/kubernetes/audit/audit.log sudo jq -r 'select(.objectRef.resource == "secrets" and .objectRef.name == "payroll") | .user.username' $F | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' > {WS}/q100/user.txt SA=$(cut -d: -f4 {WS}/q100/user.txt) kubectl -n %{ns} get pod -o jsonpath="{.items[?(@.spec.serviceAccountName=='$SA')].metadata.name}" > {WS}/q100/pod.txt kubectl -n %{ns} delete rolebinding $SA-secrets sleep 12 """, 101: H + r""" sudo mkdir -p /etc/kubernetes/audit /var/log/kubernetes/audit cat </dev/null apiVersion: audit.k8s.io/v1 kind: Policy rules: - level: Metadata resources: - group: "" resources: ["pods/exec", "pods/attach"] - level: RequestResponse verbs: [delete, deletecollection] namespaces: [%{ns}] - level: None EOF medit kube-apiserver.yaml '.spec.containers[0].command += ["--audit-policy-file=/etc/kubernetes/audit/policy.yaml", "--audit-log-path=/var/log/kubernetes/audit/audit.log", "--audit-log-maxage=14", "--audit-log-maxbackup=5", "--audit-log-maxsize=10"] | .spec.containers[0].volumeMounts += [{"name": "audit-policy", "mountPath": "/etc/kubernetes/audit", "readOnly": true}, {"name": "audit-log", "mountPath": "/var/log/kubernetes/audit"}] | .spec.volumes += [{"name": "audit-policy", "hostPath": {"path": "/etc/kubernetes/audit", "type": "DirectoryOrCreate"}}, {"name": "audit-log", "hostPath": {"path": "/var/log/kubernetes/audit", "type": "DirectoryOrCreate"}}]' upwith kube-apiserver '--audit-policy-file=' """, 102: H + r""" sudo sed -i 's/level: Metdata/level: Metadata/' /etc/kubernetes/audit/policy.yaml sudo mkdir -p /var/log/kubernetes/audit medit kube-apiserver.yaml '.spec.containers[0].volumeMounts += [{"name": "audit-policy", "mountPath": "/etc/kubernetes/audit", "readOnly": true}, {"name": "audit-log", "mountPath": "/var/log/kubernetes/audit"}] | .spec.volumes += [{"name": "audit-policy", "hostPath": {"path": "/etc/kubernetes/audit", "type": "DirectoryOrCreate"}}, {"name": "audit-log", "hostPath": {"path": "/var/log/kubernetes/audit", "type": "DirectoryOrCreate"}}]' upwith kube-apiserver '--audit-policy-file=' """, # ---- 103-110: Falco and AppArmor, on a VM host (bank_vm.py) 103: r""" L=$(sudo journalctl -u falco-modern-bpf --no-pager -o cat --since -5min | grep 'Sensitive file opened' | grep 'k8s_ns_name=%{ns}' | tail -1) echo "$L" > {WS}/q103/alert.txt POD=$(echo "$L" | sed 's/.*k8s_pod_name=\([^ ]*\).*/\1/') echo "%{ns}/$POD" > {WS}/q103/pod.txt sudo grep -B60 'Sensitive file opened for reading by non-trusted program' /etc/falco/falco_rules.yaml | grep -- '- rule:' | tail -1 | sed 's/.*- rule: //' > {WS}/q103/rule.txt kubectl -n %{ns} scale deploy "$(kubectl -n %{ns} get pod $POD -o jsonpath='{.metadata.labels.app}')" --replicas=0 """, 104: r""" cat <<'EOF' | sudo tee -a /etc/falco/falco_rules.local.yaml >/dev/null - rule: Shell in container desc: A shell was started inside a container condition: spawned_process and container and proc.name in (sh, bash) output: "Shell in container: user=%user.name container=%container.name pod=%k8s.pod.name cmdline=%proc.cmdline" priority: WARNING EOF sudo systemctl restart falco-modern-bpf sleep 12 kubectl -n %{ns} exec %{pod} -- sh -c 'echo hello' sleep 5 sudo journalctl -u falco-modern-bpf --no-pager -o cat --since -1min | grep 'Shell in container' | tail -1 > {WS}/q104/alert.txt """, 105: r""" cat <<'EOF' | sudo tee -a /etc/falco/falco_rules.local.yaml >/dev/null - rule: Read sensitive file untrusted output: "%evt.time,%container.id,%container.name,%k8s.ns.name,%k8s.pod.name,%user.name,%fd.name" override: output: replace EOF sudo systemctl restart falco-modern-bpf sleep 25 sudo journalctl -u falco-modern-bpf --no-pager -o cat --since -20s | grep ',%{pod},' > {WS}/q105/alerts.txt """, 106: r""" sudo mkdir -p /var/log/falco printf 'json_output: true\nfile_output:\n enabled: true\n keep_alive: false\n filename: /var/log/falco/events.json\n' | sudo tee /etc/falco/config.d/90-json-file.yaml >/dev/null sudo systemctl restart falco-modern-bpf sleep 20 """, 107: r""" cat <<'EOF' | sudo tee -a /etc/falco/falco_rules.local.yaml >/dev/null - list: network_tools items: [nc, ncat, wget, curl, nslookup] - rule: Network tool in restricted namespace desc: A network tool was started in a container of a Namespace that should not need one condition: spawned_process and container and proc.name in (network_tools) and k8s.ns.name = "%{ns}" output: "Network tool: ns=%k8s.ns.name pod=%k8s.pod.name tool=%proc.name cmdline=%proc.cmdline" priority: ERROR EOF sudo systemctl restart falco-modern-bpf sleep 15 """, 108: r""" sudo apparmor_parser -q {WS}/q108/k8s-deny-write kubectl -n %{ns} patch deploy %{dep} -p '{"spec":{"template":{"spec":{"containers":[{"name":"busybox","securityContext":{"appArmorProfile":{"type":"Localhost","localhostProfile":"k8s-deny-write"}}}]}}}}' kubectl -n %{ns} rollout status deploy %{dep} --timeout=180s sleep 35 kubectl -n %{ns} exec deploy/%{dep} -- touch /tmp/test > {WS}/q108/denied.txt 2>&1 kubectl -n %{ns} exec deploy/%{dep} -- cat /proc/1/attr/current > {WS}/q108/current.txt """, 109: r""" sudo apparmor_parser -q /etc/apparmor.d/k8s-audit-agent kubectl -n %{ns} get pod %{pod} -o yaml | sed 's/localhostProfile: k8s-audit-agent/localhostProfile: audit-agent-restricted/' > /tmp/pod.yaml kubectl replace --force -f /tmp/pod.yaml kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=120s """, 110: r""" cat <<'EOF' | sudo tee /etc/apparmor.d/%{name} >/dev/null #include profile %{name} flags=(attach_disconnected,mediate_deleted) { #include file, network, capability, deny /etc/shadow r, deny /usr/** w, } EOF sudo apparmor_parser -q /etc/apparmor.d/%{name} cat <