# The gateway's vhost construction kit: the listen set, the per-name TLS # attrs, and the security headers every vhost in front of this gateway # needs. # # Split out of ./vhosts.nix so it has one home and two consumers. Today # only ./vhosts.nix reads it; it is published as # `services.hyperhive.gateway.lib` (see ./options.nix) so a service # module can declare its OWN vhost without the gateway having to know # that service by name. `vhosts.nix` reads the published value rather # than calling this file directly — one source, not a copy that agrees # by inspection. # # Pure function of the gateway's config + resolved cert paths; returns # an attrset, evaluates no options itself. Everything here is *gateway* # knowledge — which port pair to bind, which issuer covers a name — and # stays here even once the service vhosts move out to their own modules. { lib, cfg, # services.hyperhive.gateway tlsCert, tlsKey, svcCert, # swarm-services leaf, for names the hive CA cannot sign svcKey, swarmServiceDomains, # which names those are (../swarm.nix derives it) errorPages, # ./error-pages.nix: { notFound, unreachable, unauthorized, ssoUnavailable } }: let # nixos `services.nginx.virtualHosts.` ssl attrs for a vhost # covered by the hive's own cert. For ACME mode: `enableACME` + # `addSSL` — NixOS's ACME integration manages the cert lifecycle and # sets ssl_certificate automatically. For self-signed / certDir: # explicit cert paths. vhostTls = if cfg.tls.acme.enable then { addSSL = true; enableACME = true; } else { addSSL = true; sslCertificate = tlsCert; sslCertificateKey = tlsKey; }; hstsDirectives = lib.concatStringsSep "; " ( [ "max-age=${toString cfg.hsts.maxAge}" ] ++ lib.optional cfg.hsts.includeSubDomains "includeSubDomains" ); in { # The gateway always terminates TLS: self-signed is the implicit # floor when neither `tls.certDir` nor ACME is set, so there is no # http-only mode. Listen addresses every vhost shares — plain http # on `cfg.port` plus TLS on `cfg.httpsPort`. See `docs/gateway.md` # ("TLS modes"). listen = [ { addr = "0.0.0.0"; port = cfg.port; } { addr = "0.0.0.0"; port = cfg.httpsPort; ssl = true; } ]; # TLS attrs for one vhost, by name. A swarm service's name may sit # outside this hive's domain — and then the hive CA is # name-constrained out of it, so its vhost must serve the # swarm-services leaf instead. Everything else keeps the hive leaf. # # Only in self-signed mode: with ACME or an operator cert there is a # single issuer that already covers every name, and a second pair # would be a cert nobody asked for. tlsFor = host: if !cfg.tls.acme.enable && cfg.tls.certDir == null && builtins.elem host swarmServiceDomains then { addSSL = true; sslCertificate = svcCert; sslCertificateKey = svcKey; } else vhostTls; # 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. Any # location with its own add_header (e.g. CORS on /.well-known or # /_matrix/) must repeat the security headers explicitly — see those # locations in ./vhosts.nix. HTML-serving and proxy locations that # carry no add_header of their own pick these up from the server # scope automatically. 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;''} ''; # The gateway's styled error pages, re-exported so a service module # can point an `error_page` at one. Republished rather than imported # per module for the same reason as everything else in this kit: these # carry the hive's branding, and a service rendering its own would # drift from the rest of the gateway the first time the theme changes. inherit errorPages; }