Skip to content

Commit 9cde20b

Browse files
authored
Merge pull request #3512 from vyos/mergify/bp/sagitta/pr-3487
reverse-proxy: T6370: Set custom HTTP headers in reverse-proxy responses (backport #3487)
2 parents 371517c + 6c25888 commit 9cde20b

File tree

5 files changed

+68
-0
lines changed

5 files changed

+68
-0
lines changed

data/templates/load-balancing/haproxy.cfg.j2

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ frontend {{ front }}
8181
{% endif %}
8282
{% endfor %}
8383
{% endif %}
84+
{% if front_config.http_response_headers is vyos_defined %}
85+
{% for header, header_config in front_config.http_response_headers.items() %}
86+
http-response set-header {{ header }} '{{ header_config['value'] }}'
87+
{% endfor %}
88+
{% endif %}
8489
{% endif %}
8590
{% if front_config.rule is vyos_defined %}
8691
{% for rule, rule_config in front_config.rule.items() %}
@@ -158,6 +163,11 @@ backend {{ back }}
158163
{% endif %}
159164
{% if back_config.mode is vyos_defined %}
160165
mode {{ back_config.mode }}
166+
{% if back_config.http_response_headers is vyos_defined %}
167+
{% for header, header_config in back_config.http_response_headers.items() %}
168+
http-response set-header {{ header }} '{{ header_config['value'] }}'
169+
{% endfor %}
170+
{% endif %}
161171
{% endif %}
162172
{% if back_config.rule is vyos_defined %}
163173
{% for rule, rule_config in back_config.rule.items() %}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!-- include start from haproxy/http-response-headers.xml.i -->
2+
<tagNode name="http-response-headers">
3+
<properties>
4+
<help>Headers to include in HTTP response</help>
5+
<valueHelp>
6+
<format>txt</format>
7+
<description>HTTP header name</description>
8+
</valueHelp>
9+
<constraint>
10+
<regex>[-a-zA-Z]+</regex>
11+
</constraint>
12+
<constraintErrorMessage>Header names must only include alphabetical characters and hyphens</constraintErrorMessage>
13+
</properties>
14+
<children>
15+
<leafNode name="value">
16+
<properties>
17+
<help>HTTP header value</help>
18+
<valueHelp>
19+
<format>txt</format>
20+
<description>HTTP header value</description>
21+
</valueHelp>
22+
<constraint>
23+
<regex>[[:ascii:]]{1,256}</regex>
24+
</constraint>
25+
</properties>
26+
</leafNode>
27+
</children>
28+
</tagNode>
29+
<!-- include end -->

interface-definitions/load-balancing_reverse-proxy.xml.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <include/port-number.xml.i>
4040
#include <include/haproxy/rule-frontend.xml.i>
4141
#include <include/haproxy/tcp-request.xml.i>
42+
#include <include/haproxy/http-response-headers.xml.i>
4243
<leafNode name="redirect-http-to-https">
4344
<properties>
4445
<help>Redirect HTTP to HTTPS</help>
@@ -90,6 +91,7 @@
9091
</leafNode>
9192
#include <include/generic-description.xml.i>
9293
#include <include/haproxy/mode.xml.i>
94+
#include <include/haproxy/http-response-headers.xml.i>
9395
<node name="parameters">
9496
<properties>
9597
<help>Backend parameters</help>

smoketest/scripts/cli/test_load-balancing_reverse-proxy.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,5 +385,26 @@ def test_06_lb_reverse_proxy_tcp_mode(self):
385385
self.assertIn(f'mode {mode}', config)
386386
self.assertIn(f'server {bk_name} {bk_server}:{bk_server_port}', config)
387387

388+
def test_07_lb_reverse_proxy_http_response_headers(self):
389+
# Setup base
390+
self.configure_pki()
391+
self.base_config()
392+
393+
# Set example headers in both frontend and backend
394+
self.cli_set(base_path + ['service', 'https_front', 'http-response-headers', 'Cache-Control', 'value', 'max-age=604800'])
395+
self.cli_set(base_path + ['backend', 'bk-01', 'http-response-headers', 'Proxy-Backend-ID', 'value', 'bk-01'])
396+
self.cli_commit()
397+
398+
# Test headers are present in generated configuration file
399+
config = read_file(HAPROXY_CONF)
400+
self.assertIn('http-response set-header Cache-Control \'max-age=604800\'', config)
401+
self.assertIn('http-response set-header Proxy-Backend-ID \'bk-01\'', config)
402+
403+
# Test setting alongside modes other than http is blocked by validation conditions
404+
self.cli_set(base_path + ['service', 'https_front', 'mode', 'tcp'])
405+
with self.assertRaises(ConfigSessionError) as e:
406+
self.cli_commit()
407+
408+
388409
if __name__ == '__main__':
389410
unittest.main(verbosity=2)

src/conf_mode/load-balancing_reverse-proxy.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ def verify(lb):
8888
if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf):
8989
raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"')
9090

91+
# Check if http-response-headers are configured in any frontend/backend where mode != http
92+
for group in ['service', 'backend']:
93+
for config_name, config in lb[group].items():
94+
if 'http_response_headers' in config and ('mode' not in config or config['mode'] != 'http'):
95+
raise ConfigError(f'{group} {config_name} must be set to http mode to use http_response_headers!')
96+
9197
if 'ssl' in back_config:
9298
if {'no_verify', 'ca_certificate'} <= set(back_config['ssl']):
9399
raise ConfigError(f'backend {back} cannot have both ssl options no-verify and ca-certificate set!')

0 commit comments

Comments
 (0)