[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
- OPNsense 26.7.3_8, install os-haproxy 5.1, enable the plugin.
- Go to Services → HAProxy → Frontend (
https://<firewall>/ui/haproxy/frontend).
- 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.
- Click Apply (or trigger it via
configctl template reload OPNsense/HAProxy).
- 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)
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().
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).
haproxy.conf — guard connectionBehaviour:
{% if frontend.connectionBehaviour|default("") != "" %} option {{frontend.connectionBehaviour}}{% endif %}
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
[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(",")/.nameon a dictand 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
https://<firewall>/ui/haproxy/frontend).bindto0.0.0.0:443, enable SSL, select a certificate, set a default backend, and add an action (use_backendon an ACL) plus the matching ACL.configctl template reload OPNsense/HAProxy).configd.logshows a chain of JinjaUndefinedError/TypeErrorwhile renderingOPNsense/HAProxy/haproxy.confandOPNsense/HAProxy/sslCerts.yaml.Expected behavior
Applyshould render a validhaproxy.confand (re)start HAProxywithout 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):
Confirmed failing spots (all in
/usr/local/opnsense/service/templates/OPNsense/HAProxy/):haproxy.conf~2468:frontend.bind.split(",")—bindis{"0.0.0.0:443": {"value": "0.0.0.0:443", "selected": 1}}haproxy.confAclsAndActionsmacro (~88, ~96, ~1757):linkedData.split(","),action_data.linkedAcls.split(","),action_data.name— dicts, not strings/objectshaproxy.conf~2517:option {{frontend.connectionBehaviour}}—renders a bare
option(empty) when the field is empty → hardHAProxy syntax error
haproxy.conf~2811:backend.linkedServers.split(",")— dict →no
serverlines emitted → HTTP 502 on every routesslCerts.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
masterof 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)
template_helpers.py— makegetUUID()return a dict subclass thatsupports both
obj['key']andobj.key:return _DotDict(self._template_in_data['__uuid__'].get(uuid, {}))inside
getUUID().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).haproxy.conf— guardconnectionBehaviour:{% if frontend.connectionBehaviour|default("") != "" %} option {{frontend.connectionBehaviour}}{% endif %}sslCerts.yaml— replace the.split(',')onssl_certificates/ssl_default_certificatewith a for-loop that iterates dict keys whenthe value is a mapping.
After these,
configctl template reload OPNsense/HAProxyreturnsOKand 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 modelfield. 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