swarm CA: retire the host-held root CA, the store holds the only anchor
The secret store's pki root issues every hive's gateway leaf and the
swarm's service names, so the root swarm-ca.service generated on the
host anchors nothing and its key is a secret outside the store.
- swarm-ca.nix is gone: swarm-ca.service, root.pem and root-key.pem,
and the swarm.ca.{autoConfigure,stateDir,validityDays} options. A
definition of any of them fails evaluation with a removal message
(swarm-ca-removed.nix), and a tmpfiles rule deletes /var/lib/swarm-ca
on every host. singleHostSwarm no longer sets a swarm.* option.
- hive-tls-ca drops the $root slot: trust-bundle.pem holds the store's
pki root and a hive CA's public ca.pem/ca-previous.pem only.
- hivectl peer-config no longer prints the scp line for the swarm root;
the store's pki root is the one step.
- The gateway's cert import no longer stands a hive leaf or a throwaway
in for a missing swarm-services leaf where it Requires= the issuing
unit: it fails, naming swarm-services-cert.service. The stand-in stays
where the gateway does not wait (no swarm name, or the store's UI
only, whose passthrough must start with the store sealed).
Comments and docs that named swarm-ca.nix, swarm.ca.* or the host root
now name the store's pki root.
Refs #4588
This commit is contained in:
parent
5d6ebda3e8
commit
93f1a76ea7
22 changed files with 305 additions and 395 deletions
|
|
@ -4,7 +4,8 @@
|
|||
# The swarm-services leaf's renewal: a timer that really re-runs the
|
||||
# issuance, at a threshold read off the store role's lifetime. And the
|
||||
# store's root on a hive that asks it for no leaf. And the gateway leaf,
|
||||
# issued by the store through the hive's own role, with no hive CA.
|
||||
# issued by the store through the hive's own role, with no hive CA and no
|
||||
# host-held swarm root.
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
|
|
@ -21,6 +22,7 @@ let
|
|||
nixosSystem
|
||||
;
|
||||
})
|
||||
hive
|
||||
hiveWithController
|
||||
runGroup
|
||||
;
|
||||
|
|
@ -208,10 +210,9 @@ let
|
|||
!(holds noService) && holds allLocal;
|
||||
}
|
||||
{
|
||||
# The controls are the store's PKI unit, which still mints its own CA
|
||||
# with `openssl req`, and the swarm root's unit, which still reads
|
||||
# `root-key.pem`: absence here is not a matcher that cannot match.
|
||||
name = "no hive TLS unit generates a CA, signs with a CA key or reads the swarm root key";
|
||||
# The control is the store's PKI unit, which still mints its own CA
|
||||
# with `openssl req`: absence here is not a matcher that cannot match.
|
||||
name = "no hive TLS unit generates a CA or signs with a CA key";
|
||||
ok =
|
||||
let
|
||||
hasCaWork =
|
||||
|
|
@ -219,16 +220,106 @@ let
|
|||
lib.any (p: lib.hasInfix p s) [
|
||||
"openssl req"
|
||||
"-CAkey"
|
||||
"root-key.pem"
|
||||
];
|
||||
pkiScript = units.swarm-bao-pki.script;
|
||||
rootScript = units.swarm-ca.script;
|
||||
in
|
||||
!(hasCaWork caUnit.script)
|
||||
&& !(hasCaWork leafUnit.script)
|
||||
&& !(hasCaWork noService.systemd.services.hive-tls-ca.script)
|
||||
&& lib.hasInfix "openssl req" pkiScript
|
||||
&& lib.hasInfix "root-key.pem" rootScript;
|
||||
&& lib.hasInfix "openssl req" pkiScript;
|
||||
}
|
||||
{
|
||||
# Every unit's script, environment and ordering, on the all-local host
|
||||
# and on a hive that runs no store. The control is the same scan finding
|
||||
# the store root's file, so an empty result is not a scan that reads
|
||||
# nothing.
|
||||
name = "no unit generates, reads or waits on a host-held swarm root, and every host deletes its directory";
|
||||
ok =
|
||||
let
|
||||
texts =
|
||||
m:
|
||||
lib.concatMap (u: [
|
||||
(u.script or "")
|
||||
(builtins.toJSON (u.environment or { }))
|
||||
(toString ((u.after or [ ]) ++ (u.requires or [ ]) ++ (u.wants or [ ])))
|
||||
]) (lib.attrValues m.systemd.services);
|
||||
mentions = m: needle: lib.any (lib.hasInfix needle) (texts m);
|
||||
gone =
|
||||
m:
|
||||
!(m.systemd.services ? swarm-ca)
|
||||
&& !(mentions m "swarm-ca.service")
|
||||
&& !(mentions m "/var/lib/swarm-ca")
|
||||
&& !(mentions m "root-key.pem")
|
||||
&& lib.elem "R /var/lib/swarm-ca" m.systemd.tmpfiles.rules;
|
||||
in
|
||||
gone allLocal
|
||||
&& gone noService
|
||||
&& mentions allLocal "swarm-services-root.pem"
|
||||
&& mentions noService "swarm-services-root.pem";
|
||||
}
|
||||
{
|
||||
# Three slots and no fourth: a root slot beside them is an anchor
|
||||
# nothing issues under.
|
||||
name = "the trust bundle anchors on the store's root and a hive CA's public certificates only";
|
||||
ok =
|
||||
let
|
||||
slots = lib.filter (l: lib.hasPrefix "if [ -s " l && lib.hasInfix "anchors+=(" l) (
|
||||
lib.splitString "\n" caUnit.script
|
||||
);
|
||||
in
|
||||
lib.length slots == 3
|
||||
&& lib.hasInfix ''if [ -s "$svcroot" ]; then anchors+=("$svcroot"); fi'' caUnit.script
|
||||
&& !(lib.hasInfix ''anchors+=("$root")'' caUnit.script);
|
||||
}
|
||||
{
|
||||
# The control is the all-local host, which defines none of them.
|
||||
name = "a definition of a removed swarm.ca option fails evaluation, naming the option and the store's root";
|
||||
ok =
|
||||
let
|
||||
refused =
|
||||
m: opt:
|
||||
lib.any (
|
||||
a:
|
||||
!a.assertion
|
||||
&& lib.hasInfix "services.hyperhive.swarm.ca.${opt}" a.message
|
||||
&& lib.hasInfix "pki root is the" a.message
|
||||
) m.assertions;
|
||||
setting = {
|
||||
autoConfigure = hive { swarm.ca.autoConfigure = true; };
|
||||
stateDir = hive { swarm.ca.stateDir = "/var/lib/swarm-ca"; };
|
||||
validityDays = hive { swarm.ca.validityDays = 10950; };
|
||||
};
|
||||
in
|
||||
lib.all (opt: refused setting.${opt} opt) (lib.attrNames setting)
|
||||
&& !(lib.any (opt: refused allLocal opt) (lib.attrNames setting));
|
||||
}
|
||||
{
|
||||
# Where the import `Requires=` the services leaf's issuing unit, a
|
||||
# missing leaf fails it; elsewhere the hive leaf stands in. The control
|
||||
# is a hive fronting no swarm name, which does not wait.
|
||||
name = "a hive gateway that waits for the services leaf fails its cert import without it, naming the issuing unit";
|
||||
ok =
|
||||
let
|
||||
imp = m: m.systemd.services.hive-gateway-self-signed-cert.script;
|
||||
waits =
|
||||
m:
|
||||
lib.elem "hive-gateway-self-signed-cert.service" m.systemd.services.swarm-services-cert.requiredBy;
|
||||
standIn = "install -m 0644 /var/lib/hive-tls/gateway.pem /var/lib/hive-gateway/tls/swarm-services.pem";
|
||||
refusal = "swarm-services-cert.service issues it";
|
||||
# The `else` branch alone, up to the `fi` that closes it.
|
||||
missingBranch =
|
||||
m:
|
||||
builtins.head (
|
||||
lib.splitString "\nfi" (lib.last (lib.splitString "swarm-services.pem ]; then" (imp m)))
|
||||
);
|
||||
in
|
||||
waits allLocal
|
||||
&& lib.hasInfix refusal (missingBranch allLocal)
|
||||
&& lib.hasSuffix "exit 1" (lib.removeSuffix "\n" (lib.trim (missingBranch allLocal)))
|
||||
&& !(lib.hasInfix standIn (imp allLocal))
|
||||
&& !(waits noService)
|
||||
&& lib.hasInfix standIn (missingBranch noService)
|
||||
&& !(lib.hasInfix refusal (imp noService));
|
||||
}
|
||||
{
|
||||
# Nothing reads or removes a `ca-key.pem`, so a rollback finds the
|
||||
|
|
|
|||
|
|
@ -287,30 +287,50 @@ let
|
|||
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;
|
||||
# The cert it installs on the default and swarm-services paths:
|
||||
# the services leaf when present, a throwaway pair otherwise.
|
||||
importsServicesLeafOrFallback =
|
||||
# the services leaf when present.
|
||||
importsServicesLeaf =
|
||||
m:
|
||||
let
|
||||
imp = m.systemd.services.hive-gateway-self-signed-cert;
|
||||
in
|
||||
lib.hasInfix "cert=/var/lib/hive-tls/swarm-services.pem" imp.script
|
||||
&& lib.hasInfix "openssl req -x509" imp.script
|
||||
&& lib.hasInfix "cert=/var/lib/hive-gateway/tls/fallback.pem" imp.script
|
||||
&& lib.hasInfix ''install -m 0644 "$cert" /var/lib/hive-gateway/tls/cert.pem'' imp.script
|
||||
&& lib.hasInfix ''install -m 0644 "$cert" /var/lib/hive-gateway/tls/swarm-services.pem'' imp.script
|
||||
&& lib.hasInfix ''install -m 0644 "$cert" /var/lib/hive-gateway/tls/swarm-services.pem'' imp.script;
|
||||
# Without it: a throwaway pair where the gateway does not wait for
|
||||
# the leaf, a failure naming the issuing unit where it does.
|
||||
standsInThrowaway =
|
||||
m:
|
||||
let
|
||||
imp = m.systemd.services.hive-gateway-self-signed-cert;
|
||||
in
|
||||
lib.hasInfix "openssl req -x509" imp.script
|
||||
&& lib.hasInfix "cert=/var/lib/hive-gateway/tls/fallback.pem" imp.script
|
||||
&& lib.any (p: lib.hasPrefix "openssl" (p.pname or (p.name or ""))) imp.path;
|
||||
failsWithoutLeaf =
|
||||
m:
|
||||
let
|
||||
s = m.systemd.services.hive-gateway-self-signed-cert.script;
|
||||
in
|
||||
lib.hasInfix "swarm-services-cert.service issues it" s
|
||||
&& lib.hasInfix "exit 1" s
|
||||
&& !(lib.hasInfix "fallback.pem" s);
|
||||
store = storeOnlyNoHive;
|
||||
hs = swarmServicesHere.systemd.services;
|
||||
in
|
||||
noHive swarmServicesOnly
|
||||
&& noHive swarmServicesOnlyNoStore
|
||||
&& noHive store
|
||||
&& importsServicesLeafOrFallback swarmServicesOnly
|
||||
&& importsServicesLeafOrFallback swarmServicesOnlyNoStore
|
||||
&& importsServicesLeafOrFallback store
|
||||
# Fronting many swarm names: the gateway waits for their leaf.
|
||||
&& importsServicesLeaf swarmServicesOnly
|
||||
&& importsServicesLeaf swarmServicesOnlyNoStore
|
||||
&& importsServicesLeaf store
|
||||
# Fronting many swarm names: the gateway waits for their leaf, and
|
||||
# its import fails without it.
|
||||
&& waitsForLeaf swarmServicesOnly
|
||||
&& waitsForLeaf swarmServicesOnlyNoStore
|
||||
&& failsWithoutLeaf swarmServicesOnly
|
||||
&& failsWithoutLeaf swarmServicesOnlyNoStore
|
||||
&& standsInThrowaway store
|
||||
&& !(failsWithoutLeaf store)
|
||||
# The store's UI alone: its nginx carries the passthrough to the
|
||||
# store, so it starts on the fallback with the store sealed.
|
||||
&&
|
||||
|
|
|
|||
Loading…
Reference in a new issue