From 1e785e028ead87da40707f59fe8f0877e3098bbd Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Wed, 4 Nov 2026 09:04:23 +0100 Subject: [PATCH 1/2] fix(radius): run FreeRADIUS from a copied, non-world-writable raddb FreeRADIUS refuses a config dir that is globally writable; the kubelet projected volume root is. Mount it at /etc/raddb-src, copy it into an emptyDir with an initContainer and run radiusd from the copy. The test harness now makes the mount world-writable and runs the initContainer from the manifest. Co-Authored-By: Claude Opus 5.5 --- .../apps/authentik/freeradius-config.yaml | 2 +- kubernetes/apps/authentik/freeradius.yaml | 46 ++++++++++-- tests/radius/run.sh | 72 +++++++++++++++++-- 3 files changed, 107 insertions(+), 13 deletions(-) diff --git a/kubernetes/apps/authentik/freeradius-config.yaml b/kubernetes/apps/authentik/freeradius-config.yaml index 97266375..637d7074 100644 --- a/kubernetes/apps/authentik/freeradius-config.yaml +++ b/kubernetes/apps/authentik/freeradius-config.yaml @@ -16,7 +16,7 @@ data: exec_prefix = ${prefix} sbindir = ${exec_prefix}/sbin libdir = ${exec_prefix}/lib - raddbdir = /etc/raddb + raddbdir = /run/raddb/raddb confdir = ${raddbdir} modconfdir = ${confdir}/mods-config certdir = ${confdir}/certs diff --git a/kubernetes/apps/authentik/freeradius.yaml b/kubernetes/apps/authentik/freeradius.yaml index 918c0932..c2075d99 100644 --- a/kubernetes/apps/authentik/freeradius.yaml +++ b/kubernetes/apps/authentik/freeradius.yaml @@ -36,6 +36,34 @@ spec: fsGroup: 65534 seccompProfile: type: RuntimeDefault + # FreeRADIUS refuses a config dir that is world-writable; kubelet's + # volume root is, so the config is copied to a dir the pod owns. + initContainers: + - name: raddb-copy + image: docker.io/freeradius/freeradius-server:3.2.10-alpine # {"$imagepolicy": "flux-system:freeradius"} + command: + - /bin/sh + - -ec + args: + - mkdir /run/raddb/raddb && cp -rL /etc/raddb-src/. /run/raddb/raddb/ && chmod 0750 /run/raddb/raddb + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + volumeMounts: + - name: raddb + mountPath: /etc/raddb-src + readOnly: true + - name: raddb-run + mountPath: /run/raddb containers: - name: freeradius image: docker.io/freeradius/freeradius-server:3.2.10-alpine # {"$imagepolicy": "flux-system:freeradius"} @@ -44,7 +72,7 @@ spec: - -l - stdout - -d - - /etc/raddb + - /run/raddb/raddb env: - name: DOT1X_NAS_SECRET valueFrom: @@ -73,14 +101,14 @@ spec: command: - /opt/bin/radclient - -d - - /etc/raddb + - /run/raddb/raddb - -q - -r - "1" - -t - "2" - -f - - /etc/raddb/probe/status + - /run/raddb/raddb/probe/status - 127.0.0.1:18121 - status - status-probe @@ -91,14 +119,14 @@ spec: command: - /opt/bin/radclient - -d - - /etc/raddb + - /run/raddb/raddb - -q - -r - "1" - -t - "2" - -f - - /etc/raddb/probe/status + - /run/raddb/raddb/probe/status - 127.0.0.1:18121 - status - status-probe @@ -118,8 +146,8 @@ spec: drop: - ALL volumeMounts: - - name: raddb - mountPath: /etc/raddb + - name: raddb-run + mountPath: /run/raddb readOnly: true - name: tmp mountPath: /tmp @@ -158,6 +186,10 @@ spec: path: certs/radius-tls/tls.crt - key: tls.key path: certs/radius-tls/tls.key + - name: raddb-run + emptyDir: + medium: Memory + sizeLimit: 4Mi # pidfile - name: tmp emptyDir: diff --git a/tests/radius/run.sh b/tests/radius/run.sh index 8877fb66..7ef5c55b 100755 --- a/tests/radius/run.sh +++ b/tests/radius/run.sh @@ -33,6 +33,9 @@ cleanup() { mapfile -t ids < <(docker ps -aq --filter name='^radtest-') if ((${#ids[@]} > 0)); then docker rm -f "${ids[@]}" >/dev/null; fi if [[ -n "$(docker network ls -q --filter name="^${net}\$")" ]]; then docker network rm "$net" >/dev/null; fi + local vols=() + mapfile -t vols < <(docker volume ls -q --filter name='^radtest-') + if ((${#vols[@]} > 0)); then docker volume rm "${vols[@]}" >/dev/null; fi rm -rf "$work" } trap cleanup EXIT @@ -67,6 +70,59 @@ render() { openssl x509 -req -in "$work/pki/server.csr" -CA "$work/eapol/ca.pem" -CAkey "$work/pki/ca.key" \ -CAcreateserial -days 1 -extfile "$work/pki/ext" -out "$raddb/$crt" >>"$work/pki/openssl.log" 2>&1 chmod -R a+rX "$work" + # kubelet creates the projected volume root world-writable + chmod 1777 "$raddb" +} + +# Volume mounts of a pod container as docker -v options, as the pod declares +# them: the projected volume is the rendered config, other emptyDirs (except +# the /tmp tmpfs) are named docker volumes. +mount_opts() { # container name; sets mounts[] + local vol name path ro ro_suffix + vol="$(deployment | yq -r '.spec.template.spec.volumes[] | select(.projected) | .name')" + mounts=() + while read -r name path ro; do + ro_suffix="" + if [[ "$ro" == true ]]; then ro_suffix=":ro"; fi + if [[ "$name" == "$vol" ]]; then + mounts+=(-v "$work/raddb:$path${ro_suffix}") + elif [[ "$name" != tmp ]]; then + mounts+=(-v "radtest-$name:$path${ro_suffix}") + fi + done < <(deployment | yq -r --arg n "$1" '(.spec.template.spec.containers + (.spec.template.spec.initContainers // []))[] + | select(.name == $n) | .volumeMounts[] | "\(.name) \(.mountPath) \(.readOnly // false)"') +} + +# emptyDir volumes other than /tmp, writable by the pod user like kubelet's +create_volumes() { + local name + while read -r name; do + docker volume create "radtest-$name" >/dev/null + docker run --rm --user 0:0 -v "radtest-$name:/v" "$mock_image" chmod 0777 /v >/dev/null + done < <(deployment | yq -r '.spec.template.spec.volumes[] | select(.emptyDir) | select(.name != "tmp") | .name') +} + +# Pod restrictions: non-root, read-only root, no capabilities. +pod_opts=(--user 65534:65534 --read-only --tmpfs /tmp:rw,size=16m --cap-drop ALL --security-opt no-new-privileges) + +# Run the Deployment's initContainers exactly as defined. +run_init() { + local names=() n image cmd=() args=() + mapfile -t names < <(deployment | yq -r '.spec.template.spec.initContainers // [] | .[].name') + for n in "${names[@]}"; do + image="$(deployment | yq -r --arg n "$n" '.spec.template.spec.initContainers[] | select(.name == $n) | .image')" + mapfile -t cmd < <(deployment | yq -r --arg n "$n" '.spec.template.spec.initContainers[] | select(.name == $n) | .command[]') + mapfile -t args < <(deployment | yq -r --arg n "$n" '.spec.template.spec.initContainers[] | select(.name == $n) | .args // [] | .[]') + mount_opts "$n" + if docker run --rm --name "radtest-init-$n" --network "$net" "${pod_opts[@]}" "${mounts[@]}" \ + --entrypoint "${cmd[0]}" "$image" "${cmd[@]:1}" "${args[@]}" >"$work/init-$n.log" 2>&1; then + pass "initContainer $n" + else + cat "$work/init-$n.log" + fail "initContainer $n" + exit 1 + fi + done } # --- dummy secrets for every secretKeyRef env var of the Deployment @@ -106,13 +162,19 @@ start() { -v "$here/mock_outpost.py:/mock_outpost.py:ro" \ "$mock_image" python3 -u /mock_outpost.py >/dev/null - # Same restrictions as the pod: non-root, read-only root, no capabilities. - local run_opts=(--network "$net" --user 65534:65534 --read-only --tmpfs /tmp:rw,size=16m - --cap-drop ALL --security-opt no-new-privileges "${secret_env[@]}" - -v "$work/raddb:/etc/raddb:ro") + create_volumes + run_init + + # Same restrictions as the pod. + mount_opts freeradius + local run_opts=(--network "$net" "${pod_opts[@]}" "${secret_env[@]}" "${mounts[@]}") + local confdir="" i + for i in "${!args[@]}"; do + if [[ "${args[$i]}" == -d ]]; then confdir="${args[$((i + 1))]}"; fi + done echo "--- radiusd -XC (config check)" - if docker run --rm "${run_opts[@]}" "$image" radiusd -XC -d /etc/raddb >"$work/configcheck.log" 2>&1 \ + if docker run --rm "${run_opts[@]}" "$image" radiusd -XC -d "$confdir" >"$work/configcheck.log" 2>&1 \ && grep -q "Configuration appears to be OK" "$work/configcheck.log"; then pass "radiusd -XC: Configuration appears to be OK" else -- 2.54.0 From 61fc16ff6783abf5e8dd52de9f7c9a02790f8b6f Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Wed, 4 Nov 2026 09:05:51 +0100 Subject: [PATCH 2/2] fix(radius): idempotent raddb copy in the init container Co-Authored-By: Claude Opus 5.5 --- kubernetes/apps/authentik/freeradius.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/apps/authentik/freeradius.yaml b/kubernetes/apps/authentik/freeradius.yaml index c2075d99..e40411ab 100644 --- a/kubernetes/apps/authentik/freeradius.yaml +++ b/kubernetes/apps/authentik/freeradius.yaml @@ -45,7 +45,7 @@ spec: - /bin/sh - -ec args: - - mkdir /run/raddb/raddb && cp -rL /etc/raddb-src/. /run/raddb/raddb/ && chmod 0750 /run/raddb/raddb + - mkdir -p /run/raddb/raddb && cp -rL /etc/raddb-src/. /run/raddb/raddb/ && chmod 0750 /run/raddb/raddb resources: requests: cpu: 10m -- 2.54.0