diff --git a/nix/modules/hive-gateway.nix b/nix/modules/hive-gateway.nix index 6f2696ee..b5065a90 100644 --- a/nix/modules/hive-gateway.nix +++ b/nix/modules/hive-gateway.nix @@ -331,48 +331,6 @@ in }; }; - hsts = { - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Add `Strict-Transport-Security` to all gateway vhosts. - - Disabled by default: HSTS pins HTTPS in the browser's HSTS - preload list; enabling it on a deployment that later loses TLS - will lock browsers out until the max-age expires. Only enable - this when you are certain TLS is permanent. - - Requires TLS to be active (`selfSignedTls = true`, a `tls.certDir`, - or `tls.acme.enable = true`). Enabling HSTS without TLS is - technically harmless (browsers ignore the header over plain HTTP) - but is almost certainly a misconfiguration. - ''; - }; - - maxAge = lib.mkOption { - type = lib.types.ints.positive; - default = 31536000; - example = 86400; - description = '' - Value for the `max-age` directive in seconds. - Default: 31536000 (1 year), which is the value required for - HSTS preload list submission. Use a shorter value (e.g. 86400) - while testing so browsers forget the pin quickly. - ''; - }; - - includeSubDomains = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - Whether to include `includeSubDomains` in the HSTS header. - Only disable this if the gateway host has sub-domains that - intentionally serve plain HTTP. - ''; - }; - }; - }; config = lib.mkIf cfg.enable { @@ -417,18 +375,6 @@ in Let's Encrypt needs a contact address for the ACME account. ''; } - { - assertion = - !cfg.hsts.enable - || cfg.selfSignedTls - || cfg.tls.certDir != null - || cfg.tls.acme.enable; - message = '' - services.hyperhive.gateway.hsts.enable = true requires TLS to be - configured (selfSignedTls, tls.certDir, or tls.acme.enable). HSTS - over plain HTTP is ignored by browsers and indicates a config error. - ''; - } ]; # Ensure bind-mount sources exist at host boot before the gateway @@ -498,9 +444,15 @@ in # - selfSignedTls=true: generated cert stored in persistent state dir. # - tls.certDir set: operator-provided cert bind-mounted at /run/hive-tls. tlsCert = - if cfg.tls.certDir != null then "/run/hive-tls/${cfg.tls.certName}" else "${tlsDir}/cert.pem"; + if cfg.tls.certDir != null then + "/run/hive-tls/${cfg.tls.certName}" + else + "${tlsDir}/cert.pem"; tlsKey = - if cfg.tls.certDir != null then "/run/hive-tls/${cfg.tls.keyName}" else "${tlsDir}/key.pem"; + if cfg.tls.certDir != null then + "/run/hive-tls/${cfg.tls.keyName}" + else + "${tlsDir}/key.pem"; # True when nginx should listen with TLS (any mode). hasTls = cfg.selfSignedTls || cfg.tls.certDir != null || cfg.tls.acme.enable; # Listen addresses every vhost shares. Plain http on `cfg.port` @@ -546,23 +498,6 @@ in publicPort = if hasTls then cfg.httpsPort else cfg.port; publicPortDefault = if hasTls then 443 else 80; publicPortSuffix = if publicPort == publicPortDefault then "" else ":${toString publicPort}"; - - # Security headers added at the server scope on every vhost. - # nginx's add_header inheritance rule: a location that defines its - # own add_header does NOT inherit the server-level ones, so API - # locations with CORS headers (e.g. /.well-known/matrix/client, - # /_matrix/) are unaffected. HTML-serving and proxy locations that - # carry no add_header of their own pick these up automatically. - hstsDirectives = lib.concatStringsSep "; " ( - [ "max-age=${toString cfg.hsts.maxAge}" ] - ++ lib.optional cfg.hsts.includeSubDomains "includeSubDomains" - ); - securityHeaders = '' - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; - ${lib.optionalString cfg.hsts.enable ''add_header Strict-Transport-Security "${hstsDirectives}" always;''} - ''; in { system.stateVersion = "26.05"; @@ -859,7 +794,7 @@ in # agents without a nixos-rebuild. nginx's longest-prefix- # match rule ensures `/agent//` from this file beats # the `/agent/` catch-all above. - extraConfig = securityHeaders + '' + extraConfig = '' include /run/hive-state/agents.conf; ''; }; @@ -873,7 +808,6 @@ in lib.optionalAttrs (forgeCfg.enable or false && forgeCfg.behindGateway or false) { "${forgeCfg.domain}" = vhostTls // { listen = vhostListen; - extraConfig = securityHeaders; locations."/" = { proxyPass = "http://127.0.0.1:${toString forgeCfg.httpPort}/"; proxyWebsockets = true; @@ -896,7 +830,6 @@ in lib.optionalAttrs (matrixCfg.enable && matrixCfg.gatewayHost != null) { "${matrixCfg.gatewayHost}" = vhostTls // { listen = vhostListen; - extraConfig = securityHeaders; locations = { "/_matrix/" = { proxyPass = "http://127.0.0.1:${toString matrixCfg.httpPort}"; @@ -1002,10 +935,10 @@ in }; networking.firewall = lib.mkIf cfg.openFirewall { - allowedTCPPorts = [ - cfg.port - ] - ++ lib.optional (cfg.selfSignedTls || cfg.tls.certDir != null || cfg.tls.acme.enable) cfg.httpsPort; + allowedTCPPorts = + [ cfg.port ] + ++ lib.optional (cfg.selfSignedTls || cfg.tls.certDir != null || cfg.tls.acme.enable) + cfg.httpsPort; }; # `/etc/hosts` entries for local dev — bare hive domain + any