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
|
let
|
||||||
cfg = config.services.hyperhive.gateway;
|
cfg = config.services.hyperhive.gateway;
|
||||||
hyperhiveDomain = config.services.hyperhive.domain;
|
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;
|
matrixCfg = config.services.hyperhive.swarm.matrix;
|
||||||
forgeCfg = config.services.hyperhive.swarm.forge;
|
forgeCfg = config.services.hyperhive.swarm.forge;
|
||||||
networkCfg = config.services.hyperhive.network;
|
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";
|
if cfg.tls.certDir != null then "/run/hive-tls/${cfg.tls.certName}" else "${tlsDir}/cert.pem";
|
||||||
tlsKey =
|
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";
|
||||||
|
# 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 {
|
nginxTree = import ./vhosts.nix {
|
||||||
inherit
|
inherit
|
||||||
lib
|
lib
|
||||||
|
|
@ -274,6 +284,9 @@ in
|
||||||
swaggerUiTheme
|
swaggerUiTheme
|
||||||
tlsCert
|
tlsCert
|
||||||
tlsKey
|
tlsKey
|
||||||
|
svcCert
|
||||||
|
svcKey
|
||||||
|
swarmServiceDomains
|
||||||
;
|
;
|
||||||
errorPages = import ./error-pages.nix { inherit pkgs; };
|
errorPages = import ./error-pages.nix { inherit pkgs; };
|
||||||
};
|
};
|
||||||
|
|
@ -362,6 +375,23 @@ in
|
||||||
# Permission denied`, blocking the unit. Cert is world-read.
|
# Permission denied`, blocking the unit. Cert is world-read.
|
||||||
install -m 0644 /run/hive-ca/gateway.pem ${tlsCert}
|
install -m 0644 /run/hive-ca/gateway.pem ${tlsCert}
|
||||||
install -m 0640 -g nginx /run/hive-ca/gateway-key.pem ${tlsKey}
|
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 }
|
errorPages, # ./error-pages.nix: { notFound, unreachable, unauthorized }
|
||||||
tlsCert,
|
tlsCert,
|
||||||
tlsKey,
|
tlsKey,
|
||||||
|
svcCert, # swarm-services leaf, for names the hive CA cannot sign
|
||||||
|
svcKey,
|
||||||
|
swarmServiceDomains, # which vhosts those are (../swarm.nix derives it)
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
# The gateway always terminates TLS: self-signed is the implicit
|
# The gateway always terminates TLS: self-signed is the implicit
|
||||||
|
|
@ -51,6 +54,25 @@ let
|
||||||
sslCertificateKey = tlsKey;
|
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
|
# Public-facing scheme + port-suffix for URLs the gateway
|
||||||
# mints into responses (well-known JSON, the deprecated
|
# mints into responses (well-known JSON, the deprecated
|
||||||
# `<hive>/matrix/*` 301 redirect, future absolute-URL needs):
|
# `<hive>/matrix/*` 301 redirect, future absolute-URL needs):
|
||||||
|
|
@ -85,7 +107,7 @@ let
|
||||||
# `forge.sshPort`. See `docs/gateway.md`. Empty attrset when the
|
# `forge.sshPort`. See `docs/gateway.md`. Empty attrset when the
|
||||||
# forge isn't behind the gateway.
|
# forge isn't behind the gateway.
|
||||||
forgeVhost = lib.optionalAttrs (forgeCfg.behindGateway or false) {
|
forgeVhost = lib.optionalAttrs (forgeCfg.behindGateway or false) {
|
||||||
"${forgeCfg.domain}" = vhostTls // {
|
"${forgeCfg.domain}" = (vhostTlsFor forgeCfg.domain) // {
|
||||||
listen = vhostListen;
|
listen = vhostListen;
|
||||||
extraConfig = securityHeaders;
|
extraConfig = securityHeaders;
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
|
|
@ -107,7 +129,7 @@ let
|
||||||
# longer-prefix-wins puts `/_matrix/` ahead of `/`. See
|
# longer-prefix-wins puts `/_matrix/` ahead of `/`. See
|
||||||
# `docs/gateway.md`. Empty attrset when matrix has no gateway host.
|
# `docs/gateway.md`. Empty attrset when matrix has no gateway host.
|
||||||
matrixVhost = lib.optionalAttrs (matrixCfg.enable && matrixCfg.gatewayHost != null) {
|
matrixVhost = lib.optionalAttrs (matrixCfg.enable && matrixCfg.gatewayHost != null) {
|
||||||
"${matrixCfg.gatewayHost}" = vhostTls // {
|
"${matrixCfg.gatewayHost}" = (vhostTlsFor matrixCfg.gatewayHost) // {
|
||||||
listen = vhostListen;
|
listen = vhostListen;
|
||||||
extraConfig = securityHeaders;
|
extraConfig = securityHeaders;
|
||||||
locations = {
|
locations = {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue