hive-tls: no hive leaf or hive identity on a gateway host without a hive
A self-signed gateway on a host with deploy.hive-controller.enable off (a dedicated swarm-services host) needed the hive leaf for its catch-all, and that leaf now comes only from hive-tls-resign with a per-hive store identity. hive-tls-resign and its timer are now defined only where a hive runs; elsewhere hive-gateway-self-signed-cert installs the swarm-services leaf as the default cert, and the gateway waits for swarm-services-cert. Also corrects the hive-gateway comments and option text that still said the leaf is hive-CA-signed. Refs #4588
This commit is contained in:
parent
fc33a62bf8
commit
75a7dccee6
6 changed files with 90 additions and 24 deletions
|
|
@ -25,6 +25,10 @@ let
|
|||
swarmServiceDomains = config.services.hyperhive.swarm.serviceDomains;
|
||||
matrixCfg = config.services.hyperhive.swarm.matrix;
|
||||
matrixDeployCfg = config.services.hyperhive.deploy.matrix;
|
||||
tlsStateDir = config.services.hyperhive.deploy.hive-controller.tls.stateDir;
|
||||
# The hive leaf exists only where a hive runs; ../hive-tls.nix issues it
|
||||
# under that same condition.
|
||||
runsHive = config.services.hyperhive.deploy.hive-controller.enable;
|
||||
|
||||
# Every vhost claiming `default_server`, ours and the operator's
|
||||
# alike. Computed once so the assertion below and the message it
|
||||
|
|
@ -45,7 +49,7 @@ let
|
|||
|
||||
# Self-signed TLS is the implicit floor: when neither an operator cert
|
||||
# (`tls.certDir`) nor ACME (`tls.acme.enable`) is configured, the gateway
|
||||
# generates + serves a hive-CA-signed leaf (see hive-tls.nix). There is no
|
||||
# serves a leaf the secret store issues (see hive-tls.nix). There is no
|
||||
# explicit toggle and no http-only mode — matrix discovery requires https,
|
||||
# so the gateway always terminates TLS.
|
||||
# `cfg.useSelfSigned` (options.nix) is the derived single source of truth.
|
||||
|
|
@ -57,15 +61,16 @@ let
|
|||
# certs for no gain.
|
||||
tlsDir = "/var/lib/hive-gateway/tls";
|
||||
# TLS cert + key paths.
|
||||
# - self-signed (default): the hive-CA-signed leaf, imported into the
|
||||
# state dir by `hive-gateway-self-signed-cert` below.
|
||||
# - self-signed (default): the hive leaf the store issues (the
|
||||
# swarm-services leaf on a host with no hive), imported into the state
|
||||
# dir by `hive-gateway-self-signed-cert` below.
|
||||
# - tls.certDir set: the operator's own cert dir, read directly.
|
||||
tlsCert =
|
||||
if cfg.tls.certDir != null then "${cfg.tls.certDir}/${cfg.tls.certName}" else "${tlsDir}/cert.pem";
|
||||
tlsKey =
|
||||
if cfg.tls.certDir != null then "${cfg.tls.certDir}/${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
|
||||
# The swarm-services pair, used only by the vhosts whose names the
|
||||
# hive leaf cannot carry. Self-signed mode only: with an operator cert
|
||||
# or ACME the operator owns every name and there is no second issuer.
|
||||
svcCert = "${tlsDir}/swarm-services.pem";
|
||||
svcKey = "${tlsDir}/swarm-services-key.pem";
|
||||
|
|
@ -244,7 +249,7 @@ in
|
|||
defaults.email = cfg.tls.acme.email;
|
||||
};
|
||||
|
||||
# Import the hive-CA leaf into nginx's state dir before nginx starts.
|
||||
# Import the gateway leaves into nginx's state dir before nginx starts.
|
||||
#
|
||||
# 🚨 DO NOT "simplify" this into pointing nginx at the CA dir. It does
|
||||
# TWO jobs: it re-modes the leaf to the 0640 root:nginx copy below (a
|
||||
|
|
@ -292,18 +297,27 @@ in
|
|||
# read it — a 0600 root:root key passes the master load but
|
||||
# fails the pre-start config test with `BIO_new_file() …
|
||||
# Permission denied`, blocking the unit. Cert is world-read.
|
||||
install -m 0644 ${config.services.hyperhive.deploy.hive-controller.tls.stateDir}/gateway.pem ${tlsCert}
|
||||
install -m 0640 -g nginx ${config.services.hyperhive.deploy.hive-controller.tls.stateDir}/gateway-key.pem ${tlsKey}
|
||||
${
|
||||
if runsHive then
|
||||
''
|
||||
install -m 0644 ${tlsStateDir}/gateway.pem ${tlsCert}
|
||||
install -m 0640 -g nginx ${tlsStateDir}/gateway-key.pem ${tlsKey}''
|
||||
else
|
||||
''
|
||||
# No hive here, so no hive leaf: the catch-all and any other
|
||||
# vhost on the hive cert path serve the swarm-services leaf.
|
||||
install -m 0644 ${tlsStateDir}/swarm-services.pem ${tlsCert}
|
||||
install -m 0640 -g nginx ${tlsStateDir}/swarm-services-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.
|
||||
# because the hive leaf's role allows only 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, and issuance
|
||||
# can also fail on a host that wants one.
|
||||
# Absent is a normal state, not a failure: a host fronting
|
||||
# no swarm name has none, and issuance can also fail on a
|
||||
# host that wants one.
|
||||
#
|
||||
# ⚠️ When it is absent the HIVE leaf goes to this path
|
||||
# anyway, and that fallback is load-bearing rather than
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ in
|
|||
|
||||
Which issuer covers a name is **gateway** knowledge, not the
|
||||
service's: a swarm service's name can sit outside this hive's
|
||||
domain, and the hive CA is name-constrained out of it, so that
|
||||
domain, and the hive leaf's role refuses it, so that
|
||||
vhost must serve the swarm-services leaf while everything else
|
||||
keeps the hive leaf. A service module calls this instead of
|
||||
deciding — deciding is how the vhost and the cert stop
|
||||
|
|
@ -231,7 +231,7 @@ in
|
|||
defaultText = lib.literalExpression "tls.certDir == null && !tls.acme.enable";
|
||||
description = ''
|
||||
Read-only derived flag: `true` when the gateway serves the
|
||||
self-signed (hive-CA-signed) leaf — i.e. neither `tls.certDir` nor
|
||||
self-signed leaf the secret store issues — i.e. neither `tls.certDir` nor
|
||||
`tls.acme.enable` is configured. Single source of truth for the
|
||||
self-signed condition; consumed by the `hive-tls` and `hive-ci`
|
||||
modules so the derivation isn't duplicated. Internal — not meant to
|
||||
|
|
@ -261,7 +261,7 @@ in
|
|||
Path to a host directory containing a TLS certificate and
|
||||
private key for nginx. When set, nginx listens on `httpsPort`
|
||||
and uses this cert, overriding the self-signed default — the
|
||||
auto-generated hive-CA-signed leaf is skipped entirely.
|
||||
store-issued self-signed leaf is skipped entirely.
|
||||
|
||||
nginx reads `<certDir>/<tls.certName>` and
|
||||
`<certDir>/<tls.keyName>` directly — it runs on the host, so
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@
|
|||
cfg, # services.hyperhive.gateway
|
||||
tlsCert,
|
||||
tlsKey,
|
||||
svcCert, # swarm-services leaf, for names the hive CA cannot sign
|
||||
svcCert, # swarm-services leaf, for names the hive leaf cannot carry
|
||||
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)
|
||||
caBundle, # hive TLS trust bundle on the host
|
||||
}:
|
||||
let
|
||||
# nixos `services.nginx.virtualHosts.<name>` ssl attrs for a vhost
|
||||
|
|
@ -68,8 +68,8 @@ in
|
|||
];
|
||||
|
||||
# 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
|
||||
# outside this hive's domain — and then the hive leaf's role
|
||||
# refuses 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
|
||||
|
|
|
|||
|
|
@ -46,8 +46,16 @@ let
|
|||
# Nor on a host fronting no swarm name: it asks the store for the root
|
||||
# alone, which nginx never reads, and a sealed store would otherwise hold
|
||||
# its gateway down.
|
||||
#
|
||||
# On a host with no hive there is no hive leaf to start on: the services
|
||||
# leaf is the only certificate its gateway has, so it always waits.
|
||||
gatewayWaitsForServicesLeaf =
|
||||
localServiceDomains != [ ] && localServiceDomains != [ baoCfg.ui.domain ];
|
||||
localServiceDomains != [ ] && (!runsHive || localServiceDomains != [ baoCfg.ui.domain ]);
|
||||
|
||||
# The hive leaf, `hive-tls-resign` and the hive identity it needs exist only
|
||||
# where a hive runs. A gateway host without one serves the swarm-services
|
||||
# leaf in their place (../hive-gateway/default.nix).
|
||||
runsHive = hyperhiveCfg.deploy.hive-controller.enable;
|
||||
|
||||
# The gateway's own leaf is only issued when the gateway actually serves a
|
||||
# self-signed cert: the gateway must run here and be in self-signed mode.
|
||||
|
|
@ -458,7 +466,7 @@ in
|
|||
# propagation failure means the leaf rotates on disk while nginx keeps
|
||||
# serving the old copy until it expires, with this unit reporting
|
||||
# success the whole time.
|
||||
systemd.services.hive-tls-resign = {
|
||||
systemd.services.hive-tls-resign = lib.mkIf runsHive {
|
||||
description = "Issue or renew the gateway TLS leaf from the secret store's PKI";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
# The store's container and its granting unit where they run here;
|
||||
|
|
@ -941,7 +949,7 @@ in
|
|||
|
||||
# Daily, for the reason `swarm-services-cert-renew` below is: the leaf
|
||||
# needs the store, which can be sealed or unreachable.
|
||||
systemd.timers.hive-tls-resign = {
|
||||
systemd.timers.hive-tls-resign = lib.mkIf runsHive {
|
||||
description = "Daily renewal check for the gateway TLS leaf";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,14 @@ let
|
|||
deploy.allSwarmServices = true;
|
||||
};
|
||||
|
||||
# A services host with hives elsewhere and the store elsewhere too: no hive
|
||||
# and no store identity minted here.
|
||||
swarmServicesOnlyNoStore = hive {
|
||||
deploy.hive-controller.enable = false;
|
||||
deploy.allSwarmServices = true;
|
||||
deploy.bao.enable = false;
|
||||
};
|
||||
|
||||
# The same switch with every service it owns placed elsewhere, so the only
|
||||
# thing that can enable the gateway/resolver/bridge here is that toggle's
|
||||
# own module — each service module asserts them from its own `enable`.
|
||||
|
|
@ -187,6 +195,36 @@ let
|
|||
&& swarmServicesSwitchAlone.services.hyperhive.gateway.dns.enable
|
||||
&& swarmServicesSwitchAlone.services.hyperhive.network.enable;
|
||||
}
|
||||
{
|
||||
# No hive, so no hive leaf and no hive identity: the gateway's default
|
||||
# certificate is the swarm-services leaf, and its start waits for that
|
||||
# leaf. The control is a hive host with the same services, which keeps
|
||||
# the hive leaf and the unit that issues it.
|
||||
name = "a gateway host without a hive serves the swarm-services leaf as its default cert and has no hive-leaf unit";
|
||||
ok =
|
||||
let
|
||||
noHive =
|
||||
m:
|
||||
let
|
||||
s = m.systemd.services;
|
||||
imp = s.hive-gateway-self-signed-cert.script;
|
||||
in
|
||||
!(s ? hive-tls-resign)
|
||||
&& !(m.systemd.timers ? hive-tls-resign)
|
||||
&& lib.hasInfix "install -m 0644 /var/lib/hive-tls/swarm-services.pem /var/lib/hive-gateway/tls/cert.pem" imp
|
||||
&& !(lib.hasInfix "/var/lib/hive-tls/gateway.pem /var/lib/hive-gateway/tls/cert.pem" imp)
|
||||
&& lib.elem "hive-gateway-self-signed-cert.service" s.swarm-services-cert.requiredBy
|
||||
&& lib.elem "hive-gateway-self-signed-cert.service" s.swarm-services-cert.before
|
||||
# Fronting swarm names, so the root-only branch that presents the
|
||||
# hive identity is not the one that runs.
|
||||
&& lib.hasInfix "want_svc=1" s.swarm-services-cert.script;
|
||||
hs = swarmServicesHere.systemd.services;
|
||||
in
|
||||
noHive swarmServicesOnly
|
||||
&& noHive swarmServicesOnlyNoStore
|
||||
&& hs ? hive-tls-resign
|
||||
&& lib.hasInfix "install -m 0644 /var/lib/hive-tls/gateway.pem /var/lib/hive-gateway/tls/cert.pem" hs.hive-gateway-self-signed-cert.script;
|
||||
}
|
||||
{
|
||||
# Each service is gated on its own deployment toggle alone, so a host
|
||||
# that runs no hive still runs every shared service the switch turns
|
||||
|
|
|
|||
Loading…
Reference in a new issue