fix(radius): run FreeRADIUS from a non-world-writable config dir #1556
@@ -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
|
||||
|
||||
@@ -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 -p /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:
|
||||
|
||||
+67
-5
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user