Fix OPNsense config.xml root element mismatch

Real config.xml root is the lowercase <opnsense> element (the whole
firewall config); plugin/core model data like WireGuard lives nested
inside a separate, capitalized <OPNsense> child element. The parser
was matching the capitalized name as the document root, so every real
export failed with "expected element type but have ...".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VjwLYRA87o8m9a9zztgs3
This commit is contained in:
sysops
2026-07-24 00:14:14 +02:00
co-authored by Claude Sonnet 5
parent 388a8377cd
commit 3a89a3cb5c
+18 -6
View File
@@ -7,8 +7,13 @@
// for the two-step preview/commit flow that does the actual store writes. // for the two-step preview/commit flow that does the actual store writes.
// //
// Schema reference (verified against OPNsense core master, // Schema reference (verified against OPNsense core master,
// src/opnsense/mvc/app/models/OPNsense/Wireguard/{Server,Client}.xml): // src/opnsense/mvc/app/models/OPNsense/Wireguard/{Server,Client}.xml, and
// against real config.xml exports/fixtures, which nest plugin model data
// under a capitalized <OPNsense> element inside the lowercase <opnsense>
// root - the two are NOT the same element):
// //
// <opnsense>
// ...
// <OPNsense><wireguard> // <OPNsense><wireguard>
// <server><servers> // <server><servers>
// <server uuid="..."><enabled/><name/><instance/><pubkey/><privkey/> // <server uuid="..."><enabled/><name/><instance/><pubkey/><privkey/>
@@ -21,6 +26,8 @@
// </clients></client> // </clients></client>
// <general><enabled/></general> // <general><enabled/></general>
// </wireguard></OPNsense> // </wireguard></OPNsense>
// ...
// </opnsense>
package opnsense package opnsense
import ( import (
@@ -30,9 +37,13 @@ import (
"strings" "strings"
) )
// rawConfig mirrors the on-disk config.xml structure. // rawConfig mirrors the on-disk config.xml structure. The root element is
// the lowercase <opnsense> (the whole firewall config); plugin/core model
// data lives inside a capitalized <OPNsense> child element - the two are
// distinct tags, not a casing quirk of one.
type rawConfig struct { type rawConfig struct {
XMLName xml.Name `xml:"OPNsense"` XMLName xml.Name `xml:"opnsense"`
Ns struct {
Wireguard struct { Wireguard struct {
Server struct { Server struct {
Servers struct { Servers struct {
@@ -48,6 +59,7 @@ type rawConfig struct {
Enabled string `xml:"enabled"` Enabled string `xml:"enabled"`
} `xml:"general"` } `xml:"general"`
} `xml:"wireguard"` } `xml:"wireguard"`
} `xml:"OPNsense"`
} }
type rawServer struct { type rawServer struct {
@@ -97,13 +109,13 @@ func Parse(r io.Reader) (*ParsedConfig, error) {
if err := dec.Decode(&cfg); err != nil { if err := dec.Decode(&cfg); err != nil {
return nil, fmt.Errorf("could not parse config.xml: %w", err) return nil, fmt.Errorf("could not parse config.xml: %w", err)
} }
if cfg.XMLName.Local != "OPNsense" { if cfg.XMLName.Local != "opnsense" {
return nil, fmt.Errorf("not an OPNsense config.xml (unexpected root element %q)", cfg.XMLName.Local) return nil, fmt.Errorf("not an OPNsense config.xml (unexpected root element %q)", cfg.XMLName.Local)
} }
return &ParsedConfig{ return &ParsedConfig{
Servers: cfg.Wireguard.Server.Servers.Server, Servers: cfg.Ns.Wireguard.Server.Servers.Server,
Clients: cfg.Wireguard.Client.Clients.Client, Clients: cfg.Ns.Wireguard.Client.Clients.Client,
}, nil }, nil
} }