"""Building blocks shared by the CKS task banks: bash for setups and checks. A setup or a check is bash that runs on this machine against the task's own cluster (see the README: $K, $CP, $W1, nx, $WS, $PRIV). Prepend what it needs: "setup": API + PODS + r\"\"\"...\"\"\" Two rules every task here follows: - It scores nothing before anyone has worked on it. A check that something is refused, blocked or gone only counts together with proof that the task's own work is in place - a connection that fails, or an object that is absent, proves nothing by itself. - What can be tested for real is tested for real: requests through NetworkPolicies, an unauthenticated call to a kubelet, a Pod the admission chain must reject, the bytes in etcd. The helpers every track uses - NET (traffic probes), PODS (busybox Pods to test with) and PY (an object as JSON for a Python expression) - live in sim/bank.py and are re-exported here. """ from sim.bank import C, NET, NS, PODS, PY, ns_pick, name_pick # noqa: F401 (the banks import these from here) # ---- the control plane's static Pods and the node's services ------------------------------- API = r""" MAN=/etc/kubernetes/manifests api_ok() { $K get --raw=/readyz >/dev/null 2>&1; } # wait_api [SECONDS]: until the API server answers (a static Pod takes 20-60 s to come back) wait_api() { local i; for i in $(seq 1 $(( ${1:-180} / 3 ))); do api_ok && return 0; sleep 3; done; return 1; } # medit FILE 'YQ EXPRESSION': change a static Pod manifest on the control plane through a copy # outside the manifests directory - an editor's temp file in there is run as a second static Pod. # The expression may contain double quotes, no single ones. medit() { nx "$CP" "cp $MAN/$1 /root/.ksim-$1 && yq -i '$2' /root/.ksim-$1 && cp /root/.ksim-$1 $MAN/$1 && rm -f /root/.ksim-$1" /dev/null" /dev/null < /www/$3 && exec httpd -f -p $2:80 -h /www"] --- apiVersion: v1 kind: Pod metadata: {name: $1-route, namespace: kube-system, labels: {ksim: external}} spec: hostNetwork: true nodeName: $W1 tolerations: [{operator: Exists}] containers: - name: route image: busybox:1.36 securityContext: {capabilities: {add: [NET_ADMIN]}} command: ["sh", "-c", "ip route replace $2/32 via $cpip; exec sleep 2147483647"] EOF $K -n kube-system wait --for=condition=Ready pod/$1 pod/$1-route --timeout=180s >/dev/null } """ # ---- certificates -------------------------------------------------------------------------- TLS = r""" # selfsigned HOST KEYFILE CERTFILE [DAYS]: a self-signed certificate for HOST (CN and subjectAltName) selfsigned() { openssl req -x509 -newkey rsa:2048 -nodes -days "${4:-180}" -keyout "$2" -out "$3" \ -subj "/CN=$1" -addext "subjectAltName=DNS:$1" >/dev/null 2>&1 } # crt_of NS SECRET / key_of NS SECRET: the PEM certificate / private key held by a TLS Secret crt_of() { j -n "$1" secret "$2" '{.data.tls\.crt}' | base64 -d 2>/dev/null; } key_of() { j -n "$1" secret "$2" '{.data.tls\.key}' | base64 -d 2>/dev/null; } fp() { openssl x509 -noout -fingerprint -sha256 2>/dev/null; } # of a certificate on stdin san_has() { openssl x509 -noout -ext subjectAltName 2>/dev/null | grep -q "DNS:$1\(,\|$\)"; } # certificate on stdin names HOST # pair CERT_PEM KEY_PEM: the private key belongs to the certificate pair() { [ -n "$1" ] && [ -n "$2" ] && [ "$(echo "$1" | openssl x509 -noout -pubkey 2>/dev/null | sha256sum)" = "$(echo "$2" | openssl pkey -pubout 2>/dev/null | sha256sum)" ]; } # tls_secret NS SECRET HOST: a kubernetes.io/tls Secret whose certificate names HOST and whose key fits it tls_secret() { [ "$(j -n "$1" secret "$2" '{.type}')" = kubernetes.io/tls ] || return 1 local c k; c=$(crt_of "$1" "$2"); k=$(key_of "$1" "$2") echo "$c" | san_has "$3" && pair "$c" "$k" } """