net/haproxy: Template generation fails when model serializes list fields as dict instead of string
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 1.2k
- Forks
- 863
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 10
Description
[HAProxy] Template generation crashes when model serializes list fields as dict instead of string
Important notices
- I have read the contributing guide lines at https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md
- I have searched the existing issues, open and closed, and I'm convinced that mine is new.
- The title contains the plugin to which this issue belongs
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
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. - Click Apply (or trigger it via
configctl template reload OPNsense/HAProxy). - See error: red banner +
configd.logshows a chain of Jinja
UndefinedError/TypeErrorwhile rendering
OPNsense/HAProxy/haproxy.confandOPNsense/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(",")—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 bareoption(empty) when the field is empty → hard
HAProxy syntax errorhaproxy.conf~2811:backend.linkedServers.split(",")— dict →
noserverlines 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 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— makegetUUID()return a dict subclass that
supports bothobj['key']andobj.key:
andclass _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 vreturn _DotDict(self._template_in_data['__uuid__'].get(uuid, {}))
insidegetUUID().haproxy.conf— guard the.split(",")calls so they accept a dict:
{% for x in (field.keys() if field is mapping else field.split(",")) %}
(applied tofrontend.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 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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the failing configctl template reload OPNsense/HAProxy command and inspect net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf and sslCerts.yaml, along with template_helpers.py. Trace the listed fields in both string and dict forms, then verify that frontend, certificate, ACL/action, and backend rendering succeeds and HAProxy starts without syntax errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100