The ssh-ca role installs the claude-bot sudo rules into /etc/sudoers.d,
which a base system without sudo does not have.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The edge-host base install has no sudo, so /etc/sudoers.d is missing and
the sudoers template fails. DSM keeps its own sudo and is skipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
edge-2.iot.renner.uno joins [edge_host] (base roles only, no peripherals)
and [ssh_ca], and the NTP VRRP group as BACKUP with priority 60, below
edge-1.
The MQTT bridge account follows the host's short name instead of a fixed
edge-1, and the bridge client id is derived from it (edge-1 keeps
edge1-bridge). The register gets the account edge-2, and the controller's
egress policy allows edge-2's credential endpoint on 8443.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
edge-install wrote a hard-coded edge-1 into /etc/hostname, so every further
edge host came up with the production host's name and DHCP hostname.
The hostname is now a required argument, validated as an RFC 1123 label
and confirmed before the disk is wiped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rootless image defaults NB_LOG_FILE to
console,/var/lib/netbird/client.log. The state volume is a 16Mi
memory emptyDir, so a single 15MB rotated log fills it (ENOSPC on
log writes and rotation compression) and the tmpfs is charged
against the 256Mi memory limit. Console output already reaches Loki.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows Settings rejected the provisioning XML: Global/SubscriberId is
base:SubscriberType (\w+, at most 20 characters) in the schemas Windows
ships in %SYSTEMROOT%\schemas\provisioning, and "renner-unifi" contains a
hyphen. It is now "rennerunifi".
Those schemas also allow WPA3ENT and Hotspot2/NAIRealm, which the online
schema reference lacks: the profile uses WPA3ENT again (the network is
WPA3-Enterprise) and carries NAI realm renner.uno next to the domain.
TestWindowsProfileSchema validates the signed document with xmllint against
a copy of that schema folder (WLAN_SETUP_PROVISIONING_XSD); without it the
test is skipped, the schemas are Microsoft's files and not in the repo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All three profiles were assembled from strings. They are now typed
documents:
- Android: PPS MO from encoding/xml structs; MIME multipart with
mime/multipart (boundary renner-passpoint-profile; Android's ConfigParser
reads part headers into a map, so their order does not matter).
- Apple: .mobileconfig marshalled with howett.net/plist.
- Windows: CarrierProvisioning built as an etree document; the signature is
an etree subtree, digest and SignedInfo canonicalized with goxmldsig's
C14N 1.0 canonicalizer (SignedInfo in place, so it carries the inherited
xmldsig namespace). Same form as Microsoft's samples: one transform,
rsa-sha1, sha1 digest, chain in KeyInfo.
Tests parse the outputs (multipart reader, encoding/xml, plist, etree) and
verify the Windows signature with goxmldsig.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows Settings rejected the profile as invalid provisioning XML. The
CarrierControl WLAN schema allows authentication only up to WPA2; WPA3ENT is
not in it. The profile now uses WPA2 (Enterprise; Windows adds the PMF the
WPA3-Enterprise network requires) and follows Microsoft's signed Passpoint
samples otherwise: Hotspot2 with DomainName only, XML declaration in front
(outside the root, so not part of the digest).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wlan-setup Deployment runs gitea.renner.uno/gitea_admin/wlan-setup
(Flux image policy wlan-setup) instead of nginx + njs; the ConfigMap
wlan-setup-config.yaml with nginx.conf, njs and the Windows .cmd is removed.
- Certificate wlan-setup-signing for wlan.renner.uno (PKCS#8), mounted at
/etc/wlan-signing (fsGroup 65534, mode 0440); the service reads it per
request, so renewals need no restart.
- Ingress wlan-setup-provisioning: /windows/profile/ without the authentik
gate, for Windows Settings fetching the signed provisioning XML.
- OAuth provider wlan-setup: redirect URI
https://wlan.renner.uno/windows/callback.
- docs/radius/dot1x.md: Go service and the Windows Passpoint flow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Android and Apple get Passpoint profiles with their own app password from
wlan.renner.uno; the manual Android steps with the account password are
removed. Windows and Linux keep their setup files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wlan.renner.uno serves Apple devices the same way as Android: /apple/install
signs in through the wlan-setup OAuth application, /apple/callback creates
an app password (wlan-apple-...) with the user's own access token and returns
a .mobileconfig with Passpoint (IsHotspot, domain and NAI realm renner.uno),
EAP-TTLS/PAP, outer identity anonymous, trusted server name
radius.renner.uno, username and app password. The static profile without a
password is removed; its PayloadIdentifier/UUIDs are kept so the new profile
replaces it on install. The OAuth provider gets the redirect URI
https://wlan.renner.uno/apple/callback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Renner UniFi" now runs WPA3 Enterprise without transition mode and
advertises Passpoint (realm and domain renner.uno, EAP-TTLS/PAP), so the
Android profiles from wlan.renner.uno join it directly. The table row for
the deleted "Renner Test" WLAN is replaced; the Windows manual setup uses
WPA3-Enterprise.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New app passwords start with 29 days (below the 30-day API cap, so no
group attribute is needed). The expression policy dot1x-app-password-renew,
re-evaluated on the dot1x-login binding, renews the app password that
TokenBackend matched to 60 days on each 802.1X login (at most one write
per day; always passes, failure_result true). authentik rejects expired
app passwords and deletes them in clean_expired_models (every 5 min).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- subrequest_output_buffer_size 256k: users/me with groups, avatar and
permissions exceeds the one-page default and was cut off.
- proxy_pass_request_headers off: browser headers (Accept-Encoding,
User-Agent, X-authentik-*) no longer reach authentik.
- Random suffix on the app password identifier, so two installs in the
same second do not collide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wlan.renner.uno signs the user in through the new authentik OAuth2
application wlan-setup (confidential, scope goauthentik.io/api). The
callback exchanges the code (client_secret_basic), creates an app password
with the user's own access token (10 years) and returns the Passpoint
profile carrying it in the response to the user's GET. The page asks for
no password and stores nothing; deleting the app password in authentik
removes the device.
- dot1x-password stage also accepts app passwords (TokenBackend).
- Client secret: ESO Password generator wlan-setup-oauth, read by the
authentik worker (blueprint) and by wlan-setup.
- authentik calls go through an internal nginx location; headers come
from $arg_* because subrequests share the parent's variable cache.
- Reloader annotations on the wlan-setup Deployment metadata.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Android starts its Passpoint installer only for a profile fetched with a
user-triggered GET and Content-Type application/x-wifi-config; a
browser-generated blob does not qualify. The page now POSTs the password to
/android/prepare, where njs builds the profile and stores it in a shared
dict (120 s, bound to the caller's username) under a random token; the
link /android/profile/<token> delivers it once.
AAAServerTrustedNames moves under Extension/Android, the only place
Android's PpsMoParser accepts it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Android section takes the password, builds a Passpoint R1 profile
(application/x-wifi-config: PPS MO with HomeSP FQDN and realm renner.uno,
EAP-TTLS with inner PAP, AAA server name radius.renner.uno, ISRG Root X1 as
trust anchor) in the browser and hands it to Android as a blob. The password
is not sent to any server.
Matches WLANs that advertise Hotspot 2.0 with domain and NAI realm
renner.uno.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nginx behind the Authentik gate (any user, public) serving:
- Apple profile (TTLS/PAP, outer identity anonymous, trusted server name
radius.renner.uno) with the caller's username from X-authentik-username,
- a Windows .cmd that imports the WLAN profile via netsh (TTLS/PAP, server
name radius.renner.uno, trust anchor ISRG Root X1),
- the Linux nmcli command.
No file carries a password. Usernames outside [A-Za-z0-9._@+-] are left
blank instead of being put into a generated file.
The Renner Test profile is removed; that SSID no longer exists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>