Compare commits

...
Author SHA1 Message Date
security-bot cd2e7864a4 chore(image-automation): bump gitea.renner.uno/gitea_admin/wlan-setup:2026-11-05-1517
Updates managed by image-automation-controller.
2026-11-05 15:19:21 +00:00
k.renner fec9f6e0e5 Merge pull request 'fix(wlan-setup): Windows profile valid against the provisioning schemas' (#1573) from fix/wlan-setup-windows-xsd into main
ci/woodpecker/push/build-ci-images Pipeline was successful
Reviewed-on: #1573
2026-11-05 15:17:16 +00:00
claude-botandClaude Opus 5.5 55fce76a2d fix(wlan-setup): Windows profile valid against the provisioning schemas
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/wlan-setup-test Pipeline was successful
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>
2026-11-05 16:16:06 +01:00
security-bot 5fcd6dc5c4 chore(image-automation): bump gitea.renner.uno/gitea_admin/wlan-setup:2026-11-05-1429
Updates managed by image-automation-controller.
2026-11-05 14:53:19 +00:00
security-bot 3a35e8481a chore(image-automation): bump gotenberg/gotenberg:8.40.0
Updates managed by image-automation-controller.
2026-11-05 14:53:04 +00:00
k.renner a5be653044 Merge pull request 'fix(wlan-setup): schema-valid Windows profile, profiles built with serializers' (#1572) from fix/wlan-setup-windows-schema into main
ci/woodpecker/push/build-ci-images Pipeline was successful
Reviewed-on: #1572
Reviewed-by: Review Bot <review-bot@renner.uno>
2026-11-05 14:29:39 +00:00
security-bot 1d5a262a84 chore(image-automation): bump gitea.renner.uno/gitea_admin/ci-backup-verify:2026-11-05
Updates managed by image-automation-controller.
2026-11-05 14:24:19 +00:00
security-bot ad6fc66875 chore(image-automation): bump gitea.renner.uno/gitea_admin/kuma-sync:2026-11-05-1355
Updates managed by image-automation-controller.
2026-11-05 14:24:14 +00:00
security-bot 861e9a3193 chore(image-automation): bump gitea.renner.uno/konairius/alpine-openssl:2026-11-05-1353
Updates managed by image-automation-controller.
2026-11-05 14:23:48 +00:00
7 changed files with 73 additions and 14 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ spec:
type: RuntimeDefault
containers:
- name: wlan-setup
image: gitea.renner.uno/gitea_admin/wlan-setup:2026-11-05-1352 # {"$imagepolicy": "flux-system:wlan-setup"}
image: gitea.renner.uno/gitea_admin/wlan-setup:2026-11-05-1517 # {"$imagepolicy": "flux-system:wlan-setup"}
env:
# Client secret of the "WLAN setup" OAuth2 provider.
- name: WLAN_SETUP_CLIENT_SECRET
+1 -1
View File
@@ -30,7 +30,7 @@ spec:
type: RuntimeDefault
containers:
- name: controller
image: gitea.renner.uno/gitea_admin/kuma-sync:2026-10-30-1314 # {"$imagepolicy": "flux-system:kuma-sync"}
image: gitea.renner.uno/gitea_admin/kuma-sync:2026-11-05-1355 # {"$imagepolicy": "flux-system:kuma-sync"}
env:
- name: KUMA_URL
value: https://kuma.renner.uno
+1 -1
View File
@@ -22,7 +22,7 @@ spec:
type: RuntimeDefault
containers:
- name: gotenberg
image: gotenberg/gotenberg:8.39.0 # {"$imagepolicy": "flux-system:gotenberg"}
image: gotenberg/gotenberg:8.40.0 # {"$imagepolicy": "flux-system:gotenberg"}
args:
- "gotenberg"
- "--chromium-disable-javascript=true"
+1 -1
View File
@@ -155,7 +155,7 @@ spec:
# exist on both registries — hence konairius/, which matches the ghcr
# namespace. Steady state still pulls from Gitea and never leaves the
# network.
image: gitea.renner.uno/konairius/alpine-openssl:2026-10-29-1348 # {"$imagepolicy": "flux-system:alpine-openssl"}
image: gitea.renner.uno/konairius/alpine-openssl:2026-11-05-1353 # {"$imagepolicy": "flux-system:alpine-openssl"}
command:
- sh
- -c
@@ -32,7 +32,7 @@ spec:
type: RuntimeDefault
containers:
- name: backup-verify
image: gitea.renner.uno/gitea_admin/ci-backup-verify:2026-10-29 # {"$imagepolicy": "flux-system:backup-verify"}
image: gitea.renner.uno/gitea_admin/ci-backup-verify:2026-11-05 # {"$imagepolicy": "flux-system:backup-verify"}
command: ["bash", "/script/backup-verify.sh"]
env:
- name: HOME
+9 -7
View File
@@ -200,10 +200,10 @@ func addText(parent *etree.Element, tag, ns, text string) *etree.Element {
}
// windowsProfile is the unsigned CarrierProvisioning document: one Hotspot 2.0 WLAN profile
// matched by domain name, EAP-TTLS with inner PAP, and the user's credentials. The
// CarrierControl WLAN schema allows authentication up to WPA2 (Enterprise); Windows adds the
// PMF that the WPA3-Enterprise network requires. Elements follow Microsoft's signed Passpoint
// provisioning samples.
// (domain and NAI realm renner.uno), WPA3-Enterprise, EAP-TTLS with inner PAP, and the user's
// credentials. Valid against the schemas Windows ships in %SYSTEMROOT%\schemas\provisioning
// (TestWindowsProfileSchema); SubscriberId is base:SubscriberType, i.e. \w+ and at most 20
// characters.
func windowsProfile(user, password string) *etree.Document {
doc := etree.NewDocument()
doc.WriteSettings = etree.WriteSettings{CanonicalEndTags: true, CanonicalText: true, CanonicalAttrVal: true}
@@ -213,16 +213,18 @@ func windowsProfile(user, password string) *etree.Document {
global := add(root, "Global", "")
addText(global, "CarrierId", "", carrierID)
addText(global, "SubscriberId", "", "renner-unifi")
addText(global, "SubscriberId", "", "rennerunifi")
wlan := add(add(root, "WLANProfiles", ""), "WLANProfile", nsWLAN)
addText(wlan, "name", "", "Renner UniFi")
addText(add(add(wlan, "SSIDConfig", ""), "SSID", ""), "name", "", "Renner UniFi")
addText(add(wlan, "Hotspot2", ""), "DomainName", "", "renner.uno")
hotspot2 := add(wlan, "Hotspot2", "")
addText(hotspot2, "DomainName", "", "renner.uno")
addText(add(hotspot2, "NAIRealm", ""), "name", "", "renner.uno")
security := add(add(wlan, "MSM", ""), "security", "")
auth := add(security, "authEncryption", "")
addText(auth, "authentication", "", "WPA2")
addText(auth, "authentication", "", "WPA3ENT")
addText(auth, "encryption", "", "AES")
addText(auth, "useOneX", "", "true")
+59 -2
View File
@@ -22,6 +22,7 @@ import (
"net/textproto"
"net/url"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
@@ -644,6 +645,61 @@ func TestCallbackWindows(t *testing.T) {
}
}
// TestWindowsProfileSchema validates the signed document with xmllint against the schemas
// Windows ships in %SYSTEMROOT%\schemas\provisioning. They are Microsoft's files and not in the
// repo: point WLAN_SETUP_PROVISIONING_XSD at a copy of that folder to run it.
func TestWindowsProfileSchema(t *testing.T) {
dir := os.Getenv("WLAN_SETUP_PROVISIONING_XSD")
if dir == "" {
t.Skip("WLAN_SETUP_PROVISIONING_XSD not set")
}
xmllint, err := exec.LookPath("xmllint")
if err != nil {
t.Skip("xmllint not installed")
}
tc := writeSigningFiles(t)
signer, err := loadSigner(tc.keyFile, tc.certFile)
if err != nil {
t.Fatal(err)
}
body, err := signer.Sign(windowsProfile("Alice.Smith", testKey))
if err != nil {
t.Fatal(err)
}
// The schemas import each other by namespace only; a wrapper schema gives libxml2 the
// locations. Dependencies first, so the later imports find their namespaces loaded.
wrapper := etree.NewDocument()
schema := wrapper.CreateElement("xs:schema")
schema.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema")
schema.CreateAttr("targetNamespace", "urn:wlan-setup:schema-test")
for _, s := range []struct{ ns, file string }{
{"http://www.microsoft.com/networking/CarrierControl/Base/v1", "provisioning_base_v1.xsd"},
{"http://www.microsoft.com/networking/CarrierControl/Plans/v1", "provisioning_plans_v1.xsd"},
{"http://www.microsoft.com/networking/CarrierControl/WWAN/v1", "provisioning_wwan_v1.xsd"},
{"http://www.microsoft.com/networking/CarrierControl/WLAN/v1", "provisioning_wlan_v1.xsd"},
{dsig.Namespace, "provisioning_sig_v1.xsd"},
{nsCarrierControl, "provisioning_v1.xsd"},
} {
imp := schema.CreateElement("xs:import")
imp.CreateAttr("namespace", s.ns)
imp.CreateAttr("schemaLocation", "file://"+filepath.Join(dir, s.file))
}
tmp := t.TempDir()
wrapperFile := filepath.Join(tmp, "wrapper.xsd")
if err := wrapper.WriteToFile(wrapperFile); err != nil {
t.Fatal(err)
}
docFile := filepath.Join(tmp, "profile.xml")
if err := os.WriteFile(docFile, body, 0o600); err != nil {
t.Fatal(err)
}
out, err := exec.Command(xmllint, "--noout", "--schema", wrapperFile, docFile).CombinedOutput()
if err != nil {
t.Fatalf("xmllint: %v\n%s", err, out)
}
}
func TestWindowsProfileDocument(t *testing.T) {
tc := writeSigningFiles(t)
signer, err := loadSigner(tc.keyFile, tc.certFile)
@@ -676,11 +732,12 @@ func TestWindowsProfileDocument(t *testing.T) {
)
for path, want := range map[string]string{
"Global/CarrierId": carrierID,
"Global/SubscriberId": "renner-unifi",
"Global/SubscriberId": "rennerunifi",
wlan + "name": "Renner UniFi",
wlan + "SSIDConfig/SSID/name": "Renner UniFi",
wlan + "Hotspot2/DomainName": "renner.uno",
sec + "authEncryption/authentication": "WPA2",
wlan + "Hotspot2/NAIRealm/name": "renner.uno",
sec + "authEncryption/authentication": "WPA3ENT",
sec + "authEncryption/encryption": "AES",
sec + "authEncryption/useOneX": "true",
sec + "OneX/authMode": "user",