diff --git a/docs/radius/clients/renner-test.mobileconfig b/docs/radius/clients/renner-test.mobileconfig
deleted file mode 100644
index 1fd30025..00000000
--- a/docs/radius/clients/renner-test.mobileconfig
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
- PayloadContent
-
-
- PayloadType
- com.apple.wifi.managed
- PayloadVersion
- 1
- PayloadIdentifier
- uno.renner.wifi.renner-test.network
- PayloadUUID
- c7814579-fc2b-4f38-bcc8-77e1123ab710
- PayloadDisplayName
- Renner Test Wi-Fi
- PayloadOrganization
- Renner
- SSID_STR
- Renner Test
- HIDDEN_NETWORK
-
- AutoJoin
-
- EncryptionType
- Any
- EAPClientConfiguration
-
- OuterIdentity
- anonymous
- AcceptEAPTypes
-
- 21
-
- TTLSInnerAuthentication
- PAP
- TLSTrustedServerNames
-
- radius.renner.uno
-
-
-
-
- PayloadType
- Configuration
- PayloadVersion
- 1
- PayloadIdentifier
- uno.renner.wifi.renner-test
- PayloadUUID
- b35513d4-97e5-4804-a77b-5ca1f723a408
- PayloadDisplayName
- Renner Test Wi-Fi (802.1X)
- PayloadOrganization
- Renner
-
-
diff --git a/docs/radius/dot1x.md b/docs/radius/dot1x.md
index f2b51ac6..baddd157 100644
--- a/docs/radius/dot1x.md
+++ b/docs/radius/dot1x.md
@@ -143,7 +143,13 @@ For IPv6 set `LIVE_SOURCE` to the workstation's `fd02:50::` address and use
`fd99::91`. `group-id=[3530]` is VLAN `50`, `tunnel-type=[0000000d]` VLAN,
`medium=[00000006]` IEEE-802.
-## Client setup (SSID "Renner Test")
+## Client setup (SSID "Renner UniFi")
+
+Setup page: `https://wlan.renner.uno` (behind the Authentik gate, any
+authentik user; `kubernetes/apps/authentik/wlan-setup*.yaml`). It serves the
+Apple profile (caller's username filled in), a Windows file that imports the
+WLAN profile with `netsh`, and the Linux `nmcli` command. No file carries a password. Android has no
+import path without Passpoint or an app; it uses the manual values below.
All clients use EAP-TTLS with inner PAP. The server certificate is a public
Let's Encrypt certificate for `radius.renner.uno`, so no CA needs to be
@@ -175,7 +181,7 @@ inside the TLS tunnel to FreeRADIUS and is never sent in clear over the air.
Settings > Network & Internet > Wi-Fi > Manage known networks > Add network:
-- Network name: `Renner Test`
+- Network name: `Renner UniFi`
- Security type: WPA2-Enterprise AES
- EAP method: EAP-TTLS
- Authentication method: Unencrypted password (PAP)
@@ -191,10 +197,9 @@ wireless network" instead.
### Apple (iOS, iPadOS, macOS)
-Apple devices need the configuration profile
-`docs/radius/clients/renner-test.mobileconfig`. Install it via AirDrop, Files
-or Settings (Profile Downloaded). The device asks for username and password on
-the first join. The profile sets security type Any (WPA2/WPA3), outer identity
+Apple devices need a configuration profile; the setup page serves it with the
+username filled in. Install it via Settings (Profile Downloaded). The device
+asks for the password on the first join. The profile sets security type Any (WPA2/WPA3), outer identity
`anonymous` and trusted server name `radius.renner.uno`; it contains no
password and no certificate. Apple
devices may show a one-time "trust certificate" prompt for `radius.renner.uno`
diff --git a/kubernetes/apps/authentik/kustomization.yaml b/kubernetes/apps/authentik/kustomization.yaml
index c465acbb..a662721c 100644
--- a/kubernetes/apps/authentik/kustomization.yaml
+++ b/kubernetes/apps/authentik/kustomization.yaml
@@ -16,6 +16,8 @@ resources:
- freeradius-network-attachment.yaml
- freeradius.yaml
- freeradius-cnp.yaml
+ - wlan-setup-config.yaml
+ - wlan-setup.yaml
# ak-pg CNPG Postgres backend (moved here from the retired seed-authentik KS;
# each carries prune: disabled so ownership transferred without a teardown).
# authentik has no data PVCs — all state lives in ak-pg. This KS depends on
diff --git a/kubernetes/apps/authentik/wlan-setup-config.yaml b/kubernetes/apps/authentik/wlan-setup-config.yaml
new file mode 100644
index 00000000..5e84f762
--- /dev/null
+++ b/kubernetes/apps/authentik/wlan-setup-config.yaml
@@ -0,0 +1,248 @@
+# wlan.renner.uno: setup files for "Renner UniFi" (802.1X, EAP-TTLS/PAP
+# against freeradius, outer identity anonymous, server name radius.renner.uno).
+# Served by wlan-setup (wlan-setup.yaml) behind the Authentik gate; the
+# username comes from X-authentik-username. No file carries a password.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: wlan-setup
+ namespace: authentik
+data:
+ default.conf: |
+ # Only plain usernames reach a generated file; anything else leaves the
+ # field empty and the device asks for it.
+ map $http_x_authentik_username $wifi_user {
+ "~^[A-Za-z0-9._@+-]{1,64}$" $http_x_authentik_username;
+ default "";
+ }
+
+ server {
+ listen 8080;
+ listen [::]:8080;
+ server_tokens off;
+ root /usr/share/nginx/html;
+ add_header Cache-Control "no-store" always;
+
+ location = / {
+ try_files /index.html =404;
+ }
+
+ location = /whoami {
+ default_type text/plain;
+ return 200 $wifi_user;
+ }
+
+ # Apple: com.apple.wifi.managed, TTLS (21) with inner PAP. Fixed
+ # PayloadIdentifier/UUIDs so a reinstall replaces the old profile.
+ location = /renner-unifi.mobileconfig {
+ default_type application/x-apple-aspen-config;
+ add_header Cache-Control "no-store" always;
+ add_header Content-Disposition 'attachment; filename="renner-unifi.mobileconfig"' always;
+ return 200 '
+
+
+
+ PayloadContent
+
+
+ PayloadType
+ com.apple.wifi.managed
+ PayloadVersion
+ 1
+ PayloadIdentifier
+ uno.renner.wifi.renner-unifi.network
+ PayloadUUID
+ 824959bf-c55d-4ed2-b569-c33f3759c2b6
+ PayloadDisplayName
+ Renner UniFi
+ PayloadOrganization
+ Renner
+ SSID_STR
+ Renner UniFi
+ HIDDEN_NETWORK
+
+ AutoJoin
+
+ EncryptionType
+ Any
+ EAPClientConfiguration
+
+ UserName
+ $wifi_user
+ OuterIdentity
+ anonymous
+ AcceptEAPTypes
+
+ 21
+
+ TTLSInnerAuthentication
+ PAP
+ TLSTrustedServerNames
+
+ radius.renner.uno
+
+
+
+
+ PayloadType
+ Configuration
+ PayloadVersion
+ 1
+ PayloadIdentifier
+ uno.renner.wifi.renner-unifi
+ PayloadUUID
+ 7175a2a3-ddd3-42ea-b4ad-b7e5501892f3
+ PayloadDisplayName
+ WLAN Renner UniFi
+ PayloadOrganization
+ Renner
+
+
+ ';
+ }
+
+ location = /renner-unifi-wlan.cmd {
+ default_type application/octet-stream;
+ add_header Cache-Control "no-store" always;
+ add_header Content-Disposition 'attachment; filename="renner-unifi-wlan.cmd"' always;
+ try_files /renner-unifi-wlan.cmd =404;
+ }
+
+ location = /healthz {
+ access_log off;
+ default_type text/plain;
+ return 200 "ok\n";
+ }
+
+ location / {
+ return 404;
+ }
+ }
+
+ # Windows: extracts the WLAN profile (everything after line 8 of itself)
+ # into %TEMP% and imports it for the current user with netsh. EAP-TTLS with
+ # PAPAuthentication, outer identity anonymous, server name
+ # radius.renner.uno, trust anchor ISRG Root X1 (SHA-1 thumbprint; the
+ # Let's Encrypt chain of radius-tls ends there). Windows asks for username
+ # and password on connect. CRLF line endings: cmd.exe needs them.
+ renner-unifi-wlan.cmd: "@echo off\r\nrem WLAN Renner UniFi einrichten (802.1X, Anmeldung mit dem authentik-Konto)\r\nset \"WLANXML=%TEMP%\\renner-unifi-wlan.xml\"\r\nmore +8 \"%~f0\" > \"%WLANXML%\"\r\nnetsh wlan add profile filename=\"%WLANXML%\" user=current\r\ndel \"%WLANXML%\"\r\nnetsh wlan connect name=\"Renner UniFi\"\r\npause & exit /b\r\nRenner UniFi52656E6E657220556E694669Renner UniFifalseESSautoWPA2AEStruetrueuser2100311radius.renner.unoca bd 2a 79 a1 07 6a 31 f2 1d 25 36 35 cb 03 9d 43 29 a5 e8falsetrueanonymous\r\n"
+
+ index.html: |
+
+
+
+
+
+ WLAN einrichten
+
+
+
+
+ WLAN einrichten
+ WLAN Renner UniFi. Anmeldung mit deinem Konto … und deinem authentik-Passwort.
+
+
+
+
+ Andere Geräte
+
+
+
+
+
+ iPhone, iPad, Mac
+ Profil laden
+
+ - iPhone/iPad: Einstellungen → „Profil geladen“ → Installieren. Mac: Systemeinstellungen → Allgemein → Geräteverwaltung → Profil doppelklicken.
+ - Beim ersten Verbinden mit „Renner UniFi“ dein Passwort eingeben.
+
+
+
+
+
+
+ Windows
+ Einrichtungsdatei laden
+
+ - Die geladene Datei doppelklicken. Fragt Windows nach („Der Computer wurde durch Windows geschützt“): „Weitere Informationen“ → „Trotzdem ausführen“.
+ - Beim Verbinden Benutzername … und dein Passwort eingeben.
+
+
+
+
+
+
+ Linux (NetworkManager)
+ Im Terminal ausführen, danach fragt NetworkManager nach dem Passwort:
+
+
+
+
+
+
+
+
diff --git a/kubernetes/apps/authentik/wlan-setup.yaml b/kubernetes/apps/authentik/wlan-setup.yaml
new file mode 100644
index 00000000..d0042f85
--- /dev/null
+++ b/kubernetes/apps/authentik/wlan-setup.yaml
@@ -0,0 +1,133 @@
+# wlan.renner.uno: setup page for the 802.1X WLAN "Renner UniFi" (Apple
+# profile, Windows import file, Linux nmcli command; config and files in
+# wlan-setup-config.yaml). Public, so a device can reach it before it is in the
+# WLAN (mobile data, Renner Gast). Any Authentik user passes the gate (no
+# group): the page holds no secrets, only the caller's own username.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: wlan-setup
+ namespace: authentik
+ labels:
+ app.kubernetes.io/name: wlan-setup
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: wlan-setup
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: wlan-setup
+ annotations:
+ configmap.reloader.stakater.com/reload: wlan-setup
+ spec:
+ automountServiceAccountToken: false
+ securityContext:
+ runAsNonRoot: true
+ runAsUser: 101
+ runAsGroup: 101
+ seccompProfile:
+ type: RuntimeDefault
+ containers:
+ - name: nginx
+ image: nginxinc/nginx-unprivileged:1.31-alpine@sha256:b5047b9b76845eac124358373c6852377c52e4b05834a47647e955dd91cb8f1e
+ ports:
+ - name: http
+ containerPort: 8080
+ protocol: TCP
+ readinessProbe:
+ httpGet:
+ path: /healthz
+ port: http
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: http
+ securityContext:
+ allowPrivilegeEscalation: false
+ readOnlyRootFilesystem: true
+ capabilities:
+ drop: ["ALL"]
+ resources:
+ requests:
+ cpu: 5m
+ memory: 16Mi
+ limits:
+ memory: 64Mi
+ volumeMounts:
+ - name: config
+ mountPath: /etc/nginx/conf.d
+ readOnly: true
+ - name: html
+ mountPath: /usr/share/nginx/html
+ readOnly: true
+ # pid file and temp paths of the unprivileged image.
+ - name: tmp
+ mountPath: /tmp
+ volumes:
+ - name: config
+ configMap:
+ name: wlan-setup
+ items:
+ - key: default.conf
+ path: default.conf
+ - name: html
+ configMap:
+ name: wlan-setup
+ items:
+ - key: index.html
+ path: index.html
+ - key: renner-unifi-wlan.cmd
+ path: renner-unifi-wlan.cmd
+ - name: tmp
+ emptyDir:
+ medium: Memory
+ sizeLimit: 16Mi
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: wlan-setup
+ namespace: authentik
+ labels:
+ app.kubernetes.io/name: wlan-setup
+spec:
+ ipFamilyPolicy: PreferDualStack
+ selector:
+ app.kubernetes.io/name: wlan-setup
+ ports:
+ - name: http
+ port: 80
+ targetPort: http
+ protocol: TCP
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: wlan-setup
+ namespace: authentik
+ labels:
+ # Public: Cloudflare CNAME → NetBird proxy → Traefik.
+ dns.renner.uno/public: "true"
+ netbird.renner.uno/public: "true"
+ annotations:
+ traefik.ingress.kubernetes.io/router.middlewares: authentik-gate@kubernetescrd
+ cert-manager.io/cluster-issuer: letsencrypt-cluster
+spec:
+ ingressClassName: traefik
+ tls:
+ - hosts:
+ - wlan.renner.uno
+ secretName: wlan-setup-tls
+ rules:
+ - host: wlan.renner.uno
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: wlan-setup
+ port:
+ number: 80