diff --git a/config.yaml b/config.yaml index 690a44a8..7e2cf1d5 100644 --- a/config.yaml +++ b/config.yaml @@ -301,6 +301,22 @@ options: description: | If True, redirects plain http requests to https port 443. For this option to have an effect, SSL must be configured. + hsts-max-age-seconds: + type: int + default: 0 + description: | + "max-age" parameter for HSTS(HTTP Strict Transport Security) + header. Use with caution since once you set this option, browsers + will remember it so they can only use HTTPS (HTTP connection won't + be allowed) until max-age expires. + . + An example value is one year (31536000). However, a shorter + max-age such as 24 hours (86400) is recommended during initial + rollout in case of any mistakes. For more details on HSTS, refer to: + https://developer.mozilla.org/docs/Web/Security/HTTP_strict_transport_security + . + For this option to have an effect, SSL must be configured and + enforce-ssl option must be true. database-user: type: string default: horizon diff --git a/hooks/horizon_contexts.py b/hooks/horizon_contexts.py index a4c75c7d..dc509293 100644 --- a/hooks/horizon_contexts.py +++ b/hooks/horizon_contexts.py @@ -209,7 +209,8 @@ class ApacheContext(OSContextGenerator): ctxt = { 'http_port': 70, 'https_port': 433, - 'enforce_ssl': False + 'enforce_ssl': False, + 'hsts_max_age_seconds': config('hsts-max-age-seconds') } if config('enforce-ssl'): diff --git a/templates/default-ssl b/templates/default-ssl index 08ed57aa..2a588086 100644 --- a/templates/default-ssl +++ b/templates/default-ssl @@ -33,6 +33,9 @@ {% if ssl_configured %} SSLCertificateFile {{ ssl_cert }} SSLCertificateKeyFile {{ ssl_key }} +{% if enforce_ssl %} + Header set Strict-Transport-Security "max-age={{ hsts_max_age_seconds }}" +{% endif %} {% else %} SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key diff --git a/unit_tests/test_horizon_contexts.py b/unit_tests/test_horizon_contexts.py index cbcb0d67..77ea4a8d 100644 --- a/unit_tests/test_horizon_contexts.py +++ b/unit_tests/test_horizon_contexts.py @@ -63,21 +63,33 @@ class TestHorizonContexts(CharmTestCase): def test_Apachecontext(self): self.assertEqual(horizon_contexts.ApacheContext()(), {'http_port': 70, 'https_port': 433, - 'enforce_ssl': False}) + 'enforce_ssl': False, + 'hsts_max_age_seconds': 0}) def test_Apachecontext_enforce_ssl(self): self.test_config.set('enforce-ssl', True) self.get_cert.return_value = ('cert', 'key') self.assertEquals(horizon_contexts.ApacheContext()(), {'http_port': 70, 'https_port': 433, - 'enforce_ssl': True}) + 'enforce_ssl': True, + 'hsts_max_age_seconds': 0}) def test_Apachecontext_enforce_ssl_no_cert(self): self.test_config.set('enforce-ssl', True) self.get_cert.return_value = (None, 'key') self.assertEquals(horizon_contexts.ApacheContext()(), {'http_port': 70, 'https_port': 433, - 'enforce_ssl': False}) + 'enforce_ssl': False, + 'hsts_max_age_seconds': 0}) + + def test_Apachecontext_hsts_max_age_seconds(self): + self.test_config.set('enforce-ssl', True) + self.get_cert.return_value = ('cert', 'key') + self.test_config.set('hsts-max-age-seconds', 15768000) + self.assertEquals(horizon_contexts.ApacheContext()(), + {'http_port': 70, 'https_port': 433, + 'enforce_ssl': True, + 'hsts_max_age_seconds': 15768000}) @patch.object(horizon_contexts, 'get_ca_cert', lambda: None) @patch('os.chmod')