wlan-setup/ is a Go service that replaces the nginx + njs backend of wlan.renner.uno. This PR adds the service, its tests, CI and image automation; the Deployment switches to the image in a follow-up once the first image exists.
Page, /whoami, /healthz; /{android,apple,windows}/install and /callback: authentik OAuth code flow (client wlan-setup, client_secret_basic), user check against X-authentik-username, per-device app password wlan-<platform>-… (29 days), profile per platform:
Android: Passpoint (application/x-wifi-config)
Apple: .mobileconfig with Passpoint
Windows: CarrierProvisioning with Passpoint, signed with XML-DSig in the form of Microsoft's samples (enveloped, C14N 1.0, rsa-sha1, chain in KeyInfo) by the key in /etc/wlan-signing; kept for 5 minutes under a single-use token; the browser is sent to ms-settings:wifi-provisioning?uri=…/windows/profile/<token>.
Containerfile as review-bot (static binary, FROM scratch, uid 65534).
Tests offline against a fake authentik (11 tests, go test -race); goxmldsig verifies the Windows signature in the tests only.
CI: .woodpecker/wlan-setup-test.yml (gofmt, vet, test) on PRs; build-ci-images.yml builds gitea.renner.uno/gitea_admin/wlan-setup:YYYY-MM-DD-HHMM on main.
`wlan-setup/` is a Go service that replaces the nginx + njs backend of wlan.renner.uno. This PR adds the service, its tests, CI and image automation; the Deployment switches to the image in a follow-up once the first image exists.
- Page, `/whoami`, `/healthz`; `/{android,apple,windows}/install` and `/callback`: authentik OAuth code flow (client `wlan-setup`, client_secret_basic), user check against `X-authentik-username`, per-device app password `wlan-<platform>-…` (29 days), profile per platform:
- Android: Passpoint (`application/x-wifi-config`)
- Apple: `.mobileconfig` with Passpoint
- Windows: CarrierProvisioning with Passpoint, signed with XML-DSig in the form of Microsoft's samples (enveloped, C14N 1.0, rsa-sha1, chain in KeyInfo) by the key in `/etc/wlan-signing`; kept for 5 minutes under a single-use token; the browser is sent to `ms-settings:wifi-provisioning?uri=…/windows/profile/<token>`.
- Containerfile as review-bot (static binary, FROM scratch, uid 65534).
- Tests offline against a fake authentik (11 tests, `go test -race`); goxmldsig verifies the Windows signature in the tests only.
- CI: `.woodpecker/wlan-setup-test.yml` (gofmt, vet, test) on PRs; `build-ci-images.yml` builds `gitea.renner.uno/gitea_admin/wlan-setup:YYYY-MM-DD-HHMM` on main.
- Image automation `wlan-setup` (newest `YYYY-MM-DD-HHMM` tag).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
wlan-setup/ is a Go service that will replace the nginx + njs backend of
wlan.renner.uno (switched over in a follow-up once the first image exists).
- Page, /whoami, /healthz; /{android,apple,windows}/install and /callback:
authentik OAuth code flow (client wlan-setup, client_secret_basic), user
check against X-authentik-username, per-device app password
wlan-<platform>-... (29 days), profile per platform: Android Passpoint
(application/x-wifi-config), Apple .mobileconfig with Passpoint, Windows
CarrierProvisioning with Passpoint.
- Windows: the profile is signed with XML-DSig in the form of Microsoft's
samples (enveloped, C14N 1.0, rsa-sha1, chain in KeyInfo) by the key in
/etc/wlan-signing, kept for 5 minutes under a single-use token, and the
browser is sent to ms-settings:wifi-provisioning?uri=.../windows/profile/<token>.
- Offline tests against a fake authentik; goxmldsig verifies the signature
in the tests only.
- CI: wlan-setup-test (gofmt, vet, test -race) on PRs; build-ci-images
builds gitea.renner.uno/gitea_admin/wlan-setup:YYYY-MM-DD-HHMM on main;
Flux image automation follows the newest tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
k.renner
requested review from review-bot 2026-11-05 13:41:56 +00:00
🤖Automated review — model claude-opus-5-5, commit a05f1431caea881b81f808b0017a7ea923524ca6.
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 wlan-setup/, a Go service meant to replace the nginx + njs backend in kubernetes/apps/authentik/wlan-setup-config.yaml. It also adds an offline test suite, a PR test pipeline, a kaniko build step and Flux image automation. I compared the Android/Apple flow line by line with the current passpoint.js and found no deviation: same state cookie, the same four authentik calls, the same user check against X-authentik-username, the same identifier and expiry format, and the same profile bodies. The new Windows path (XML-DSig signing plus a single-use token store) looks correct, and a test verifies its signature with goxmldsig. The CI step, Containerfile and ImagePolicy match their siblings (build-review-bot, review-bot/Containerfile, kuma-sync.yaml). mergeable is true. Every changed file is new except three append-only lines in build-ci-images.yml, image-automation/kustomization.yaml and its README. I found no blocking defect.
⚪ Unverified
Gate exemption for /windows/profile/{token} (wlan-setup/server.go, windowsProfile handler at the end of the file). The handler expects Windows Settings to fetch the profile without an authentik session, so the follow-up Deployment/ingress change has to exempt exactly this path from the forward-auth gate. Two things matter:
The gate must stay in front of /{platform}/callback and /whoami.
The proxy in front must keep stripping or overwriting any X-authentik-username header sent by the client, because callback trusts that header as the signed-in user.
That config is not in this PR, so I couldn't check it.
Missing redirect URI for Windows. The wlan-setup OAuth provider in the authentik blueprint has to allow https://wlan.renner.uno/windows/callback. The current config only serves android/apple. I didn't read the blueprint, and this belongs in the follow-up.
Windows Settings behaviour. I can't verify from the repo whether Windows Settings fetches the ms-settings:wifi-provisioning?uri=… target only once (HEAD is handled without consuming the token). I also can't verify whether it accepts the WLANProfile with Hotspot2 + WPA3ENT and a chain signed by the wlan-signing certificate. If it does a second GET, the single-use token returns 404.
Multiple replicas.ProfileStore (wlan-setup/store.go) is in-memory. If the follow-up Deployment runs more than one replica, the profile fetch can land on a different pod than the callback and get a 404. With one replica this is fine.
🟡 Suggestions
Orphan app passwords on the Windows path (wlan-setup/server.go, callback, case "windows"). signer.Sign and s.profiles.Put run after AppPassword has already created the app password. If either fails, the user keeps an unused wlan-windows-… token in authentik until it expires 29 days later. One fix is to check store capacity before creating the password; at minimum, log the identifier in the failure message so it can be found and deleted.
Unescaped token in the redirect (wlan-setup/server.go, the ms-settings: redirect). The URL is concatenated without escaping. That is safe today, because PublicURL comes from config and the token is raw-URL base64. Wrapping it in url.QueryEscape would make it robust if PublicURL ever has a path or query.
🟢 Nits
wlan-setup/index.html already says Windows gets its own app password and links to /windows/install. That's fine for an image that only goes live with the follow-up, but the ConfigMap's index.html and the image's copy will disagree until the switch.
Tests
Coverage is good for a new service. There are 11 offline tests against a fake authentik. They cover:
state mismatch, user mismatch, each upstream failure, and a missing signing key (no authentik call)
the exact call sequence and request body sent to authentik
Android, Apple and Windows output
signature verification, a tamper check, single use and TTL expiry
route 404s and config validation
.woodpecker/wlan-setup-test.yml runs gofmt, vet and test -race on PRs. Nothing is missing that would block.
Recommendation
Looks reasonable: no blocking issues; the comments above are optional. The unverified points need settling in the follow-up Deployment/gate change.
🤖 **Automated review** — model `claude-opus-5-5`, commit `a05f1431caea881b81f808b0017a7ea923524ca6`.
> 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 `wlan-setup/`, a Go service meant to replace the nginx + njs backend in `kubernetes/apps/authentik/wlan-setup-config.yaml`. It also adds an offline test suite, a PR test pipeline, a kaniko build step and Flux image automation. I compared the Android/Apple flow line by line with the current `passpoint.js` and found no deviation: same state cookie, the same four authentik calls, the same user check against `X-authentik-username`, the same identifier and expiry format, and the same profile bodies. The new Windows path (XML-DSig signing plus a single-use token store) looks correct, and a test verifies its signature with goxmldsig. The CI step, Containerfile and ImagePolicy match their siblings (`build-review-bot`, `review-bot/Containerfile`, `kuma-sync.yaml`). `mergeable` is true. Every changed file is new except three append-only lines in `build-ci-images.yml`, `image-automation/kustomization.yaml` and its README. I found no blocking defect.
## ⚪ Unverified
- **Gate exemption for `/windows/profile/{token}`** (`wlan-setup/server.go`, `windowsProfile` handler at the end of the file). The handler expects Windows Settings to fetch the profile without an authentik session, so the follow-up Deployment/ingress change has to exempt exactly this path from the forward-auth gate. Two things matter:
- The gate must stay in front of `/{platform}/callback` and `/whoami`.
- The proxy in front must keep stripping or overwriting any `X-authentik-username` header sent by the client, because `callback` trusts that header as the signed-in user.
That config is not in this PR, so I couldn't check it.
- **Missing redirect URI for Windows.** The `wlan-setup` OAuth provider in the authentik blueprint has to allow `https://wlan.renner.uno/windows/callback`. The current config only serves android/apple. I didn't read the blueprint, and this belongs in the follow-up.
- **Windows Settings behaviour.** I can't verify from the repo whether Windows Settings fetches the `ms-settings:wifi-provisioning?uri=…` target only once (HEAD is handled without consuming the token). I also can't verify whether it accepts the WLANProfile with `Hotspot2` + `WPA3ENT` and a chain signed by the `wlan-signing` certificate. If it does a second GET, the single-use token returns 404.
- **Multiple replicas.** `ProfileStore` (`wlan-setup/store.go`) is in-memory. If the follow-up Deployment runs more than one replica, the profile fetch can land on a different pod than the callback and get a 404. With one replica this is fine.
## 🟡 Suggestions
- **Orphan app passwords on the Windows path** (`wlan-setup/server.go`, `callback`, `case "windows"`). `signer.Sign` and `s.profiles.Put` run after `AppPassword` has already created the app password. If either fails, the user keeps an unused `wlan-windows-…` token in authentik until it expires 29 days later. One fix is to check store capacity before creating the password; at minimum, log the identifier in the failure message so it can be found and deleted.
- **Unescaped token in the redirect** (`wlan-setup/server.go`, the `ms-settings:` redirect). The URL is concatenated without escaping. That is safe today, because `PublicURL` comes from config and the token is raw-URL base64. Wrapping it in `url.QueryEscape` would make it robust if `PublicURL` ever has a path or query.
## 🟢 Nits
- `wlan-setup/index.html` already says Windows gets its own app password and links to `/windows/install`. That's fine for an image that only goes live with the follow-up, but the ConfigMap's `index.html` and the image's copy will disagree until the switch.
## Tests
Coverage is good for a new service. There are 11 offline tests against a fake authentik. They cover:
- state mismatch, user mismatch, each upstream failure, and a missing signing key (no authentik call)
- the exact call sequence and request body sent to authentik
- Android, Apple and Windows output
- signature verification, a tamper check, single use and TTL expiry
- route 404s and config validation
`.woodpecker/wlan-setup-test.yml` runs `gofmt`, `vet` and `test -race` on PRs. Nothing is missing that would block.
## Recommendation
Looks reasonable: no blocking issues; the comments above are optional. The unverified points need settling in the follow-up Deployment/gate change.
<!-- pr-review:sha=a05f1431caea881b81f808b0017a7ea923524ca6 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
wlan-setup/is a Go service that replaces the nginx + njs backend of wlan.renner.uno. This PR adds the service, its tests, CI and image automation; the Deployment switches to the image in a follow-up once the first image exists./whoami,/healthz;/{android,apple,windows}/installand/callback: authentik OAuth code flow (clientwlan-setup, client_secret_basic), user check againstX-authentik-username, per-device app passwordwlan-<platform>-…(29 days), profile per platform:application/x-wifi-config).mobileconfigwith Passpoint/etc/wlan-signing; kept for 5 minutes under a single-use token; the browser is sent toms-settings:wifi-provisioning?uri=…/windows/profile/<token>.go test -race); goxmldsig verifies the Windows signature in the tests only..woodpecker/wlan-setup-test.yml(gofmt, vet, test) on PRs;build-ci-images.ymlbuildsgitea.renner.uno/gitea_admin/wlan-setup:YYYY-MM-DD-HHMMon main.wlan-setup(newestYYYY-MM-DD-HHMMtag).🤖 Generated with Claude Code
wlan-setup/ is a Go service that will replace the nginx + njs backend of wlan.renner.uno (switched over in a follow-up once the first image exists). - Page, /whoami, /healthz; /{android,apple,windows}/install and /callback: authentik OAuth code flow (client wlan-setup, client_secret_basic), user check against X-authentik-username, per-device app password wlan-<platform>-... (29 days), profile per platform: Android Passpoint (application/x-wifi-config), Apple .mobileconfig with Passpoint, Windows CarrierProvisioning with Passpoint. - Windows: the profile is signed with XML-DSig in the form of Microsoft's samples (enveloped, C14N 1.0, rsa-sha1, chain in KeyInfo) by the key in /etc/wlan-signing, kept for 5 minutes under a single-use token, and the browser is sent to ms-settings:wifi-provisioning?uri=.../windows/profile/<token>. - Offline tests against a fake authentik; goxmldsig verifies the signature in the tests only. - CI: wlan-setup-test (gofmt, vet, test -race) on PRs; build-ci-images builds gitea.renner.uno/gitea_admin/wlan-setup:YYYY-MM-DD-HHMM on main; Flux image automation follows the newest tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>🤖 Automated review — model
claude-opus-5-5, commita05f1431caea881b81f808b0017a7ea923524ca6.Summary
This PR adds
wlan-setup/, a Go service meant to replace the nginx + njs backend inkubernetes/apps/authentik/wlan-setup-config.yaml. It also adds an offline test suite, a PR test pipeline, a kaniko build step and Flux image automation. I compared the Android/Apple flow line by line with the currentpasspoint.jsand found no deviation: same state cookie, the same four authentik calls, the same user check againstX-authentik-username, the same identifier and expiry format, and the same profile bodies. The new Windows path (XML-DSig signing plus a single-use token store) looks correct, and a test verifies its signature with goxmldsig. The CI step, Containerfile and ImagePolicy match their siblings (build-review-bot,review-bot/Containerfile,kuma-sync.yaml).mergeableis true. Every changed file is new except three append-only lines inbuild-ci-images.yml,image-automation/kustomization.yamland its README. I found no blocking defect.⚪ Unverified
Gate exemption for
/windows/profile/{token}(wlan-setup/server.go,windowsProfilehandler at the end of the file). The handler expects Windows Settings to fetch the profile without an authentik session, so the follow-up Deployment/ingress change has to exempt exactly this path from the forward-auth gate. Two things matter:/{platform}/callbackand/whoami.X-authentik-usernameheader sent by the client, becausecallbacktrusts that header as the signed-in user.That config is not in this PR, so I couldn't check it.
Missing redirect URI for Windows. The
wlan-setupOAuth provider in the authentik blueprint has to allowhttps://wlan.renner.uno/windows/callback. The current config only serves android/apple. I didn't read the blueprint, and this belongs in the follow-up.Windows Settings behaviour. I can't verify from the repo whether Windows Settings fetches the
ms-settings:wifi-provisioning?uri=…target only once (HEAD is handled without consuming the token). I also can't verify whether it accepts the WLANProfile withHotspot2+WPA3ENTand a chain signed by thewlan-signingcertificate. If it does a second GET, the single-use token returns 404.Multiple replicas.
ProfileStore(wlan-setup/store.go) is in-memory. If the follow-up Deployment runs more than one replica, the profile fetch can land on a different pod than the callback and get a 404. With one replica this is fine.🟡 Suggestions
wlan-setup/server.go,callback,case "windows").signer.Signands.profiles.Putrun afterAppPasswordhas already created the app password. If either fails, the user keeps an unusedwlan-windows-…token in authentik until it expires 29 days later. One fix is to check store capacity before creating the password; at minimum, log the identifier in the failure message so it can be found and deleted.wlan-setup/server.go, thems-settings:redirect). The URL is concatenated without escaping. That is safe today, becausePublicURLcomes from config and the token is raw-URL base64. Wrapping it inurl.QueryEscapewould make it robust ifPublicURLever has a path or query.🟢 Nits
wlan-setup/index.htmlalready says Windows gets its own app password and links to/windows/install. That's fine for an image that only goes live with the follow-up, but the ConfigMap'sindex.htmland the image's copy will disagree until the switch.Tests
Coverage is good for a new service. There are 11 offline tests against a fake authentik. They cover:
.woodpecker/wlan-setup-test.ymlrunsgofmt,vetandtest -raceon PRs. Nothing is missing that would block.Recommendation
Looks reasonable: no blocking issues; the comments above are optional. The unverified points need settling in the follow-up Deployment/gate change.