net/haproxy: Template generation fails when model serializes list fields as dict instead of string

Aperta
#5,690 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
55/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
python

Direzione di ricerca

Iniziate dal comando che fallisce configctl template reload OPNsense/HAProxy ed esaminate net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf e sslCerts.yaml, insieme a template_helpers.py. Tracciate i campi elencati sia nella forma string sia nella forma dict, quindi verificate che il rendering di frontend, certificato, ACL/action e backend abbia esito positivo e che HAProxy si avvii senza errori di sintassi.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

[HAProxy] Template generation crashes when model serializes list fields as dict instead of string

Important notices

Describe the bug
Applying any HAProxy frontend configuration fails with
Configuration exception — Template generation failed for internal service "haproxy". See backend log for details.
(and a red "HAProxy configtest found critical errors" banner in the GUI).

Root cause: the HAProxy Jinja2 templates (haproxy.conf, sslCerts.yaml)
assume several model fields are CSV strings, but on os-haproxy 5.1 /
OPNsense 26.7.3_8 the model serializes those same fields as dicts
(OrderedDict). The template then calls .split(",") / .name on a dict
and aborts rendering — so HAProxy can never be (re)started from the
plugin-generated config.

Last known working version: unknown — the trigger depends on how the
model serializes list-typed fields; on setups where it still emits
strings the bug stays hidden.

To Reproduce

  1. OPNsense 26.7.3_8, install os-haproxy 5.1, enable the plugin.
  2. Go to Services → HAProxy → Frontend (https://<firewall>/ui/haproxy/frontend).
  3. Click Add, set bind to 0.0.0.0:443, enable SSL, select a certificate, set a default backend, and add an action (use_backend on an ACL) plus the matching ACL.
  4. Click Apply (or trigger it via configctl template reload OPNsense/HAProxy).
  5. See error: red banner + configd.log shows a chain of Jinja
    UndefinedError / TypeError while rendering
    OPNsense/HAProxy/haproxy.conf and OPNsense/HAProxy/sslCerts.yaml.

Expected behavior
Apply should render a valid haproxy.conf and (re)start HAProxy
without manual template patching, regardless of whether the model
serializes list fields as string or dict.

Screenshots
n/a

Relevant log files
configd.log (excerpt, plugin version 5.1 / OPNsense 26.7.3_8):

error generating template OPNsense/HAProxy : ...
  File ".../OPNsense/HAProxy/haproxy.conf", line 2468, in top-level template code
    {% for bind in frontend.bind.split(",") %}
jinja2.exceptions.UndefinedError: 'collections.OrderedDict object' has no attribute 'split'

error generating template OPNsense/HAProxy : ...
  File ".../OPNsense/HAProxy/sslCerts.yaml", line 37, in top-level template code
    {% for cert in frontend.get('ssl_certificates', '').split(',') + ... %}
TypeError: can only concatenate list (not "odict_keys") to list

Confirmed failing spots (all in
/usr/local/opnsense/service/templates/OPNsense/HAProxy/):

  • haproxy.conf ~2468: frontend.bind.split(",") — bind is
    {"0.0.0.0:443": {"value": "0.0.0.0:443", "selected": 1}}
  • haproxy.conf AclsAndActions macro (~88, ~96, ~1757):
    linkedData.split(","), action_data.linkedAcls.split(","),
    action_data.name — dicts, not strings/objects
  • haproxy.conf ~2517: option {{frontend.connectionBehaviour}} —
    renders a bare option (empty) when the field is empty → hard
    HAProxy syntax error
  • haproxy.conf ~2811: backend.linkedServers.split(",") — dict →
    no server lines emitted → HTTP 502 on every route
  • sslCerts.yaml ~37: frontend.get('ssl_certificates','').split(',')
    — dict → no certlist generated → frontend has no listener

Upstream confirmation
The exact same patterns are present in current master of the plugin:

  • net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf
    ({% for action in linkedData.split(",") %},
    action_data.linkedAcls.split(","),
    backend.linkedServers.split(","))
  • net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/sslCerts.yaml
    (frontend.get('ssl_certificates', '').split(','))

So the templates assume string-typed list fields. The bug only surfaces
when the model serializes those fields as dict (os-haproxy 5.1 /
OPNsense 26.7.3_8). On setups where the model still emits strings the
failure is invisible — which is likely why prior reports were
auto-triaged as low-priority.

Workaround (tested locally — not a PR)

  1. template_helpers.py — make getUUID() return a dict subclass that
    supports both obj['key'] and obj.key:
    class _DotDict(dict):
        def __getattr__(self, name):
            if name in self: return self[name]
            raise AttributeError(name)
        def __getitem__(self, key):
            v = dict.__getitem__(self, key)
            if isinstance(v, dict) and not isinstance(v, _DotDict): return _DotDict(v)
            return v
    
    and return _DotDict(self._template_in_data['__uuid__'].get(uuid, {}))
    inside getUUID().
  2. haproxy.conf — guard the .split(",") calls so they accept a dict:
    {% for x in (field.keys() if field is mapping else field.split(",")) %}
    (applied to frontend.bind, backend.linkedServers,
    action_data.linkedAcls, frontend_data.bind).
  3. haproxy.conf — guard connectionBehaviour:
    {% if frontend.connectionBehaviour|default("") != "" %} option {{frontend.connectionBehaviour}}{% endif %}
  4. sslCerts.yaml — replace the .split(',') on ssl_certificates /
    ssl_default_certificate with a for-loop that iterates dict keys when
    the value is a mapping.

After these, configctl template reload OPNsense/HAProxy returns OK
and HAProxy starts from the plugin-generated config with all backends up.

Suggested proper fix
Add a small Jinja filter (e.g. tolist) in the template engine /
helpers that normalizes both shapes — string CSV and dict — into a list,
and use it wherever the templates currently call .split(",") on a model
field. That makes the templates robust regardless of how the model
serializes list-typed fields.

Additional context
This report was drafted with assistance from an AI tool (Hermes / Nous
Research, model tencent/hy3) based on direct inspection of the failing
template render and a comparison with the plugin master branch, as
required by CONTRIBUTING.md.

Environment

  • OPNsense 26.7.3_8 (amd64)
  • os-haproxy plugin: 5.1
  • haproxy: 3.2.22
  • Python: 3.13.15
  • Jinja2: 3.1.6
  • FreeBSD: 15.1-RELEASE-p3
Lingua principale
PHP
Stelle
1.2k
Fork
896
Merge medio
3g 15h
PR unite (30g)
11

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di opnsense/plugins

Tutte le issue di opnsense/plugins

Issue simili

Altre issue su PHP

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.