feat(radius): in-cluster 802.1X RADIUS via authentik (test SSID) #1554

Merged
k.renner merged 16 commits from dot1x-radius-authentik into main 2026-11-04 07:39:20 +00:00
Member

In-cluster 802.1X RADIUS backed by authentik, for the test SSID "Renner Test". The UDM RADIUS, "Unifi Radius" provider, "Local Radius" outpost and the existing WLANs are unchanged.

Path: client → EAP-TTLS (Let's Encrypt cert radius.renner.uno) → FreeRADIUS (radius-lb, 10.2.99.91 / fd99::91) → inner PAP proxied to the new authentik RADIUS outpost "802.1X Radius" (ak-outpost-8021x-radius) → VLAN from the group attribute dot1x_vlan (lowest wins; no group → Access-Reject).

Changes

  • authentik (blueprints-configmap.yaml, helmrelease.yaml): blueprint dot1x-radius.yaml with flow, dot1x-has-vlan policy, VLAN property mapping (Tunnel-Type/Medium-Type/Private-Group-Id plus Cisco-AV-Pair dot1x-vlan=<id>), provider, application dot1x and a 2-replica outpost. DOT1X_OUTPOST_SECRET is set on the worker only (optional: true), so SSO does not depend on it.
  • Secrets/cert: ExternalSecret radius-secrets from OpenBao apps/radius; Certificate radius-tls for radius.renner.uno (ClusterIssuer letsencrypt-cluster).
  • FreeRADIUS (freeradius-config.yaml, freeradius.yaml, freeradius-cnp.yaml):
    • 2 anti-affine replicas, non-root, read-only root filesystem.
    • EAP-TTLS only; inner PAP proxied to the outpost.
    • VLAN: Tunnel-* passed through, or Cisco-AVPair translated. An Accept without a VLAN is rejected in inner-tunnel post-auth.
    • Accounting is answered locally and never forwarded.
    • Outpost liveness: status_check = request with the non-existent user dot1x-status-probe (always rejected; sent only while the home server is zombie/dead), response_window = 5.
    • proxy_dedup_window = 10 (> response_window): a NAS retransmit is never forwarded to the outpost. In FreeRADIUS 3.2.10, a TTLS inner request sent twice to the home server stays in the proxy hash while eapttls_postproxy() rewrites request->proxy->dst_port (part of the hash key), so the freed request is never removed and the next proxy reply lookup segfaults radiusd.
    • Listens on IPv4 and IPv6. NAS clients are 10.2.10.0/24 and fd02:10::/64. Test clients are 10.2.50.0/24 and fd02:50::/64; these are test phase only.
  • Image automation for freeradius-server (3.2.x-alpine); alerts RadiusDeploymentDown / RadiusDeploymentDegraded for FreeRADIUS and the outpost.
  • Tests tests/radius/run.sh: runs the production ConfigMap and Deployment args in Docker against a mock outpost, with eapol_test over IPv4 and IPv6. It checks:
    • VLAN via Tunnel-* and via AV-pair.
    • No VLAN → Reject.
    • Wrong password → Reject in under 1 s.
    • A Reject without Message-Authenticator still reaches the NAS.
    • Accounting is answered locally.
    • The probe command works.
    • Recovery: the outpost is stalled until marked dead, then resumed; a login succeeds again within 20 s and radiusd stays up.
  • Docs docs/radius/dot1x.md: client setup (Android, Linux, Windows 11 with server-certificate validation, Apple profile docs/radius/clients/renner-test.mobileconfig), Loki latency queries, the VLAN contract.

Rollout order

The worker restarts (Reloader) and applies the blueprint, which creates the outpost Deployment and Service. FreeRADIUS resolves ak-outpost-8021x-radius at startup and crash-loops until that Service exists; RadiusDeploymentDown can fire once during the first rollout.

UniFi objects (firewall policy for APs → 10.2.99.91, RADIUS profile, WLAN "Renner Test") are created via the API after merge.

🤖 Generated with Claude Code

In-cluster 802.1X RADIUS backed by authentik, for the test SSID "Renner Test". The UDM RADIUS, "Unifi Radius" provider, "Local Radius" outpost and the existing WLANs are unchanged. Path: client → EAP-TTLS (Let's Encrypt cert `radius.renner.uno`) → FreeRADIUS (`radius-lb`, 10.2.99.91 / fd99::91) → inner PAP proxied to the new authentik RADIUS outpost "802.1X Radius" (`ak-outpost-8021x-radius`) → VLAN from the group attribute `dot1x_vlan` (lowest wins; no group → Access-Reject). ## Changes - **authentik** (`blueprints-configmap.yaml`, `helmrelease.yaml`): blueprint `dot1x-radius.yaml` with flow, `dot1x-has-vlan` policy, VLAN property mapping (Tunnel-Type/Medium-Type/Private-Group-Id plus `Cisco-AV-Pair dot1x-vlan=<id>`), provider, application `dot1x` and a 2-replica outpost. `DOT1X_OUTPOST_SECRET` is set on the worker only (`optional: true`), so SSO does not depend on it. - **Secrets/cert**: ExternalSecret `radius-secrets` from OpenBao `apps/radius`; Certificate `radius-tls` for `radius.renner.uno` (ClusterIssuer `letsencrypt-cluster`). - **FreeRADIUS** (`freeradius-config.yaml`, `freeradius.yaml`, `freeradius-cnp.yaml`): - 2 anti-affine replicas, non-root, read-only root filesystem. - EAP-TTLS only; inner PAP proxied to the outpost. - VLAN: Tunnel-* passed through, or `Cisco-AVPair` translated. An Accept without a VLAN is rejected in inner-tunnel post-auth. - Accounting is answered locally and never forwarded. - Outpost liveness: `status_check = request` with the non-existent user `dot1x-status-probe` (always rejected; sent only while the home server is zombie/dead), `response_window = 5`. - `proxy_dedup_window = 10` (> `response_window`): a NAS retransmit is never forwarded to the outpost. In FreeRADIUS 3.2.10, a TTLS inner request sent twice to the home server stays in the proxy hash while `eapttls_postproxy()` rewrites `request->proxy->dst_port` (part of the hash key), so the freed request is never removed and the next proxy reply lookup segfaults radiusd. - Listens on IPv4 and IPv6. NAS clients are 10.2.10.0/24 and fd02:10::/64. Test clients are 10.2.50.0/24 and fd02:50::/64; these are test phase only. - **Image automation** for `freeradius-server` (`3.2.x-alpine`); **alerts** `RadiusDeploymentDown` / `RadiusDeploymentDegraded` for FreeRADIUS and the outpost. - **Tests** `tests/radius/run.sh`: runs the production ConfigMap and Deployment args in Docker against a mock outpost, with `eapol_test` over IPv4 and IPv6. It checks: - VLAN via Tunnel-* and via AV-pair. - No VLAN → Reject. - Wrong password → Reject in under 1 s. - A Reject without Message-Authenticator still reaches the NAS. - Accounting is answered locally. - The probe command works. - Recovery: the outpost is stalled until marked dead, then resumed; a login succeeds again within 20 s and radiusd stays up. - **Docs** `docs/radius/dot1x.md`: client setup (Android, Linux, Windows 11 with server-certificate validation, Apple profile `docs/radius/clients/renner-test.mobileconfig`), Loki latency queries, the VLAN contract. ## Rollout order The worker restarts (Reloader) and applies the blueprint, which creates the outpost Deployment and Service. FreeRADIUS resolves `ak-outpost-8021x-radius` at startup and crash-loops until that Service exists; `RadiusDeploymentDown` can fire once during the first rollout. UniFi objects (firewall policy for APs → 10.2.99.91, RADIUS profile, WLAN "Renner Test") are created via the API after merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 14 commits 2026-11-04 06:17:11 +00:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blueprint dot1x-radius.yaml: non-interactive flow dot1x-authentication-flow,
policy dot1x-has-vlan, RADIUS mapping dot1x-vlan (lowest dot1x_vlan group
attribute; RFC 3580 Tunnel-* by raw code plus Cisco-AV-Pair fallback),
provider and outpost "802.1X Radius" (2 replicas, ClusterIP), client
networks = cluster pod CIDRs. Shared secret from radius-secrets via env
DOT1X_OUTPOST_SECRET.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Server pods (SSO) no longer depend on radius-secrets. Worker reloads on
radius-secrets changes. Docs: worker-only secret, unquoted integer
dot1x_vlan, FreeRADIUS VLAN contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeRADIUS 3.2 (2 replicas, non-root, read-only root) terminates EAP-TTLS
for radius.renner.uno and proxies inner PAP to ak-outpost-8021x-radius.
Inner post-auth passes standard Tunnel-* through, translates
Cisco-AV-Pair dot1x-vlan=<id> into Tunnel-*, strips Cisco-AV-Pair and
rejects an Accept without VLAN; doing this in the inner tunnel makes
TTLS answer with EAP-Failure. Home server does not require
Message-Authenticator (outpost rejects lack it). Accounting is answered
locally.

Adds radius-lb (10.2.99.91/fd99::91), CNPs, image automation, replica
alerts and tests/radius/run.sh (Docker + eapol_test against a mock
outpost).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeRADIUS's dictionary names the Cisco VSA Cisco-AVPair; Cisco-AV-Pair
fails the config load. The image puts /opt/bin on PATH only in its
entrypoint, so exec probes need the absolute path. The AV-pair regex
now checks for the attribute first to avoid a condition-evaluation
failure message on Accepts without it.

tests/radius/run.sh now also runs the Deployment's readiness probe
command inside the test container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeRADIUS listens on :: for 1812/1813 and accepts aps6 fd02:10::/64 and
test6 fd02:50::/64 with the same secrets as their IPv4 entries; the NAS
CNP admits both prefixes. The harness adds IPv6 cases on an isolated
dual-stack bridge.

Windows 11 setup requires server validation for radius.renner.uno and
never Windows sign-in credentials. The Apple profile uses EncryptionType
Any and OuterIdentity anonymous. dot1x.md marks the test/test6 clients
and their CNP CIDRs as test-phase only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(radius): anonymous outer identity on Android
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
3ed987dd74
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
k.renner requested review from review-bot 2026-11-04 06:23:15 +00:00
review-bot approved these changes 2026-11-04 06:29:37 +00:00
Dismissed
review-bot left a comment
Member

🤖 Automated review — model claude-opus-5-5, commit 3ed987dd74ebc8394f00108bf7dd7821dc028837.

Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Request a review from review-bot again for a fresh one.


Summary

This PR adds an in-cluster 802.1X path for the "Renner Test" SSID. FreeRADIUS ends the EAP-TTLS tunnel and sends the inner PAP request to a new authentik RADIUS outpost, which is managed by a blueprint. The PR also adds a certificate, an ExternalSecret, network policies, image automation, alerts, a Docker-based functional test and docs. The design is careful and well tested, and I found no defect I could verify. The two open risks are in authentik and the Helm chart, which I couldn't check from the repo. mergeable is true. There is no linked issue and no earlier review from me, so this is a first review.

⚪ Unverified

  • The worker env list may replace the global one (kubernetes/apps/authentik/helmrelease.yaml:183-189).
    • The new worker.env list adds DOT1X_OUTPOST_SECRET. If chart 2026.8.3 replaces global.env with worker.env instead of joining them, the worker loses AUTHENTIK_SECRET_KEY, the Postgres password and the other variables.
    • I couldn't read the chart template from this repo.
    • Check with helm template before merging.
  • The secret is on the worker only, which goes against the repo's own comment (helmrelease.yaml:85-92).
    • That comment says variables used through !Env are put in global.env, because "if only the worker had it, blueprint apply would crash on startup with KeyError".
    • This PR puts DOT1X_OUTPOST_SECRET on the worker only. If the server also resolves blueprint !Env tags, as that comment suggests, the server may fail on dot1x-radius.yaml.
    • I found no issue or PR that settles which pod really needs it. A second, smaller concern: with optional: true and no Secret present, shared_secret would resolve to an empty or None value.
  • The raw Tunnel attributes may not reach the reply (blueprints-configmap.yaml, the dot1x-vlan-mapping expression).
    • The mapping writes raw integer keys (packet[64] = [b"..."]) inside try/except: pass, and calls define_attribute.
    • I can't confirm that authentik 2026.8's packet object accepts integer keys, or that the outpost passes them through. The error would be swallowed silently.
    • The Cisco-AV-Pair fallback, translated in the FreeRADIUS inner tunnel, covers this case. The plan's post-merge spike (Task 2, Step 5) should record which of the two paths is actually used.

🟡 Suggestions

  • The outpost network policy doesn't restrict anything (kubernetes/apps/authentik/freeradius-cnp.yaml:27-45).
    • dot1x-outpost-freeradius-ingress is additive. infrastructure/network-policies/authentik-ns.yaml already allows ingress to every pod from the whole authentik namespace, and from traefik, database, monitoring, paperless and host/remote-node.
    • So the spec's goal "CNP so the RADIUS outpost accepts RADIUS only from the FreeRADIUS pods" is not met. The provider's client_networks also covers the whole pod CIDR. The comment admits this.
    • Either say in docs/radius/dot1x.md that the shared secret is the only guard on this hop, or add a deny rule for UDP 1812 on the outpost from anything except FreeRADIUS.
  • A short glitch in the single home server can block all logins (freeradius-config.yaml, proxy.conf).
    • The pool has only one home server, with status_check = none, response_window = 3, zombie_period = 20 and revive_interval = 10.
    • One slow or lost outpost reply, for example during an outpost pod rollout, marks it zombie and then dead. All 802.1X logins then fail for about 10–30 s, because there is no fallback.
    • Consider a longer response_window or a shorter revive_interval. Also document that the hostname is resolved once at startup, so a re-created outpost Service needs a FreeRADIUS restart.
  • There is no PodDisruptionBudget for freeradius (freeradius.yaml). Node drains could take both replicas down if the two drains overlap.

🟢 Nits

  • docs/superpowers/plans/2026-11-03-dot1x-radius-authentik.md still uses ak-outpost-802-1x-radius, secrets in apps/authentik and authentik-secrets. That differs from what was built: 8021x-radius, apps/radius and radius-secrets.
  • The spec says VLAN 10 has no IPv6, but clients.conf and the CNP add fd02:10::/64. This is harmless, but the docs and the code disagree.

Tests

tests/radius/run.sh covers the FreeRADIUS side well. It uses the production ConfigMap and Deployment args, and checks:

  • the Tunnel-* and AV-pair VLAN paths;
  • rejection when there is no VLAN;
  • a fast reject without Message-Authenticator;
  • local accounting;
  • the probe command;
  • IPv6.

Nothing tests the authentik side: the blueprint, the property mapping output and the policy. That rests on the post-merge spike described in the plan.

Recommendation

Looks reasonable — no blocking issues; comments above are optional. Before merging, check the Helm env merge and the server-side !Env question.

🤖 **Automated review** — model `claude-opus-5-5`, commit `3ed987dd74ebc8394f00108bf7dd7821dc028837`. > Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Request a review from `review-bot` again for a fresh one. --- ## Summary This PR adds an in-cluster 802.1X path for the "Renner Test" SSID. FreeRADIUS ends the EAP-TTLS tunnel and sends the inner PAP request to a new authentik RADIUS outpost, which is managed by a blueprint. The PR also adds a certificate, an ExternalSecret, network policies, image automation, alerts, a Docker-based functional test and docs. The design is careful and well tested, and I found no defect I could verify. The two open risks are in authentik and the Helm chart, which I couldn't check from the repo. `mergeable` is true. There is no linked issue and no earlier review from me, so this is a first review. ## ⚪ Unverified - **The worker env list may replace the global one** (`kubernetes/apps/authentik/helmrelease.yaml:183-189`). - The new `worker.env` list adds `DOT1X_OUTPOST_SECRET`. If chart 2026.8.3 *replaces* `global.env` with `worker.env` instead of joining them, the worker loses `AUTHENTIK_SECRET_KEY`, the Postgres password and the other variables. - I couldn't read the chart template from this repo. - Check with `helm template` before merging. - **The secret is on the worker only, which goes against the repo's own comment** (`helmrelease.yaml:85-92`). - That comment says variables used through `!Env` are put in `global.env`, because "if only the worker had it, blueprint apply would crash on startup with KeyError". - This PR puts `DOT1X_OUTPOST_SECRET` on the worker only. If the server also resolves blueprint `!Env` tags, as that comment suggests, the server may fail on `dot1x-radius.yaml`. - I found no issue or PR that settles which pod really needs it. A second, smaller concern: with `optional: true` and no Secret present, `shared_secret` would resolve to an empty or `None` value. - **The raw Tunnel attributes may not reach the reply** (`blueprints-configmap.yaml`, the `dot1x-vlan-mapping` expression). - The mapping writes raw integer keys (`packet[64] = [b"..."]`) inside `try/except: pass`, and calls `define_attribute`. - I can't confirm that authentik 2026.8's packet object accepts integer keys, or that the outpost passes them through. The error would be swallowed silently. - The `Cisco-AV-Pair` fallback, translated in the FreeRADIUS inner tunnel, covers this case. The plan's post-merge spike (Task 2, Step 5) should record which of the two paths is actually used. ## 🟡 Suggestions - **The outpost network policy doesn't restrict anything** (`kubernetes/apps/authentik/freeradius-cnp.yaml:27-45`). - `dot1x-outpost-freeradius-ingress` is additive. `infrastructure/network-policies/authentik-ns.yaml` already allows ingress to every pod from the whole `authentik` namespace, and from `traefik`, `database`, `monitoring`, `paperless` and host/remote-node. - So the spec's goal "CNP so the RADIUS outpost accepts RADIUS only from the FreeRADIUS pods" is not met. The provider's `client_networks` also covers the whole pod CIDR. The comment admits this. - Either say in `docs/radius/dot1x.md` that the shared secret is the only guard on this hop, or add a deny rule for UDP 1812 on the outpost from anything except FreeRADIUS. - **A short glitch in the single home server can block all logins** (`freeradius-config.yaml`, `proxy.conf`). - The pool has only one home server, with `status_check = none`, `response_window = 3`, `zombie_period = 20` and `revive_interval = 10`. - One slow or lost outpost reply, for example during an outpost pod rollout, marks it zombie and then dead. All 802.1X logins then fail for about 10–30 s, because there is no fallback. - Consider a longer `response_window` or a shorter `revive_interval`. Also document that the hostname is resolved once at startup, so a re-created outpost Service needs a FreeRADIUS restart. - **There is no PodDisruptionBudget for `freeradius`** (`freeradius.yaml`). Node drains could take both replicas down if the two drains overlap. ## 🟢 Nits - `docs/superpowers/plans/2026-11-03-dot1x-radius-authentik.md` still uses `ak-outpost-802-1x-radius`, secrets in `apps/authentik` and `authentik-secrets`. That differs from what was built: `8021x-radius`, `apps/radius` and `radius-secrets`. - The spec says VLAN 10 has no IPv6, but `clients.conf` and the CNP add `fd02:10::/64`. This is harmless, but the docs and the code disagree. ## Tests `tests/radius/run.sh` covers the FreeRADIUS side well. It uses the production ConfigMap and Deployment args, and checks: - the Tunnel-* and AV-pair VLAN paths; - rejection when there is no VLAN; - a fast reject without Message-Authenticator; - local accounting; - the probe command; - IPv6. Nothing tests the authentik side: the blueprint, the property mapping output and the policy. That rests on the post-merge spike described in the plan. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. Before merging, check the Helm env merge and the server-side `!Env` question. <!-- pr-review:sha=3ed987dd74ebc8394f00108bf7dd7821dc028837 -->
claude-bot added 2 commits 2026-11-04 07:19:22 +00:00
Home server: response_window 5, status_check = request with a user that
does not exist in authentik (always rejected), check_interval 6,
check_timeout 4, num_answers_to_alive 3; revive_interval dropped. With
status_check = none the blind revive gave 7-8 s in some runs and no
recovery within 120 s in others.

tests/radius/run.sh: recovery case stalls the mock outpost until the
home server is marked dead, resumes it and asserts a login succeeds
within 20 s. It fails intermittently: radiusd stops answering
(liveness probe fails) after late replies arrive for requests it already
failed. Diagnosis pending.

Docs: secret is the only guard on the FreeRADIUS -> outpost hop; outpost
Service name resolved at startup. Spec: VLAN 10 is dual-stack
(fd02:10::/64). helmrelease comment: missing !Env resolves to the
default, no KeyError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(radius): proxy_dedup_window above response_window
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
64e19b4087
radiusd 3.2.10 segfaulted (exit 139) after the outpost came back from a
stall. A NAS retransmit forwarded to the outpost leaves the request in the
proxy hash while post-proxy runs; eapttls_postproxy() then sets
request->proxy->dst_port = 0 (ttls.c:880), which is part of the rbtree key,
so the later removal misses, the freed request stays in proxy_list and the
next reply lookup dereferences it in fr_packet_cmp(). Still present on the
v3.2.x head; 3.2.10 is the newest image.

With proxy_dedup_window (10) > response_window (5) a retransmit is never
forwarded while a request waits, num_proxied_requests stays 1 and the
request leaves the hash before post-proxy. Lost in-cluster packets are no
longer resent; the request fails after response_window.

Verified: 12/12 consecutive tests/radius/run.sh runs green incl. recovery
(10-15 s), radiusd -XC OK; before the change 2/2 runs crashed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot dismissed review-bot's review 2026-11-04 07:19:23 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

claude-bot requested review from review-bot 2026-11-04 07:19:46 +00:00
review-bot approved these changes 2026-11-04 07:23:53 +00:00
review-bot left a comment
Member

🤖 Automated review — model claude-opus-5-5, commit 64e19b40877299e7eb7e220303467bbdaf056c21.

Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Request a review from review-bot again for a fresh one.


Summary

This is a re-review. Two commits landed after my review at 3ed987dd:

  • f9eb38e2 adds active outpost status checks, a recovery test case and corrections to the docs and spec.
  • 64e19b40 sets proxy_dedup_window = 10, above response_window = 5, to avoid a crash in radiusd 3.2.10 after a stall.

My earlier review had no 🔴. Status of the earlier 🟡 points:

  • Outpost network policy doesn't restrict anything: addressed. docs/radius/dot1x.md now says the shared secret is the only guard on the FreeRADIUS → outpost hop.
  • A short outpost glitch can block all logins: addressed. The home server now uses status_check = request, and the docs say the outpost Service name is resolved once at startup.
  • No PodDisruptionBudget for freeradius: still open. It was optional.

The two earlier unverified points (env merge, server-side !Env) are unchanged in code. Only the helmrelease.yaml comment was reworded to claim there is no KeyError. The new commits look correct to me. mergeable is true.

⚪ Unverified

  • The status probe may need Message-Authenticator (kubernetes/apps/authentik/freeradius-config.yaml, proxy.conf, status_check = request).
    • Revival now depends completely on the outpost answering the dot1x-status-probe Access-Request: 3 answers, one check every 6 s.
    • The spec mentions outpost issues where packets are dropped as "invalid message authenticator" (goauthentik #17901, #22931).
    • tests/radius/mock_outpost.py answers every request whether or not it carries a valid Message-Authenticator (request_ma is only logged). So the test can't show whether radiusd 3.2.10 puts Message-Authenticator on status-check requests.
    • If the probe lacks it and the real outpost drops it, a home server marked dead never comes back until FreeRADIUS restarts.
    • Worth checking in the post-merge spike: stall the outpost once and watch for the "marking alive" log line.
  • What the reworded comment claims (kubernetes/apps/authentik/helmrelease.yaml:91-92).
    • The new text says a pod without the variable resolves !Env to None or the default instead of raising KeyError. I couldn't check this against authentik 2026.8 from the repo.
    • If it is true and the server pod also applies dot1x-radius.yaml, the server would write shared_secret: None/empty. The worker would then write the real value back, so the two would keep overwriting each other.
    • This is still the earlier point. Confirm which pod applies blueprints.

🟡 Suggestions

  • The recovery test leaves little margin (tests/radius/run.sh, check_recovery 20). After a dead marking, revival needs 3 probe answers 6 s apart, so roughly 12–18 s. The commit reports 10–15 s against a bound of 20 s. A slow CI host could make this flaky. Consider a bound of 25–30 s, or derive it from check_interval × num_answers_to_alive plus check_timeout.

🟢 Nits

  • The comment for proxy_dedup_window = 10 (freeradius-config.yaml, radiusd.conf) could say that 10 is FreeRADIUS's upper bound for this setting. Then nobody later "fixes" the crash by raising response_window above it. I'm going from memory on the bound; I didn't check it in the repo.

Tests

check_recovery covers the new behaviour directly: it stalls the outpost until it is marked dead, resumes it, and expects a successful login within a set time with radiusd still running. The commit reports 12/12 green runs after the fix and 2/2 crashes before it. The mock outpost doesn't check Message-Authenticator on requests, so the probe-acceptance risk above stays untested.

Recommendation

Looks reasonable — no blocking issues; comments above are optional. There were no earlier 🔴 points, and the new commits add none.

🤖 **Automated review** — model `claude-opus-5-5`, commit `64e19b40877299e7eb7e220303467bbdaf056c21`. > Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Request a review from `review-bot` again for a fresh one. --- ## Summary This is a re-review. Two commits landed after my review at `3ed987dd`: - `f9eb38e2` adds active outpost status checks, a recovery test case and corrections to the docs and spec. - `64e19b40` sets `proxy_dedup_window = 10`, above `response_window = 5`, to avoid a crash in radiusd 3.2.10 after a stall. My earlier review had no 🔴. Status of the earlier 🟡 points: - **Outpost network policy doesn't restrict anything:** addressed. `docs/radius/dot1x.md` now says the shared secret is the only guard on the FreeRADIUS → outpost hop. - **A short outpost glitch can block all logins:** addressed. The home server now uses `status_check = request`, and the docs say the outpost Service name is resolved once at startup. - **No PodDisruptionBudget for `freeradius`:** still open. It was optional. The two earlier unverified points (env merge, server-side `!Env`) are unchanged in code. Only the `helmrelease.yaml` comment was reworded to claim there is no KeyError. The new commits look correct to me. `mergeable` is true. ## ⚪ Unverified - **The status probe may need Message-Authenticator** (`kubernetes/apps/authentik/freeradius-config.yaml`, `proxy.conf`, `status_check = request`). - Revival now depends completely on the outpost answering the `dot1x-status-probe` Access-Request: 3 answers, one check every 6 s. - The spec mentions outpost issues where packets are dropped as "invalid message authenticator" (goauthentik #17901, #22931). - `tests/radius/mock_outpost.py` answers every request whether or not it carries a valid Message-Authenticator (`request_ma` is only logged). So the test can't show whether radiusd 3.2.10 puts Message-Authenticator on status-check requests. - If the probe lacks it and the real outpost drops it, a home server marked dead never comes back until FreeRADIUS restarts. - Worth checking in the post-merge spike: stall the outpost once and watch for the "marking alive" log line. - **What the reworded comment claims** (`kubernetes/apps/authentik/helmrelease.yaml:91-92`). - The new text says a pod without the variable resolves `!Env` to None or the default instead of raising KeyError. I couldn't check this against authentik 2026.8 from the repo. - If it is true and the server pod also applies `dot1x-radius.yaml`, the server would write `shared_secret: None`/empty. The worker would then write the real value back, so the two would keep overwriting each other. - This is still the earlier point. Confirm which pod applies blueprints. ## 🟡 Suggestions - **The recovery test leaves little margin** (`tests/radius/run.sh`, `check_recovery 20`). After a dead marking, revival needs 3 probe answers 6 s apart, so roughly 12–18 s. The commit reports 10–15 s against a bound of 20 s. A slow CI host could make this flaky. Consider a bound of 25–30 s, or derive it from `check_interval × num_answers_to_alive` plus `check_timeout`. ## 🟢 Nits - The comment for `proxy_dedup_window = 10` (`freeradius-config.yaml`, `radiusd.conf`) could say that 10 is FreeRADIUS's upper bound for this setting. Then nobody later "fixes" the crash by raising `response_window` above it. I'm going from memory on the bound; I didn't check it in the repo. ## Tests `check_recovery` covers the new behaviour directly: it stalls the outpost until it is marked dead, resumes it, and expects a successful login within a set time with radiusd still running. The commit reports 12/12 green runs after the fix and 2/2 crashes before it. The mock outpost doesn't check Message-Authenticator on requests, so the probe-acceptance risk above stays untested. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. There were no earlier 🔴 points, and the new commits add none. <!-- pr-review:sha=64e19b40877299e7eb7e220303467bbdaf056c21 -->
k.renner merged commit 49017ac5d7 into main 2026-11-04 07:39:20 +00:00
Sign in to join this conversation.