gateway: declare the gateway's own vhosts under gateway.vhosts
The catch-all, the dashboard and the hive-domain vhost were built in vhosts.nix with the listen set, TLS attrs and headers applied by hand. They are declared under services.hyperhive.gateway.vhosts now, so every vhost behind the gateway goes through declared-vhosts.nix. Two gateway-side rules cover what the catch-all needed: a vhost can set securityHeaders = false (the catch-all only returns 444), and `default` is rendered at mkDefault priority so an operator's own default vhost still wins. The matrix discovery locations are unchanged. The rendered nginx vhosts are unchanged.
This commit is contained in:
parent
d836bde888
commit
68e30957e2
6 changed files with 97 additions and 35 deletions
|
|
@ -32,6 +32,7 @@ let
|
|||
own = removeAttrs declared [
|
||||
"_module"
|
||||
"certificate"
|
||||
"securityHeaders"
|
||||
];
|
||||
base =
|
||||
if declared.certificate != null then
|
||||
|
|
@ -48,7 +49,13 @@ let
|
|||
// own
|
||||
// {
|
||||
listen = cfg.lib.listen;
|
||||
extraConfig = cfg.lib.securityHeaders + (own.extraConfig or "");
|
||||
extraConfig =
|
||||
lib.optionalString declared.securityHeaders cfg.lib.securityHeaders + (own.extraConfig or "");
|
||||
}
|
||||
// lib.optionalAttrs (own ? default) {
|
||||
# At `mkDefault` priority, so an operator can turn this vhost's
|
||||
# `default` off with a plain definition.
|
||||
default = lib.mkDefault own.default;
|
||||
};
|
||||
in
|
||||
{
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ let
|
|||
caBundle = "${config.services.hyperhive.deploy.hive-controller.tls.stateDir}/trust-bundle.pem";
|
||||
};
|
||||
|
||||
nginxTree = import ./vhosts.nix {
|
||||
ownVhosts = import ./vhosts.nix {
|
||||
gwLib = cfg.lib;
|
||||
inherit (config.services.hyperhive.swarm) dashboardDomain;
|
||||
autheliaDomain = config.services.hyperhive.swarm.authelia.domain;
|
||||
|
|
@ -145,6 +145,9 @@ in
|
|||
# file's `let` and are not option surface.
|
||||
services.hyperhive.gateway.lib = vhostLib;
|
||||
|
||||
# The gateway's own vhosts, rendered the way every service's are.
|
||||
services.hyperhive.gateway.vhosts = ownVhosts.vhosts;
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = !(cfg.tls.acme.enable && cfg.tls.certDir != null);
|
||||
|
|
@ -392,7 +395,6 @@ in
|
|||
recommendedTlsSettings = true;
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
inherit (nginxTree) virtualHosts;
|
||||
|
||||
# Route the error log through syslog so each line keeps its own
|
||||
# severity. The module default is `stderr`, and systemd files that
|
||||
|
|
|
|||
|
|
@ -142,6 +142,15 @@ in
|
|||
# Every other attribute is an nginx vhost option, passed through
|
||||
# to `services.nginx.virtualHosts.<name>` as written.
|
||||
freeformType = lib.types.attrsOf lib.types.anything;
|
||||
options.securityHeaders = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether the gateway puts its security headers ahead of this
|
||||
vhost's `extraConfig`. Off only for a vhost that answers no
|
||||
request with a response, such as the catch-all.
|
||||
'';
|
||||
};
|
||||
options.certificate = lib.mkOption {
|
||||
type = lib.types.nullOr (
|
||||
lib.types.submodule {
|
||||
|
|
@ -178,7 +187,10 @@ in
|
|||
- the TLS attributes for that name (`gateway.lib.tlsFor`, or
|
||||
`certificate` below), with `addSSL` dropped when the vhost sets
|
||||
`forceSSL`;
|
||||
- `securityHeaders`, placed ahead of the vhost's own `extraConfig`.
|
||||
- `securityHeaders`, placed ahead of the vhost's own `extraConfig`
|
||||
unless the vhost sets `securityHeaders = false`;
|
||||
- `default`, when a vhost sets it, at `mkDefault` priority, so an
|
||||
operator's own `default` can win.
|
||||
|
||||
A vhost that sets `listen`, `addSSL`, `onlySSL`, `enableACME`,
|
||||
`useACMEHost`, `sslCertificate` or `sslCertificateKey` itself fails
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
# nginx virtual-host tree for the **hive's own** gateway surface: the `_`
|
||||
# default server, which answers unmatched `Host` with a bare 444; the
|
||||
# dashboard's vhost (`swarm.dashboardDomain`), which carries the dashboard
|
||||
# behind authelia, per-agent routing and the swagger UI; and the vhost
|
||||
# named for the hive domain, which carries matrix discovery, the legacy
|
||||
# `<hive>/matrix/*` redirect and a redirect of everything else to the
|
||||
# dashboard's name.
|
||||
# The gateway's own vhosts, as `services.hyperhive.gateway.vhosts`
|
||||
# declarations (rendered by ./declared-vhosts.nix): the `_` default server,
|
||||
# which answers unmatched `Host` with a bare 444; the dashboard's vhost
|
||||
# (`swarm.dashboardDomain`), which carries the dashboard behind authelia,
|
||||
# per-agent routing and the swagger UI; and the vhost named for the hive
|
||||
# domain, which carries matrix discovery, the legacy `<hive>/matrix/*`
|
||||
# redirect and a redirect of everything else to the dashboard's name.
|
||||
#
|
||||
# Scope: a swarm service declares its own vhost and dns name in its own
|
||||
# module, as `services.hyperhive.gateway.vhosts.<name>` (rendered by
|
||||
# ./declared-vhosts.nix). That keeps a service's gateway surface next to
|
||||
# module, the same way. That keeps a service's gateway surface next to
|
||||
# the service, and keeps this file to the surface the hive itself serves.
|
||||
#
|
||||
# Pure function — called from ./default.nix with the outer-scope config
|
||||
# values as arguments; returns `{ virtualHosts }`.
|
||||
# values as arguments; returns `{ vhosts }`.
|
||||
{
|
||||
lib,
|
||||
cfg, # services.hyperhive.gateway
|
||||
|
|
@ -28,14 +27,10 @@
|
|||
gwLib, # `services.hyperhive.gateway.lib` — ./vhost-lib.nix's kit, via the option
|
||||
}:
|
||||
let
|
||||
# The kit's three members, bound to the names this file already used.
|
||||
# Read through `gwLib` (the published option) rather than importing
|
||||
# ./vhost-lib.nix directly: a service module declaring its own vhost
|
||||
# gets the same object, so "the forge vhost listens where the gateway
|
||||
# listens" is true by construction and not by review.
|
||||
# ./vhost-lib.nix directly, so the headers on the matrix discovery
|
||||
# locations are the ones every other vhost gets.
|
||||
inherit (gwLib) securityHeaders;
|
||||
vhostListen = gwLib.listen;
|
||||
vhostTlsFor = gwLib.tlsFor;
|
||||
|
||||
# Public-facing scheme + port-suffix for URLs the gateway
|
||||
# mints into responses (well-known JSON, the deprecated
|
||||
|
|
@ -238,7 +233,7 @@ let
|
|||
};
|
||||
in
|
||||
{
|
||||
virtualHosts = {
|
||||
vhosts = {
|
||||
# The catch-all, and now *only* a catch-all: anything whose `Host`
|
||||
# matches no vhost gets an immediate 444 (close without a response)
|
||||
# rather than being served the hive's dashboard.
|
||||
|
|
@ -247,21 +242,22 @@ in
|
|||
# so it can never match a request by name — it serves traffic solely
|
||||
# by being `default_server`.
|
||||
#
|
||||
# ⚠️ It still needs TLS attrs. It listens on the https port, so a
|
||||
# ⚠️ It still gets TLS attrs from the gateway. It listens on the https port, so a
|
||||
# client connecting by IP completes a TLS handshake *before* nginx
|
||||
# can look at `Host` and reject it; with no cert the vhost fails to
|
||||
# load. The certificate will not match what such a client asked for
|
||||
# — that is unavoidable and correct: nothing can present a valid
|
||||
# cert for a name the operator never issued one for.
|
||||
#
|
||||
# `mkDefault` per the operator: an operator with their own
|
||||
# The gateway renders `default` at `mkDefault` priority: an operator with their own
|
||||
# `default = true` vhost must be able to win without fighting
|
||||
# priorities. The assertion in ./default.nix catches the case where
|
||||
# they add one *without* turning this off, which nginx would
|
||||
# otherwise only report at runtime as a failed config test.
|
||||
"_" = (vhostTlsFor "_") // {
|
||||
listen = vhostListen;
|
||||
default = lib.mkDefault true;
|
||||
"_" = {
|
||||
default = true;
|
||||
# A response to attach headers to is exactly what this vhost refuses.
|
||||
securityHeaders = false;
|
||||
extraConfig = ''
|
||||
return 444;
|
||||
'';
|
||||
|
|
@ -281,11 +277,9 @@ in
|
|||
#
|
||||
# `forceSSL` for the reason ../swarm-ui.nix gives: authelia answers the
|
||||
# subrequest for an `http://` target with 400, which `auth_request`
|
||||
# cannot read. `removeAttrs` because nixos asserts on a vhost declaring
|
||||
# both.
|
||||
${dashboardDomain} = (builtins.removeAttrs (vhostTlsFor dashboardDomain) [ "addSSL" ]) // {
|
||||
# cannot read.
|
||||
${dashboardDomain} = {
|
||||
forceSSL = true;
|
||||
listen = vhostListen;
|
||||
locations = agentLocations // dashboardProxyLocation // swaggerUiLocations // autheliaLocation;
|
||||
# Per-agent location blocks, generated at runtime by
|
||||
# hive-c0re and written to /var/lib/hive-gateway/conf/agents.conf
|
||||
|
|
@ -295,7 +289,7 @@ in
|
|||
# agents without a nixos-rebuild. nginx's longest-prefix-
|
||||
# match rule ensures `/agent/<name>/` from this file beats
|
||||
# the `/agent/` catch-all above.
|
||||
extraConfig = securityHeaders + ''
|
||||
extraConfig = ''
|
||||
include /var/lib/hive-gateway/conf/agents.conf;
|
||||
'';
|
||||
};
|
||||
|
|
@ -303,8 +297,7 @@ in
|
|||
# The hive domain's own vhost: matrix discovery, which federation
|
||||
# reads off the server name, and a redirect of every other path to the
|
||||
# dashboard's name, so a bookmarked dashboard or agent link still lands.
|
||||
${hyperhiveDomain} = (vhostTlsFor hyperhiveDomain) // {
|
||||
listen = vhostListen;
|
||||
${hyperhiveDomain} = {
|
||||
locations =
|
||||
matrixRedirectLocations
|
||||
// wellKnownLocations
|
||||
|
|
@ -313,7 +306,6 @@ in
|
|||
return 301 ${publicScheme}://${dashboardDomain}${publicPortSuffix}$request_uri;
|
||||
'';
|
||||
};
|
||||
extraConfig = securityHeaders;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue