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 +
+
+ + + + + + +
+ + + 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