diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index 1a58662adafa5..b372889e07bc9 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -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 @@ -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 { @@ -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; ''} @@ -1091,6 +1102,7 @@ in "hydra.example.com" = { forceSSL = true; enableACME = true; + strictTransportSecurity.enable = true; locations."/" = { proxyPass = "http://localhost:3000"; }; diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix index 24fcb101c910d..764a8844354d9 100644 --- a/nixos/modules/services/web-servers/nginx/vhost-options.nix +++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix @@ -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 = false; + 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;