mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-23 03:27:50 +09:00
fix(backups): run the kafka-metadata demo CLI Pod as restricted
The kafka-metadata walkthrough started its long-lived CLI Pod with a bare kubectl run, so the Pod carried no securityContext and a namespace enforcing the restricted Pod Security level would reject it. The kafka topic-data walkthrough already layers the restricted context onto its CLI Pod; the metadata demo now applies the same JSON patch. A running CLI Pod left by an earlier run without that context is replaced rather than reused, since a Pod's securityContext cannot change in place. The new bats cases read the overrides kubectl run receives and fail unless both the Pod-level and the container-level fields are present and the patch is sent as --override-type=json, and check that an owned Pod without the restricted context is recreated. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
This commit is contained in:
2 files changed
+61
-12
No files matched your search
@@ -150,23 +150,35 @@ kafka_wait_ready() {
|
||||
# Ensure the long-lived kafka-cli Pod exists and is Ready, so kafka_run can exec
|
||||
# into it. Idempotent: a Ready Pod this demo owns is reused across calls and
|
||||
# across the numbered demo scripts; a leftover in a terminal phase (Succeeded /
|
||||
# Failed) is replaced rather than waited on; and a same-named Pod this demo does
|
||||
# not own is refused rather than hijacked or deleted. cleanup.sh removes it.
|
||||
# Failed) is replaced rather than waited on, and so is one started before the
|
||||
# Pod ran under the restricted profile, since a Pod's securityContext cannot be
|
||||
# changed in place; and a same-named Pod this demo does not own is refused
|
||||
# rather than hijacked or deleted. cleanup.sh removes it.
|
||||
# Every step returns on failure explicitly: this runs as the left side of
|
||||
# `|| return 1`, and some callers also wrap it in $(...), so errexit never
|
||||
# applies inside it and a bare failure would carry on to the next step.
|
||||
kafka_cli_pod() {
|
||||
local phase owner
|
||||
local phase owner nonroot overrides
|
||||
phase=$(kubectl -n "$NAMESPACE" get pod "$KAFKA_CLI_POD" --ignore-not-found -o jsonpath='{.status.phase}') || return 1
|
||||
owner=$(kubectl -n "$NAMESPACE" get pod "$KAFKA_CLI_POD" --ignore-not-found -o jsonpath='{.metadata.labels.cozystack\.io/backup-demo}') || return 1
|
||||
if [ -n "$phase" ] && [ "$owner" != "kafka-metadata" ]; then
|
||||
log_error "Pod $NAMESPACE/$KAFKA_CLI_POD exists but this demo does not own it; refusing to use or delete it"
|
||||
return 1
|
||||
fi
|
||||
if [ "$phase" != "Running" ] && [ "$phase" != "Pending" ]; then
|
||||
# Only this helper creates the Pod, and it sets every restricted field at
|
||||
# once, so one of them tells a current Pod from an older one.
|
||||
nonroot=$(kubectl -n "$NAMESPACE" get pod "$KAFKA_CLI_POD" --ignore-not-found -o jsonpath='{.spec.securityContext.runAsNonRoot}') || return 1
|
||||
if { [ "$phase" != "Running" ] && [ "$phase" != "Pending" ]; } || [ "$nonroot" != "true" ]; then
|
||||
# The Strimzi image runs as non-root UID 1001, so runAsNonRoot and
|
||||
# seccomp at the Pod level plus allowPrivilegeEscalation=false and
|
||||
# drop-ALL on the container satisfy PSA "restricted" without pinning a
|
||||
# runAsUser specific to this image tag. The add-only JSON patch leaves
|
||||
# the run-generated Pod's image, command and args untouched.
|
||||
overrides='[{"op":"add","path":"/spec/securityContext","value":{"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}},{"op":"add","path":"/spec/containers/0/securityContext","value":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]}}}]'
|
||||
kubectl -n "$NAMESPACE" delete pod "$KAFKA_CLI_POD" --grace-period=1 --ignore-not-found >/dev/null || return 1
|
||||
kubectl -n "$NAMESPACE" run "$KAFKA_CLI_POD" --image="$KAFKA_IMAGE" \
|
||||
--labels=cozystack.io/backup-demo=kafka-metadata \
|
||||
--override-type=json --overrides="$overrides" \
|
||||
--restart=Never --command -- sleep infinity >/dev/null || return 1
|
||||
fi
|
||||
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$KAFKA_CLI_POD" \
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
# helper the kafka-3-metadata-roundtrip suite seeds and verifies topics through.
|
||||
#
|
||||
# kubectl and the Kafka CLI are stub executables on PATH. The kubectl stub
|
||||
# answers the calls kafka_run makes -- get the CLI Pod's phase and owner label,
|
||||
# create it when absent, wait for it, and `exec` into it -- and runs the exec'd
|
||||
# snippet locally against the kafka-topics.sh stub. STUB_PHASE / STUB_OWNER drive
|
||||
# the reuse guard, and STUB_EXEC_ERROR makes the exec fail so the "a failed read
|
||||
# says why" claim is pinned rather than asserted in prose.
|
||||
# answers the calls kafka_run makes -- get the CLI Pod's phase, owner label and
|
||||
# runAsNonRoot, create it when absent or started without the restricted profile
|
||||
# (recording the overrides it is given), wait for it, and `exec` into it -- and
|
||||
# runs the exec'd snippet locally against the kafka-topics.sh stub. STUB_PHASE /
|
||||
# STUB_OWNER / STUB_NONROOT drive the reuse guard, and STUB_EXEC_ERROR makes the
|
||||
# exec fail so the "a failed read says why" claim is pinned rather than asserted
|
||||
# in prose.
|
||||
#
|
||||
# cozytest.sh's awk runner recognizes only @test blocks and a bare `}`; there is
|
||||
# no bats `run` or `$status`, and no setup/teardown, so each case calls
|
||||
@@ -26,13 +28,21 @@ case "$1 $2 $3" in
|
||||
case "$*" in
|
||||
*"jsonpath={.status.phase}"*) printf '%s' "${STUB_PHASE-Running}" ;;
|
||||
*"jsonpath={.metadata.labels"*) printf '%s' "${STUB_OWNER-kafka-metadata}" ;;
|
||||
*"jsonpath={.spec.securityContext.runAsNonRoot}"*) printf '%s' "${STUB_NONROOT-true}" ;;
|
||||
esac
|
||||
exit 0 ;;
|
||||
"-n tenant-root delete")
|
||||
exit 0 ;;
|
||||
"-n tenant-root run")
|
||||
# Reachable only when the Pod is absent; a reuse that starts a Pod is a bug.
|
||||
[ -z "${STUB_PHASE-Running}" ] && exit 0
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--overrides=*) printf '%s' "${a#--overrides=}" > "$STUB_DIR/overrides.json" ;;
|
||||
--override-type=*) printf '%s' "${a#--override-type=}" > "$STUB_DIR/override-type" ;;
|
||||
esac
|
||||
done
|
||||
# Reachable only when the Pod is absent or predates the restricted
|
||||
# profile; a reuse that starts a Pod is a bug.
|
||||
{ [ -z "${STUB_PHASE-Running}" ] || [ -z "${STUB_NONROOT-true}" ]; } && exit 0
|
||||
echo "kafka_run started a Pod though a Ready one it owns exists" >&2; exit 95 ;;
|
||||
"-n tenant-root wait")
|
||||
exit 0 ;;
|
||||
@@ -59,7 +69,7 @@ EOF
|
||||
}
|
||||
|
||||
run_topic_meta() {
|
||||
STUB_DIR=$stub STUB_PHASE="${STUB_PHASE-Running}" STUB_OWNER="${STUB_OWNER-kafka-metadata}" \
|
||||
STUB_DIR=$stub STUB_PHASE="${STUB_PHASE-Running}" STUB_OWNER="${STUB_OWNER-kafka-metadata}" STUB_NONROOT="${STUB_NONROOT-true}" \
|
||||
STUB_EXEC_ERROR="${STUB_EXEC_ERROR:-}" STUB_GET_ERROR="${STUB_GET_ERROR:-}" PATH="$stub:$PATH" NAMESPACE=tenant-root \
|
||||
KAFKA_BIN=$stub TOPIC=orders bash -c '
|
||||
set -euo pipefail
|
||||
@@ -88,6 +98,33 @@ run_topic_meta() {
|
||||
grep -q 'exec -i kafka-cli -- bash -c' "$stub/kubectl.argv"
|
||||
}
|
||||
|
||||
@test "kafka_run starts the CLI Pod under the restricted Pod Security profile" {
|
||||
make_stubs
|
||||
export STUB_PHASE=
|
||||
run_topic_meta >/dev/null
|
||||
# The overrides are a JSON patch, which kubectl applies only with
|
||||
# --override-type=json; under the default merge type the array is rejected.
|
||||
[ "$(cat "$stub/override-type")" = "json" ]
|
||||
jq --exit-status '
|
||||
(map(select(.op == "add" and .path == "/spec/securityContext")) | .[0].value) as $pod
|
||||
| (map(select(.op == "add" and .path == "/spec/containers/0/securityContext")) | .[0].value) as $ctr
|
||||
| $pod.runAsNonRoot == true
|
||||
and $pod.seccompProfile.type == "RuntimeDefault"
|
||||
and $ctr.allowPrivilegeEscalation == false
|
||||
and $ctr.capabilities.drop == ["ALL"]
|
||||
' "$stub/overrides.json"
|
||||
}
|
||||
|
||||
@test "kafka_run replaces a Pod it owns that predates the restricted profile" {
|
||||
make_stubs
|
||||
export STUB_NONROOT=
|
||||
out=$(run_topic_meta)
|
||||
[ "$out" = "3 123456" ]
|
||||
grep -q 'delete pod kafka-cli' "$stub/kubectl.argv"
|
||||
grep -q 'run kafka-cli --image=' "$stub/kubectl.argv"
|
||||
[ "$(cat "$stub/override-type")" = "json" ]
|
||||
}
|
||||
|
||||
@test "kafka_run refuses a same-named Pod the demo does not own" {
|
||||
make_stubs
|
||||
export STUB_OWNER=someone-else
|
||||
|
||||
Reference in new issue
Block a user