Commit b71cf56e authored by Łukasz Nowak's avatar Łukasz Nowak

caddy-frontend: Implement Strict-Transport-Security

parent 7287fbd8
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
# not need these here). # not need these here).
[template] [template]
filename = instance.cfg.in filename = instance.cfg.in
md5sum = 04015a7a552285984d091293ef573fb9 md5sum = de69a8c408ce4f228fc22eacb7e96657
[profile-common] [profile-common]
filename = instance-common.cfg.in filename = instance-common.cfg.in
...@@ -26,11 +26,11 @@ md5sum = a6a626fd1579fd1d4b80ea67433ca16a ...@@ -26,11 +26,11 @@ md5sum = a6a626fd1579fd1d4b80ea67433ca16a
[profile-caddy-replicate] [profile-caddy-replicate]
filename = instance-apache-replicate.cfg.in filename = instance-apache-replicate.cfg.in
md5sum = acd4ab3d3df5ce46ece6f58b126f1665 md5sum = 7cb8157d2b368ab3b281ea42f743eb9c
[profile-slave-list] [profile-slave-list]
_update_hash_filename_ = templates/apache-custom-slave-list.cfg.in _update_hash_filename_ = templates/apache-custom-slave-list.cfg.in
md5sum = c48c1be68d547540971f874e78e5c96d md5sum = 772c04c165fdae91299fd909e061f926
[profile-replicate-publish-slave-information] [profile-replicate-publish-slave-information]
_update_hash_filename_ = templates/replicate-publish-slave-information.cfg.in _update_hash_filename_ = templates/replicate-publish-slave-information.cfg.in
...@@ -46,7 +46,7 @@ md5sum = 88af61e7abbf30dc99a1a2526161128d ...@@ -46,7 +46,7 @@ md5sum = 88af61e7abbf30dc99a1a2526161128d
[template-default-slave-virtualhost] [template-default-slave-virtualhost]
_update_hash_filename_ = templates/default-virtualhost.conf.in _update_hash_filename_ = templates/default-virtualhost.conf.in
md5sum = 1eb9f415229aa74de83f6d8660cac5a8 md5sum = a0ae858a3db8825c22d33d323392f588
[template-backend-haproxy-configuration] [template-backend-haproxy-configuration]
_update_hash_filename_ = templates/backend-haproxy.cfg.in _update_hash_filename_ = templates/backend-haproxy.cfg.in
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
'ciphers', 'ciphers',
'request-timeout', 'request-timeout',
'authenticate-to-backend', 'authenticate-to-backend',
'strict-transport-security',
] ]
%} %}
{% set aikc_enabled = slapparameter_dict.get('automatic-internal-kedifa-caucase-csr', 'true').lower() in TRUE_VALUES %} {% set aikc_enabled = slapparameter_dict.get('automatic-internal-kedifa-caucase-csr', 'true').lower() in TRUE_VALUES %}
...@@ -167,6 +168,11 @@ context = ...@@ -167,6 +168,11 @@ context =
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{# Check strict-transport-security #}
{% set strict_transport_security = (slave.get('strict-transport-security') or '0') | int(false) %}
{% if strict_transport_security is false or strict_transport_security < 0 %}
{% do slave_error_list.append('Wrong strict-transport-security %s' % (slave.get('strict-transport-security'),)) %}
{% endif %}
{% set custom_domain = slave.get('custom_domain') %} {% set custom_domain = slave.get('custom_domain') %}
{% if custom_domain and custom_domain in used_host_list %} {% if custom_domain and custom_domain in used_host_list %}
{% do slave_error_list.append('custom_domain %r clashes' % (custom_domain,)) %} {% do slave_error_list.append('custom_domain %r clashes' % (custom_domain,)) %}
......
...@@ -107,6 +107,12 @@ ...@@ -107,6 +107,12 @@
], ],
"title": "Authenticate to backend", "title": "Authenticate to backend",
"type": "string" "type": "string"
},
"strict-transport-security": {
"title": "Strict Transport Security",
"description": "Enables Strict Transport Security (HSTS) on the slave, the default 0 results with option disabled. More information: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security",
"default": "0",
"type": "integer"
} }
}, },
"title": "Input Parameters", "title": "Input Parameters",
......
...@@ -285,6 +285,32 @@ ...@@ -285,6 +285,32 @@
"description": "Amount of bad responses from the backend to consider it down.", "description": "Amount of bad responses from the backend to consider it down.",
"default": "1", "default": "1",
"type": "integer" "type": "integer"
},
"strict-transport-security": {
"title": "Strict Transport Security",
"description": "Enables Strict Transport Security (HSTS) on the slave, the default 0 results with option disabled. More information: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security",
"default": "0",
"type": "integer"
},
"strict-transport-security-sub-domains": {
"title": "Strict Transport Security Sub Domains",
"description": "Configures Strict Transport Security for sub domains.",
"enum": [
"false",
"true"
],
"type": "string",
"default": "false"
},
"strict-transport-security-preload": {
"title": "Strict Transport Security Preload",
"description": "Configures Strict Transport Security preload mechanism.",
"enum": [
"false",
"true"
],
"type": "string",
"default": "false"
} }
}, },
"title": "Input Parameters", "title": "Input Parameters",
......
...@@ -106,3 +106,4 @@ configuration.backend-haproxy-statistic-port = 21444 ...@@ -106,3 +106,4 @@ configuration.backend-haproxy-statistic-port = 21444
configuration.authenticate-to-backend = False configuration.authenticate-to-backend = False
configuration.rotate-num = 4000 configuration.rotate-num = 4000
configuration.slave-introspection-https-port = 22443 configuration.slave-introspection-https-port = 22443
configuration.strict-transport-security = 0
...@@ -62,7 +62,7 @@ context = ...@@ -62,7 +62,7 @@ context =
{%- for key in ['https-only', 'websocket-transparent'] %} {%- for key in ['https-only', 'websocket-transparent'] %}
{%- do slave_instance.__setitem__(key, ('' ~ slave_instance.get(key, 'true')).lower() in TRUE_VALUES) %} {%- do slave_instance.__setitem__(key, ('' ~ slave_instance.get(key, 'true')).lower() in TRUE_VALUES) %}
{%- endfor %} {%- endfor %}
{%- for key in ['enable_cache', 'disable-no-cache-request', 'disable-via-header', 'prefer-gzip-encoding-to-backend'] %} {%- for key in ['enable_cache', 'disable-no-cache-request', 'disable-via-header', 'prefer-gzip-encoding-to-backend', 'strict-transport-security-sub-domains', 'strict-transport-security-preload'] %}
{%- do slave_instance.__setitem__(key, ('' ~ slave_instance.get(key, 'false')).lower() in TRUE_VALUES) %} {%- do slave_instance.__setitem__(key, ('' ~ slave_instance.get(key, 'false')).lower() in TRUE_VALUES) %}
{%- endfor %} {%- endfor %}
{%- for key in ['disabled-cookie-list'] %} {%- for key in ['disabled-cookie-list'] %}
...@@ -128,11 +128,12 @@ context = ...@@ -128,11 +128,12 @@ context =
{%- do part_list.extend([slave_section_title]) %} {%- do part_list.extend([slave_section_title]) %}
{%- set slave_log_folder = '${logrotate-directory:logrotate-backup}/' + slave_reference + "-logs" %} {%- set slave_log_folder = '${logrotate-directory:logrotate-backup}/' + slave_reference + "-logs" %}
{#- Pass backend timeout values #} {#- Pass backend timeout values #}
{%- for key in ['backend-connect-timeout', 'backend-connect-retries', 'request-timeout', 'authenticate-to-backend'] %} {%- for key in ['backend-connect-timeout', 'backend-connect-retries', 'request-timeout', 'authenticate-to-backend', 'strict-transport-security'] %}
{%- if slave_instance.get(key, '') == '' %} {%- if slave_instance.get(key, '') == '' %}
{%- do slave_instance.__setitem__(key, configuration[key]) %} {%- do slave_instance.__setitem__(key, configuration[key]) %}
{%- endif %} {%- endif %}
{%- endfor %} {%- endfor %}
{%- do slave_instance.__setitem__('strict-transport-security', int(slave_instance['strict-transport-security'])) %}
{%- do slave_instance.__setitem__('authenticate-to-backend', ('' ~ slave_instance.get('authenticate-to-backend', '')).lower() in TRUE_VALUES) %} {%- do slave_instance.__setitem__('authenticate-to-backend', ('' ~ slave_instance.get('authenticate-to-backend', '')).lower() in TRUE_VALUES) %}
{#- Setup active check #} {#- Setup active check #}
{%- do slave_instance.__setitem__('backend-active-check', ('' ~ slave_instance.get('backend-active-check', '')).lower() in TRUE_VALUES) %} {%- do slave_instance.__setitem__('backend-active-check', ('' ~ slave_instance.get('backend-active-check', '')).lower() in TRUE_VALUES) %}
......
...@@ -18,6 +18,21 @@ ...@@ -18,6 +18,21 @@
try_interval 250ms try_interval 250ms
{%- endmacro %} {# proxy_header #} {%- endmacro %} {# proxy_header #}
{%- macro hsts_header(tls) %}
{%- if tls %}
{%- if slave_parameter['strict-transport-security'] > 0 %}
{%- set strict_transport_security = ['max-age=%i' % (slave_parameter['strict-transport-security'],)] %}
{%- if slave_parameter['strict-transport-security-sub-domains'] %}
{%- do strict_transport_security.append('; includeSubDomains') %}
{%- endif %}
{%- if slave_parameter['strict-transport-security-preload'] %}
{%- do strict_transport_security.append('; preload') %}
{%- endif %}
header_downstream Strict-Transport-Security "{{ ''.join(strict_transport_security) }}"
{%- endif %}
{%- endif %}
{%- endmacro %} {# hsts_header #}
{%- for tls in [True, False] %} {%- for tls in [True, False] %}
{%- if tls %} {%- if tls %}
{%- set backend_url = slave_parameter.get('backend-https-url', slave_parameter['backend-http-url']) %} {%- set backend_url = slave_parameter.get('backend-https-url', slave_parameter['backend-http-url']) %}
...@@ -82,6 +97,7 @@ ...@@ -82,6 +97,7 @@
# {{ proxy_comment }} # {{ proxy_comment }}
proxy "/{{ proxy_name }}" {{ backend_url }} { proxy "/{{ proxy_name }}" {{ backend_url }} {
{{ proxy_header() }} {{ proxy_header() }}
{{ hsts_header(tls) }}
{%- if proxy_name == 'prefer-gzip' %} {%- if proxy_name == 'prefer-gzip' %}
without /prefer-gzip without /prefer-gzip
header_upstream Accept-Encoding gzip header_upstream Accept-Encoding gzip
...@@ -147,6 +163,7 @@ ...@@ -147,6 +163,7 @@
{%- elif slave_parameter['type'] == 'notebook' %} {%- elif slave_parameter['type'] == 'notebook' %}
proxy / {{ backend_url }} { proxy / {{ backend_url }} {
{{ proxy_header() }} {{ proxy_header() }}
{{ hsts_header(tls) }}
transparent transparent
} }
rewrite { rewrite {
...@@ -155,6 +172,7 @@ ...@@ -155,6 +172,7 @@
} }
proxy /proxy/ {{ backend_url }} { proxy /proxy/ {{ backend_url }} {
{{ proxy_header() }} {{ proxy_header() }}
{{ hsts_header(tls) }}
transparent transparent
websocket websocket
without /proxy/ without /proxy/
...@@ -163,6 +181,7 @@ ...@@ -163,6 +181,7 @@
{%- if slave_parameter['websocket-path-list'] %} {%- if slave_parameter['websocket-path-list'] %}
proxy / {{ backend_url }} { proxy / {{ backend_url }} {
{{ proxy_header() }} {{ proxy_header() }}
{{ hsts_header(tls) }}
{%- if slave_parameter['websocket-transparent'] %} {%- if slave_parameter['websocket-transparent'] %}
transparent transparent
{%- else %} {%- else %}
...@@ -172,6 +191,7 @@ ...@@ -172,6 +191,7 @@
{%- for websocket_path in slave_parameter['websocket-path-list'] %} {%- for websocket_path in slave_parameter['websocket-path-list'] %}
proxy "/{{ websocket_path }}" {{ backend_url }} { proxy "/{{ websocket_path }}" {{ backend_url }} {
{{ proxy_header() }} {{ proxy_header() }}
{{ hsts_header(tls) }}
websocket websocket
{%- if slave_parameter['websocket-transparent'] %} {%- if slave_parameter['websocket-transparent'] %}
transparent transparent
...@@ -183,6 +203,7 @@ ...@@ -183,6 +203,7 @@
{%- else %} {%- else %}
proxy / {{ backend_url }} { proxy / {{ backend_url }} {
{{ proxy_header() }} {{ proxy_header() }}
{{ hsts_header(tls) }}
websocket websocket
{%- if slave_parameter['websocket-transparent'] %} {%- if slave_parameter['websocket-transparent'] %}
transparent transparent
...@@ -205,6 +226,7 @@ ...@@ -205,6 +226,7 @@
# {{ proxy_comment }} # {{ proxy_comment }}
proxy "/{{ proxy_name }}" {{ backend_url }} { proxy "/{{ proxy_name }}" {{ backend_url }} {
{{ proxy_header() }} {{ proxy_header() }}
{{ hsts_header(tls) }}
{%- if proxy_name == 'prefer-gzip' %} {%- if proxy_name == 'prefer-gzip' %}
without /prefer-gzip without /prefer-gzip
header_upstream Accept-Encoding gzip header_upstream Accept-Encoding gzip
......
...@@ -1247,18 +1247,26 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin): ...@@ -1247,18 +1247,26 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin):
'backend-connect-timeout': 10, 'backend-connect-timeout': 10,
'backend-connect-retries': 5, 'backend-connect-retries': 5,
'request-timeout': 15, 'request-timeout': 15,
'strict-transport-security': '200',
'strict-transport-security-sub-domains': True,
'strict-transport-security-preload': True,
}, },
'server-alias': { 'server-alias': {
'url': cls.backend_url, 'url': cls.backend_url,
'server-alias': 'alias1.example.com alias2.example.com', 'server-alias': 'alias1.example.com alias2.example.com',
'strict-transport-security': '200',
}, },
'server-alias-empty': { 'server-alias-empty': {
'url': cls.backend_url, 'url': cls.backend_url,
'server-alias': '', 'server-alias': '',
'strict-transport-security': '200',
'strict-transport-security-sub-domains': True,
}, },
'server-alias-wildcard': { 'server-alias-wildcard': {
'url': cls.backend_url, 'url': cls.backend_url,
'server-alias': '*.alias1.example.com', 'server-alias': '*.alias1.example.com',
'strict-transport-security': '200',
'strict-transport-security-preload': True,
}, },
'server-alias-duplicated': { 'server-alias-duplicated': {
'url': cls.backend_url, 'url': cls.backend_url,
...@@ -1828,6 +1836,7 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin): ...@@ -1828,6 +1836,7 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin):
self.certificate_pem, self.certificate_pem,
der2pem(result.peercert)) der2pem(result.peercert))
self.assertNotIn('Strict-Transport-Security', result.headers)
self.assertEqualResultJson(result, 'Path', '/test-path/deeper') self.assertEqualResultJson(result, 'Path', '/test-path/deeper')
try: try:
...@@ -2294,6 +2303,8 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin): ...@@ -2294,6 +2303,8 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin):
self.certificate_pem, self.certificate_pem,
der2pem(result.peercert)) der2pem(result.peercert))
self.assertEqual(
'max-age=200', result.headers['Strict-Transport-Security'])
self.assertEqualResultJson(result, 'Path', '/test-path/deeper') self.assertEqualResultJson(result, 'Path', '/test-path/deeper')
result = fakeHTTPSResult( result = fakeHTTPSResult(
...@@ -2304,12 +2315,16 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin): ...@@ -2304,12 +2315,16 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin):
self.certificate_pem, self.certificate_pem,
der2pem(result.peercert)) der2pem(result.peercert))
self.assertEqual(
'max-age=200', result.headers['Strict-Transport-Security'])
self.assertEqualResultJson(result, 'Path', '/test-path/deeper') self.assertEqualResultJson(result, 'Path', '/test-path/deeper')
result = fakeHTTPSResult( result = fakeHTTPSResult(
'alias2.example.com', 'alias2.example.com',
'test-path/deep/.././deeper') 'test-path/deep/.././deeper')
self.assertEqual(
'max-age=200', result.headers['Strict-Transport-Security'])
self.assertEqual( self.assertEqual(
self.certificate_pem, self.certificate_pem,
der2pem(result.peercert)) der2pem(result.peercert))
...@@ -2330,6 +2345,9 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin): ...@@ -2330,6 +2345,9 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin):
self.certificate_pem, self.certificate_pem,
der2pem(result.peercert)) der2pem(result.peercert))
self.assertEqual(
'max-age=200; includeSubDomains',
result.headers['Strict-Transport-Security'])
self.assertEqualResultJson(result, 'Path', '/test-path/deeper') self.assertEqualResultJson(result, 'Path', '/test-path/deeper')
try: try:
...@@ -2369,6 +2387,9 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin): ...@@ -2369,6 +2387,9 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin):
self.certificate_pem, self.certificate_pem,
der2pem(result.peercert)) der2pem(result.peercert))
self.assertEqual(
'max-age=200; preload',
result.headers['Strict-Transport-Security'])
self.assertEqualResultJson(result, 'Path', '/test-path') self.assertEqualResultJson(result, 'Path', '/test-path')
result = fakeHTTPSResult( result = fakeHTTPSResult(
...@@ -2378,6 +2399,9 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin): ...@@ -2378,6 +2399,9 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin):
self.certificate_pem, self.certificate_pem,
der2pem(result.peercert)) der2pem(result.peercert))
self.assertEqual(
'max-age=200; preload',
result.headers['Strict-Transport-Security'])
self.assertEqualResultJson(result, 'Path', '/test-path') self.assertEqualResultJson(result, 'Path', '/test-path')
def test_server_alias_duplicated(self): def test_server_alias_duplicated(self):
...@@ -4621,6 +4645,10 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin): ...@@ -4621,6 +4645,10 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin):
self.certificate_pem, self.certificate_pem,
der2pem(result.peercert)) der2pem(result.peercert))
self.assertEqual(
'max-age=200; includeSubDomains; preload',
result.headers['Strict-Transport-Security'])
self.assertEqualResultJson(result, 'Path', '/https/test-path/deeper') self.assertEqualResultJson(result, 'Path', '/https/test-path/deeper')
result_http = fakeHTTPResult( result_http = fakeHTTPResult(
...@@ -4632,6 +4660,8 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin): ...@@ -4632,6 +4660,8 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin):
result_http.status_code result_http.status_code
) )
self.assertNotIn('Strict-Transport-Security', result_http.headers)
self.assertEqual( self.assertEqual(
'https://urlhttpsurl.example.com:%s/test-path/deeper' % (HTTP_PORT,), 'https://urlhttpsurl.example.com:%s/test-path/deeper' % (HTTP_PORT,),
result_http.headers['Location'] result_http.headers['Location']
...@@ -6983,6 +7013,7 @@ class TestPassedRequestParameter(HttpFrontendTestCase): ...@@ -6983,6 +7013,7 @@ class TestPassedRequestParameter(HttpFrontendTestCase):
'ciphers': 'ciphers', 'ciphers': 'ciphers',
'request-timeout': 100, 'request-timeout': 100,
'authenticate-to-backend': True, 'authenticate-to-backend': True,
'strict-transport-security': 200,
# specific parameters # specific parameters
'-frontend-config-1-ram-cache-size': '512K', '-frontend-config-1-ram-cache-size': '512K',
'-frontend-config-2-ram-cache-size': '256K', '-frontend-config-2-ram-cache-size': '256K',
...@@ -7080,7 +7111,8 @@ class TestPassedRequestParameter(HttpFrontendTestCase): ...@@ -7080,7 +7111,8 @@ class TestPassedRequestParameter(HttpFrontendTestCase):
u'ram-cache-size': u'512K', u'ram-cache-size': u'512K',
u're6st-verification-url': u're6st-verification-url', u're6st-verification-url': u're6st-verification-url',
u'request-timeout': u'100', u'request-timeout': u'100',
u'slave-kedifa-information': u'{}' u'slave-kedifa-information': u'{}',
u'strict-transport-security': u'200'
}, },
'caddy-frontend-2': { 'caddy-frontend-2': {
'X-software_release_url': self.frontend_2_sr, 'X-software_release_url': self.frontend_2_sr,
...@@ -7107,7 +7139,8 @@ class TestPassedRequestParameter(HttpFrontendTestCase): ...@@ -7107,7 +7139,8 @@ class TestPassedRequestParameter(HttpFrontendTestCase):
u'ram-cache-size': u'256K', u'ram-cache-size': u'256K',
u're6st-verification-url': u're6st-verification-url', u're6st-verification-url': u're6st-verification-url',
u'request-timeout': u'100', u'request-timeout': u'100',
u'slave-kedifa-information': u'{}' u'slave-kedifa-information': u'{}',
u'strict-transport-security': u'200'
}, },
'caddy-frontend-3': { 'caddy-frontend-3': {
'X-software_release_url': self.frontend_3_sr, 'X-software_release_url': self.frontend_3_sr,
...@@ -7133,7 +7166,8 @@ class TestPassedRequestParameter(HttpFrontendTestCase): ...@@ -7133,7 +7166,8 @@ class TestPassedRequestParameter(HttpFrontendTestCase):
u'port': u'11443', u'port': u'11443',
u're6st-verification-url': u're6st-verification-url', u're6st-verification-url': u're6st-verification-url',
u'request-timeout': u'100', u'request-timeout': u'100',
u'slave-kedifa-information': u'{}' u'slave-kedifa-information': u'{}',
u'strict-transport-security': u'200'
}, },
'kedifa': { 'kedifa': {
'X-software_release_url': self.kedifa_sr, 'X-software_release_url': self.kedifa_sr,
...@@ -7179,7 +7213,8 @@ class TestPassedRequestParameter(HttpFrontendTestCase): ...@@ -7179,7 +7213,8 @@ class TestPassedRequestParameter(HttpFrontendTestCase):
'request-timeout': '100', 'request-timeout': '100',
'root_instance_title': 'testing partition 0', 'root_instance_title': 'testing partition 0',
'slap_software_type': 'RootSoftwareInstance', 'slap_software_type': 'RootSoftwareInstance',
'slave_instance_list': [] 'slave_instance_list': [],
'strict-transport-security': '200'
} }
} }
self.assertEqual( self.assertEqual(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment