diff --git a/haproxy-operator/templates/haproxy_route.cfg.j2 b/haproxy-operator/templates/haproxy_route.cfg.j2 index 1ea93458..7c7bca2e 100644 --- a/haproxy-operator/templates/haproxy_route.cfg.j2 +++ b/haproxy-operator/templates/haproxy_route.cfg.j2 @@ -9,11 +9,15 @@ frontend haproxy {{ ddos_protection_rules() }} # Redirect HTTP to HTTPS - http-request redirect scheme https unless { ssl_fc } {% for acl in acls_for_allow_http %} || {{ acl }}{% endfor %} +{% for acl in acls_for_allow_http %} + acl is_allow_http {{ acl }} +{% endfor %} + http-request redirect scheme https unless { ssl_fc } {% if acls_for_allow_http %}|| is_allow_http{% endif %} {% if enable_hsts %} # Add HSTS header for HTTPS connections (only when HTTP is not allowed) - http-response set-header Strict-Transport-Security "max-age=2592000" if { ssl_fc } {% for acl in acls_for_allow_http %} !{{ acl }}{% endfor %} + http-response set-header Strict-Transport-Security "max-age=2592000" if { ssl_fc } {% if acls_for_allow_http %}!is_allow_http{% endif %} + {% endif %} {% for spoe_auth_info in spoe_auth_info_list %} diff --git a/haproxy-operator/tests/unit/test_hsts.py b/haproxy-operator/tests/unit/test_hsts.py index 5098404e..ce4147d6 100644 --- a/haproxy-operator/tests/unit/test_hsts.py +++ b/haproxy-operator/tests/unit/test_hsts.py @@ -96,13 +96,81 @@ def test_hsts_disabled_allow_http(monkeypatch: pytest.MonkeyPatch, certificates_ state, ) assert render_file_mock.call_count == 1 + haproxy_conf_contents = render_file_mock.call_args.args[1] + assert ( + "acl is_allow_http { req.hdr(host),field(1,:) -i haproxy.internal }" + in haproxy_conf_contents + ) render_file_mock.assert_any_call( pathlib.Path("/etc/haproxy/haproxy.cfg"), RegexMatcher( re.escape( - 'http-response set-header Strict-Transport-Security "max-age=2592000" if { ssl_fc } !{ req.hdr(host),field(1,:) -i haproxy.internal }\n' + 'http-response set-header Strict-Transport-Security "max-age=2592000" if { ssl_fc } !is_allow_http\n' ) ), ANY, ) assert out.unit_status.name == ops.testing.ActiveStatus.name + + +@pytest.mark.usefixtures("systemd_mock", "mocks_external_calls") +def test_redirect_without_allow_http_uses_named_acl( + monkeypatch: pytest.MonkeyPatch, certificates_integration +): + """ + arrange: Prepare a haproxy with haproxy_route without allow_http. + act: trigger relation changed. + assert: haproxy.conf uses inline ssl_fc for the redirect rule without is_allow_http ACL. + """ + render_file_mock = MagicMock() + monkeypatch.setattr("haproxy.render_file", render_file_mock) + haproxy_route_relation = build_haproxy_route_relation() + + ctx = ops.testing.Context(HAProxyCharm) + state = ops.testing.State( + relations=[certificates_integration, haproxy_route_relation], + ) + out = ctx.run( + ctx.on.relation_changed(haproxy_route_relation), + state, + ) + assert render_file_mock.call_count == 1 + haproxy_conf_contents = render_file_mock.call_args.args[1] + assert "acl is_allow_http" not in haproxy_conf_contents + assert "http-request redirect scheme https unless { ssl_fc }" in haproxy_conf_contents + assert out.unit_status.name == ops.testing.ActiveStatus.name + + +@pytest.mark.usefixtures("systemd_mock", "mocks_external_calls") +def test_redirect_allow_http_uses_named_acl( + monkeypatch: pytest.MonkeyPatch, certificates_integration +): + """ + arrange: Prepare a haproxy with haproxy_route with allow_http enabled. + act: trigger relation changed. + assert: haproxy.conf uses is_allow_http named ACL for both redirect and HSTS rules. + """ + render_file_mock = MagicMock() + monkeypatch.setattr("haproxy.render_file", render_file_mock) + haproxy_route_relation = build_haproxy_route_relation() + haproxy_route_relation.remote_app_data["allow_http"] = "true" + + ctx = ops.testing.Context(HAProxyCharm) + state = ops.testing.State( + relations=[certificates_integration, haproxy_route_relation], + ) + out = ctx.run( + ctx.on.relation_changed(haproxy_route_relation), + state, + ) + assert render_file_mock.call_count == 1 + haproxy_conf_contents = render_file_mock.call_args.args[1] + assert ( + "acl is_allow_http { req.hdr(host),field(1,:) -i haproxy.internal }" + in haproxy_conf_contents + ) + assert ( + "http-request redirect scheme https unless { ssl_fc } || is_allow_http" + in haproxy_conf_contents + ) + assert out.unit_status.name == ops.testing.ActiveStatus.name