feat(nix): serve swarm-service vhosts the swarm-services leaf
nginx already carried TLS per vhost, so this is a cert choice rather than a restructure: a vhost whose name this hive's CA cannot sign gets the swarm-services pair, and every other vhost keeps the hive pair. Which vhosts those are is not decided here. Both the sub-CA's name constraints and this choice read swarm.serviceDomains, so "a swarm service" means one thing in one place -- a vhost served a cert its issuer is constrained out of would fail at TLS, and the two lists drifting is the only way to get there. 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. The container import copies the pair only when the host issued one, and removes a stale copy otherwise: the leaf exists only where the swarm CA is autoconfigured, so absent is a normal state and a leftover from a host that stopped issuing it is not.
This commit is contained in:
parent
5a83c40dca
commit
11b8140981
2 changed files with 54 additions and 2 deletions
|
|
@ -15,6 +15,10 @@
|
|||
let
|
||||
cfg = config.services.hyperhive.gateway;
|
||||
hyperhiveDomain = config.services.hyperhive.domain;
|
||||
# Derived once in ../swarm.nix; the vhosts that get the swarm-services
|
||||
# cert are exactly the names that cert is issued for, so both read the
|
||||
# same list rather than each deciding what "a swarm service" means.
|
||||
swarmServiceDomains = config.services.hyperhive.swarm.serviceDomains;
|
||||
matrixCfg = config.services.hyperhive.swarm.matrix;
|
||||
forgeCfg = config.services.hyperhive.swarm.forge;
|
||||
networkCfg = config.services.hyperhive.network;
|
||||
|
|
@ -263,6 +267,12 @@ in
|
|||
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";
|
||||
# The swarm-services pair, used only by the vhosts whose names
|
||||
# this hive's CA cannot sign. Self-signed mode only: with an
|
||||
# operator cert or ACME the operator owns every name and there
|
||||
# is no second issuer in the picture.
|
||||
svcCert = "${tlsDir}/swarm-services.pem";
|
||||
svcKey = "${tlsDir}/swarm-services-key.pem";
|
||||
nginxTree = import ./vhosts.nix {
|
||||
inherit
|
||||
lib
|
||||
|
|
@ -274,6 +284,9 @@ in
|
|||
swaggerUiTheme
|
||||
tlsCert
|
||||
tlsKey
|
||||
svcCert
|
||||
svcKey
|
||||
swarmServiceDomains
|
||||
;
|
||||
errorPages = import ./error-pages.nix { inherit pkgs; };
|
||||
};
|
||||
|
|
@ -362,6 +375,23 @@ in
|
|||
# Permission denied`, blocking the unit. Cert is world-read.
|
||||
install -m 0644 /run/hive-ca/gateway.pem ${tlsCert}
|
||||
install -m 0640 -g nginx /run/hive-ca/gateway-key.pem ${tlsKey}
|
||||
|
||||
# The swarm-services leaf, when this host issues one. It is
|
||||
# a separate pair rather than more SANs on the one above
|
||||
# because no hive CA can sign these names — each is
|
||||
# constrained to its own hive's domain and the service
|
||||
# names are siblings of it.
|
||||
#
|
||||
# Absent is a normal state, not a failure: the leaf exists
|
||||
# only where the swarm CA is autoconfigured. Copying it
|
||||
# conditionally keeps a hive whose certs come from its
|
||||
# operator working unchanged.
|
||||
if [ -s /run/hive-ca/swarm-services.pem ]; then
|
||||
install -m 0644 /run/hive-ca/swarm-services.pem ${svcCert}
|
||||
install -m 0640 -g nginx /run/hive-ca/swarm-services-key.pem ${svcKey}
|
||||
else
|
||||
rm -f ${svcCert} ${svcKey}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
errorPages, # ./error-pages.nix: { notFound, unreachable, unauthorized }
|
||||
tlsCert,
|
||||
tlsKey,
|
||||
svcCert, # swarm-services leaf, for names the hive CA cannot sign
|
||||
svcKey,
|
||||
swarmServiceDomains, # which vhosts those are (../swarm.nix derives it)
|
||||
}:
|
||||
let
|
||||
# The gateway always terminates TLS: self-signed is the implicit
|
||||
|
|
@ -51,6 +54,25 @@ let
|
|||
sslCertificateKey = tlsKey;
|
||||
};
|
||||
|
||||
# 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.
|
||||
vhostTlsFor =
|
||||
host:
|
||||
if !cfg.tls.acme.enable && cfg.tls.certDir == null && builtins.elem host swarmServiceDomains then
|
||||
{
|
||||
addSSL = true;
|
||||
sslCertificate = svcCert;
|
||||
sslCertificateKey = svcKey;
|
||||
}
|
||||
else
|
||||
vhostTls;
|
||||
|
||||
# Public-facing scheme + port-suffix for URLs the gateway
|
||||
# mints into responses (well-known JSON, the deprecated
|
||||
# `<hive>/matrix/*` 301 redirect, future absolute-URL needs):
|
||||
|
|
@ -85,7 +107,7 @@ let
|
|||
# `forge.sshPort`. See `docs/gateway.md`. Empty attrset when the
|
||||
# forge isn't behind the gateway.
|
||||
forgeVhost = lib.optionalAttrs (forgeCfg.behindGateway or false) {
|
||||
"${forgeCfg.domain}" = vhostTls // {
|
||||
"${forgeCfg.domain}" = (vhostTlsFor forgeCfg.domain) // {
|
||||
listen = vhostListen;
|
||||
extraConfig = securityHeaders;
|
||||
locations."/" = {
|
||||
|
|
@ -107,7 +129,7 @@ let
|
|||
# longer-prefix-wins puts `/_matrix/` ahead of `/`. See
|
||||
# `docs/gateway.md`. Empty attrset when matrix has no gateway host.
|
||||
matrixVhost = lib.optionalAttrs (matrixCfg.enable && matrixCfg.gatewayHost != null) {
|
||||
"${matrixCfg.gatewayHost}" = vhostTls // {
|
||||
"${matrixCfg.gatewayHost}" = (vhostTlsFor matrixCfg.gatewayHost) // {
|
||||
listen = vhostListen;
|
||||
extraConfig = securityHeaders;
|
||||
locations = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue