Skip to content

Commit bd4e2fa

Browse files
authored
Merge pull request #48 from hartwork/hsts-preload-off-by-default
`Caddyfile.generate`: Make HSTS preloading optional and disabled by default
2 parents 001c4fd + 3a7cb8d commit bd4e2fa

File tree

3 files changed

+40
-26
lines changed

3 files changed

+40
-26
lines changed

.github/workflows/expected-output.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
+example.org {
1313
+ import common
1414
+ reverse_proxy example-org:80 {
15-
+ header_down +Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
15+
+ header_down +Strict-Transport-Security "max-age=63072000; includeSubDomains"
1616
+ }
1717
+}
1818
+

Caddyfile.generate

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ from argparse import ArgumentParser
88
from collections import namedtuple
99
from configparser import ConfigParser, NoOptionError
1010
from contextlib import suppress
11+
from operator import attrgetter
1112
from tempfile import NamedTemporaryFile
1213
from textwrap import dedent
1314

@@ -19,17 +20,15 @@ class CaddyfileGenerator:
1920
"alias_domains",
2021
"backend_authority",
2122
"domain",
23+
"hsts_preload",
2224
],
2325
)
2426

2527
def __init__(self):
26-
self._redir_target_of = {}
27-
self._backend_of = {}
28+
self._sites = []
2829

2930
def add(self, site):
30-
self._backend_of[site.domain] = site.backend_authority
31-
for domain in site.alias_domains:
32-
self._redir_target_of[domain] = site.domain
31+
self._sites.append(site)
3332

3433
def write_to(self, fp):
3534
print(
@@ -44,33 +43,40 @@ class CaddyfileGenerator:
4443
file=fp,
4544
)
4645

47-
for domain, backend_authority in sorted(self._backend_of.items()):
48-
if backend_authority is None:
49-
continue
46+
sites_with_backends: list[self.Site] = [
47+
s for s in self._sites if s.backend_authority is not None
48+
]
49+
sites_with_alias_domains: list[self.Site] = [s for s in self._sites if s.alias_domains]
50+
51+
for site in sorted(sites_with_backends, key=attrgetter("domain")):
52+
hsts_header_value = "max-age=63072000; includeSubDomains"
53+
if site.hsts_preload:
54+
hsts_header_value += "; preload"
5055

5156
print(
5257
dedent("""
5358
%s {
5459
import common
5560
reverse_proxy %s {
56-
header_down +Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
61+
header_down +Strict-Transport-Security "%s"
5762
}
58-
}""") # noqa: E501
59-
% (domain, backend_authority),
60-
file=fp,
61-
)
62-
63-
for source_domain, target_domain in sorted(self._redir_target_of.items()):
64-
print(
65-
dedent("""
66-
%s {
67-
import common
68-
redir https://%s{uri}
6963
}""")
70-
% (source_domain, target_domain),
64+
% (site.domain, site.backend_authority, hsts_header_value),
7165
file=fp,
7266
)
7367

68+
for site in sorted(sites_with_alias_domains, key=attrgetter("domain")):
69+
for alias_domain in sorted(site.alias_domains):
70+
print(
71+
dedent("""
72+
%s {
73+
import common
74+
redir https://%s{uri}
75+
}""")
76+
% (alias_domain, site.domain),
77+
file=fp,
78+
)
79+
7480

7581
def run(options):
7682
config = ConfigParser()
@@ -89,7 +95,9 @@ def run(options):
8995
except NoOptionError:
9096
backend_authority = None
9197

92-
site = CaddyfileGenerator.Site(alias_domains, backend_authority, domain)
98+
hsts_preload = config.getboolean(domain, "hsts_preload", fallback=False)
99+
100+
site = CaddyfileGenerator.Site(alias_domains, backend_authority, domain, hsts_preload)
93101
caddyfile.add(site)
94102

95103
with NamedTemporaryFile() as temp_file:

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ that I build upon prior to switching to
4848

4949
# How to write the `sites.cfg` file
5050

51-
The format is rather simple and has three options only.
51+
The format is rather simple and has four options only.
5252
Let's look at this example:
5353

5454
[example.org]
@@ -64,7 +64,13 @@ content. Here, `example-org` is the name of the Docker container that
6464
Docker DNS will let us access because we made both containers join external
6565
network `ssl-reverse-proxy` in their `docker-compose.yml` files.
6666
`aliases` is an optional list of domain names to have both HTTP and HTTPS
67-
redirect to master domain `example.org`. That's it.
67+
redirect to master domain `example.org`.
68+
Adding `hsts_preload = true` is optional, defaults to false, and extends the
69+
[HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security)
70+
response headers to
71+
[unlock addition to the browser preload list](https://hstspreload.org/#submission-requirements)
72+
at [https://hstspreload.org/](https://hstspreload.org/).
73+
That's it.
6874

6975
The `Caddyfile` generated from that very `sites.cfg` would read:
7076

@@ -79,7 +85,7 @@ The `Caddyfile` generated from that very `sites.cfg` would read:
7985
example.org {
8086
import common
8187
reverse_proxy example-org:80 {
82-
header_down +Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
88+
header_down +Strict-Transport-Security "max-age=63072000; includeSubDomains"
8389
}
8490
}
8591

0 commit comments

Comments
 (0)