Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos/nginx: add strictTransportSecurity options #350302

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions nixos/modules/services/web-servers/nginx/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,11 @@ let
${optionalString (cfg.sslCiphers != null) "ssl_ciphers ${cfg.sslCiphers};"}
${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"}

${optionalString cfg.recommendedTlsSettings ''
# Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate

${optionalString cfg.recommendedTlsSettings
# Everything from
# https://ssl-config.mozilla.org/#server=nginx&config=intermediate that
# isn't already handled by some other option.
''
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
# Breaks forward secrecy: https://github.com/mozilla/server-side-tls/issues/135
Expand Down Expand Up @@ -374,6 +376,12 @@ let
''}
'';

hstsValue =
concatStringsSep "; " (
[ "max-age=${toString vhost.strictTransportSecurity.seconds}" ]
++ optional vhost.strictTransportSecurity.includeSubdomains "includeSubdomains"
++ optional vhost.strictTransportSecurity.preload "preload"
);
in ''
${optionalString vhost.forceSSL ''
server {
Expand Down Expand Up @@ -408,6 +416,9 @@ let
${optionalString vhost.rejectSSL ''
ssl_reject_handshake on;
''}
${optionalString vhost.strictTransportSecurity.enable ''
add_header Strict-Transport-Security "${hstsValue}" always;
''}
${optionalString (hasSSL && vhost.kTLS) ''
ssl_conf_command Options KTLS;
''}
Expand Down Expand Up @@ -1091,6 +1102,7 @@ in
"hydra.example.com" = {
forceSSL = true;
enableACME = true;
strictTransportSecurity.enable = true;
locations."/" = {
proxyPass = "http://localhost:3000";
};
Expand Down
80 changes: 80 additions & 0 deletions nixos/modules/services/web-servers/nginx/vhost-options.nix
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,86 @@ with lib;
description = "Path to root SSL certificate for stapling and client certificates.";
};

# Since this is just an add_header, in principle it could be configured at
# the location level, but in practice I can't imagine that often being what
# people want, and they can always use `extraConfig` if necessary.
strictTransportSecurity = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable HTTP Strict Transport Security (HSTS). This sends
the header "strict-transport-security: max-age=63072000" on all
responses, which tells clients to refuse to use an insecure HTTP
connection for this host for the next 63072000 seconds (approximately
2 years), or for the duration specified by
`strictTransportSecurity.seconds` if set. This helps protects against
man-in-the-middle attacks that e.g. block HTTPS connections in the
hope that the client will fall back to an insecure HTTP connection,
which can be intercepted and modified.
The strict-transport-security header is (sent but) ignored on HTTP
connections, so this setting will only take effect if your website
actually is available over HTTPS.
HSTS only protects clients who have managed to connect at least once
without the man in the middle in the last two years: see also
`strictTransportSecurity.preload` for how to protect even the very
first connection.
HSTS is recommended in cases where you can reliably serve HTTPS, but
very annoying if you temporarily set it up but then want to use HTTP
again, so it is not enabled by default.
'';
};

seconds = mkOption {
type = types.int;
default = 63072000;
description = ''
If HTTP Strict Transport Security (HSTS) is enabled by
`strictTransportSecurity.enable`, this is how long the browser is
instructed to retain the setting, in seconds. 2 years (63072000 seconds)
is recommended, but if you're introducing HSTS for the first time on an
existing site you may want to ramp up the value gradually, or set it to
0 to tell clients to drop their HSTS config for this domain.
'';
};

includeSubdomains = mkOption {
type = types.bool;
default = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure this should be on by default, it is a footgun unless you are really prepared. Lots of cases of random sub‐subdomains with internal test deployments or services that aren’t ready for it.

(Haven’t done a thorough review of the rest of the PR yet.)

Copy link
Author

@bmillwood bmillwood Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, done :)

tbh my attitude was "enableACME is so easy these days" but yeah I guess non-public uses have the potential to be trickier

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the problem is that it‘s easy if you have one single NixOS machine serving your entire domain hierarchy, and it‘s easy if you have a trillion machines that are all behind consistent HTTPS frontend stuff proxying everything to your highly‐structured web scale enterprise backends, but there‘s a wide range of setups between those where you just don‘t have complete visibility and control into everything going on under *.mycorp.com. Intranet, legacy SaaS, a random appliance you can‘t update, … there’s a big gap between “this web server on this domain can reliably do HTTPS” and “absolutely nothing under this hierarchy exists that cannot reliably do HTTPS in every single context”, and with HSTS it’s easy to screw up painfully. Of course people should set it if they can.

Will try to take a more detailed look at this PR in the next few days if nobody beats me to it, feel free to ping if I forget.

description = ''
If HTTP Strict Transport Security (HSTS) is enabled by
`strictTransportSecurity.enable`, also specify the `includeSubdomains`
directive, so that e.g. if the vhost is https://www.website.example it
also sets the HSTS policy for https://sub.www.website.example. (Note
that "sibling" domains like https://mail.website.example are not
affected.)
'';
};

preload = mkOption {
type = types.bool;
# https://hstspreload.org/ specifically says not to enable this by
# default
default = false;
description = ''
If HTTP Strict Transport Security (HSTS) is enabled by
`strictTransportSecurity.enable`, also specify the (non-standard, but
widely recognised) `preload` directive. This allows submitting your
site to https://hstspreload.org/ to be added to the HSTS preload list
used by all major browsers, so that it is not necessary to visit your
site even once to enable HSTS.
Read the submission form before enabling this, since it describes the
constraints your config must satisfy, and has advice and warnings
about e.g. how slow adding or removing a domain from the preload list
can be.
'';
};
};

http2 = mkOption {
type = types.bool;
default = true;
Expand Down