The comment-block lint (added in 79dc8ca6) now trips on this block —
genuinely pre-existing, unrelated to that change, just newly caught.
Per the lint's own suggested remedy: relocated the full per-directive
reasoning plus both footguns (session-cache keying, the Host-header
clobber that can recurse a subrequest into itself) to a new
"Dialing another vhost by name" section in docs/gateway.md, and left
a short why + pointer comment in the source. No behavior change.
131 lines
5.1 KiB
Nix
131 lines
5.1 KiB
Nix
# 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 }
|
|
caBundle, # hive CA trust bundle on the host (root + intermediate)
|
|
}:
|
|
let
|
|
# nixos `services.nginx.virtualHosts.<name>` 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;
|
|
|
|
# Dial another service on this hive BY NAME over https, verified.
|
|
# nginx verifies nothing by default (`proxy_ssl_verify` is off), so a
|
|
# `proxy_pass https://…` without this is encrypted but unauthenticated
|
|
# — invisibly, it works and keeps working against any certificate at
|
|
# all. Full reasoning for every directive here (plus two real
|
|
# footguns — session-cache keying, and a `Host`-header clobber that
|
|
# can recurse a subrequest into itself) is in docs/gateway.md's
|
|
# "Dialing another vhost by name" section — read it before touching
|
|
# this.
|
|
verifiedProxyTo = name: ''
|
|
proxy_ssl_verify on;
|
|
proxy_ssl_verify_depth 3;
|
|
proxy_ssl_trusted_certificate ${caBundle};
|
|
proxy_ssl_name ${name};
|
|
proxy_ssl_server_name on;
|
|
proxy_set_header Host ${name};
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Server $hostname;
|
|
'';
|
|
|
|
# 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;
|
|
}
|