Compare commits
6 changed files with 6 additions and 167 deletions
|
|
@ -24,7 +24,6 @@
|
||||||
./hive-tls.nix
|
./hive-tls.nix
|
||||||
./otel.nix
|
./otel.nix
|
||||||
./glue-bao-tls.nix
|
./glue-bao-tls.nix
|
||||||
./glue-controller-bao-identity.nix
|
|
||||||
./glue-matrix-bao-token.nix
|
./glue-matrix-bao-token.nix
|
||||||
./swarm-authelia.nix
|
./swarm-authelia.nix
|
||||||
./swarm-bao.nix
|
./swarm-bao.nix
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
# ⚠️ Not the hive CA and not the swarm CA. The store will eventually
|
# ⚠️ Not the hive CA and not the swarm CA. The store will eventually
|
||||||
# distribute both, and an authority you must already hold a certificate from
|
# distribute both, and an authority you must already hold a certificate from
|
||||||
# cannot be one the store hands out — reach the store to get the CA material,
|
# cannot be one the store hands out — reach the store to get the CA material,
|
||||||
# need a cert from that CA to reach the store. This CA signs a fixed, short
|
# need a cert from that CA to reach the store. This CA signs exactly two
|
||||||
# list of leaves and distributes nothing, so it cannot enter that cycle.
|
# things and distributes nothing, so it cannot enter that cycle.
|
||||||
#
|
#
|
||||||
# ⚠️ Files like this are the only place a `deploy.<foo>` value may derive from
|
# ⚠️ Files like this are the only place a `deploy.<foo>` value may derive from
|
||||||
# a `deploy.<bar>.enable`. Everywhere else that is forbidden. The exception
|
# a `deploy.<bar>.enable`. Everywhere else that is forbidden. The exception
|
||||||
|
|
@ -126,17 +126,6 @@ in
|
||||||
${lib.escapeShellArg cfg.domain} ${lib.escapeShellArg "DNS:${cfg.domain}"} serverAuth
|
${lib.escapeShellArg cfg.domain} ${lib.escapeShellArg "DNS:${cfg.domain}"} serverAuth
|
||||||
[ -s ${pkiDir}/client.pem ] || ${signLeaf} ${pkiDir} client \
|
[ -s ${pkiDir}/client.pem ] || ${signLeaf} ${pkiDir} client \
|
||||||
${lib.escapeShellArg clientCn} "" clientAuth
|
${lib.escapeShellArg clientCn} "" clientAuth
|
||||||
|
|
||||||
# Minted whether or not a controller runs here, because the case it
|
|
||||||
# serves is the one where it does not: a controller elsewhere needs a
|
|
||||||
# leaf from this CA and has no way to sign one. Issuing it here turns
|
|
||||||
# "obtain a certificate out of band" into "copy this file".
|
|
||||||
#
|
|
||||||
# Its own CN rather than the reader's above: the controller's policy
|
|
||||||
# lets it create roles for every hive, and the reader's leaf carries
|
|
||||||
# this hive's name.
|
|
||||||
[ -s ${pkiDir}/controller.pem ] || ${signLeaf} ${pkiDir} controller \
|
|
||||||
${lib.escapeShellArg deployCfg.bao.controllerCommonName} "" clientAuth
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
# Glue: point the swarm controller at the bao leaf minted for it.
|
|
||||||
#
|
|
||||||
# ONE PAIRING PER FILE — controller ← bao, and nothing else. Deleting this
|
|
||||||
# leaves a controller that takes operator-provided certificate paths, which is
|
|
||||||
# what every deployment that does not mint its own already does.
|
|
||||||
#
|
|
||||||
# ⚠️ The minting is NOT here. ./glue-bao-tls.nix holds the CA and signs the
|
|
||||||
# leaf, because the thing that owns a private key owns issuing from it; a
|
|
||||||
# second signer would duplicate that file's `openssl` helper to no benefit.
|
|
||||||
# What belongs here is the pairing: which paths this host's controller reads.
|
|
||||||
#
|
|
||||||
# ⚠️ Gated on the leaf existing, not on the store being enabled. A controller
|
|
||||||
# on the store's own host is one deployment; a controller three networks away
|
|
||||||
# holding a leaf issued out of band is another, and both want the same wiring.
|
|
||||||
# `deploy.bao.enable` would have made the first one the only supported shape.
|
|
||||||
#
|
|
||||||
# Everything is `mkDefault`. An operator naming their own paths wins.
|
|
||||||
{
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
hyperhiveCfg = config.services.hyperhive;
|
|
||||||
deployCfg = hyperhiveCfg.deploy;
|
|
||||||
baoDeploy = deployCfg.bao;
|
|
||||||
|
|
||||||
# Where ./glue-bao-tls.nix puts the leaves. Derived from the reader's own
|
|
||||||
# path rather than repeating that file's directory literal: an operator who
|
|
||||||
# moves the PKI moves both, and the two cannot drift apart.
|
|
||||||
haveMintedPki = baoDeploy.clientCertFile != null;
|
|
||||||
pkiDir = if haveMintedPki then builtins.dirOf baoDeploy.clientCertFile else null;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.swarm-controller.enable && haveMintedPki) {
|
|
||||||
services.hyperhive.deploy.swarm-controller = {
|
|
||||||
baoClientCertFile = lib.mkDefault "${pkiDir}/controller.pem";
|
|
||||||
baoClientKeyFile = lib.mkDefault "${pkiDir}/controller-key.pem";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -154,9 +154,10 @@ let
|
||||||
# policy by spelling it the same way, and is itself named after it.
|
# policy by spelling it the same way, and is itself named after it.
|
||||||
controllerPolicyName = "swarm-controller";
|
controllerPolicyName = "swarm-controller";
|
||||||
|
|
||||||
# An option rather than a literal because whatever mints the controller's
|
# The subject the controller's certificate must carry. Cert auth matches on
|
||||||
# leaf has to spell it identically, and that lives in another file.
|
# the CN, so this is an interface rather than a label: a leaf signed by the
|
||||||
controllerCn = baoDeploy.controllerCommonName;
|
# right CA but minted with any other subject cannot authenticate.
|
||||||
|
controllerCn = "swarm-controller";
|
||||||
|
|
||||||
# Derived with `bao write -output-policy`, which short-circuits the request
|
# Derived with `bao write -output-policy`, which short-circuits the request
|
||||||
# and needs no server, rather than written from memory.
|
# and needs no server, rather than written from memory.
|
||||||
|
|
@ -477,27 +478,6 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
controllerCommonName = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "swarm-controller";
|
|
||||||
example = "swarm-controller.svc";
|
|
||||||
description = ''
|
|
||||||
Subject the store's `swarm-controller` cert-auth role accepts. An
|
|
||||||
interface, not a label: cert auth matches on the CN, so whatever mints
|
|
||||||
the controller's leaf has to spell it the same way.
|
|
||||||
|
|
||||||
Lives here because it is a property of the **role**, which this module
|
|
||||||
writes — not a credential belonging to the controller. Nothing outside
|
|
||||||
the store needs it except whatever issues that leaf.
|
|
||||||
|
|
||||||
⚠️ The CA this role trusts also signs each hive's reader leaf, whose CN
|
|
||||||
is the hive's own name, so a hive named `swarm-controller` would satisfy
|
|
||||||
the role. Changing this to something outside the hive-name grammar
|
|
||||||
(`[a-z0-9-]`) rules that out — at the cost of a role rename in any store
|
|
||||||
that has already run the granting unit.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
serverCaFile = lib.mkOption {
|
serverCaFile = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
default = null;
|
default = null;
|
||||||
|
|
|
||||||
|
|
@ -467,37 +467,6 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
baoClientCertFile = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
example = "/var/lib/swarm-controller/bao-client.pem";
|
|
||||||
description = ''
|
|
||||||
Certificate this daemon presents to the swarm's secret store. Its
|
|
||||||
subject has to match
|
|
||||||
{option}`services.hyperhive.deploy.bao.controllerCommonName`, which is
|
|
||||||
what the store's cert-auth role matches on.
|
|
||||||
|
|
||||||
Distinct from {option}`services.hyperhive.deploy.bao.clientCertFile` on
|
|
||||||
purpose, and not an oversight: that one is **this host as a reader** and
|
|
||||||
carries the hive's name, while the controller's policy lets it create
|
|
||||||
roles for every hive. One certificate serving both would hand that power
|
|
||||||
to whatever else reads the store here.
|
|
||||||
|
|
||||||
Defaulted by `glue-controller-bao-identity.nix` where this deployment
|
|
||||||
mints its own; elsewhere the leaf is issued out of band and named here.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
baoClientKeyFile = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
example = "/var/lib/swarm-controller/bao-client-key.pem";
|
|
||||||
description = ''
|
|
||||||
Private key for {option}`services.hyperhive.deploy.swarm-controller.baoClientCertFile`.
|
|
||||||
Both or neither — a certificate with no key authenticates nothing.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
queue = {
|
queue = {
|
||||||
clientSecretFile = lib.mkOption {
|
clientSecretFile = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
|
||||||
|
|
@ -254,20 +254,6 @@ let
|
||||||
baoGrantNoStore = hive {
|
baoGrantNoStore = hive {
|
||||||
deploy.bao.bootstrapTokenFile = "/run/secrets/bao-bootstrap.token";
|
deploy.bao.bootstrapTokenFile = "/run/secrets/bao-bootstrap.token";
|
||||||
};
|
};
|
||||||
# Store and controller on one machine, with a CN no default could supply.
|
|
||||||
# The odd value is what lets the case below tell "both ends read the same
|
|
||||||
# option" from "both ends happen to say swarm-controller".
|
|
||||||
baoControllerHere = hive {
|
|
||||||
deploy.bao.enable = true;
|
|
||||||
deploy.bao.bootstrapTokenFile = "/run/secrets/bao-bootstrap.token";
|
|
||||||
deploy.bao.controllerCommonName = "cn-marker-not-a-default";
|
|
||||||
deploy.swarm-controller.enable = true;
|
|
||||||
};
|
|
||||||
# The controller with no store, which is every spread deployment. Nothing
|
|
||||||
# mints here, so the pairing must leave the paths unset rather than name
|
|
||||||
# files this host will never have.
|
|
||||||
controllerNoStore = hive { deploy.swarm-controller.enable = true; };
|
|
||||||
|
|
||||||
# The store and the token, with no CA to trust. `mkForce` because the PKI
|
# The store and the token, with no CA to trust. `mkForce` because the PKI
|
||||||
# glue supplies one by default here — this is the deployment that brings its
|
# glue supplies one by default here — this is the deployment that brings its
|
||||||
# own certificates and has not named the authority yet, and it separates
|
# own certificates and has not named the authority yet, and it separates
|
||||||
|
|
@ -541,49 +527,6 @@ let
|
||||||
&& lib.hasInfix "auth/cert/certs/swarm-controller" s
|
&& lib.hasInfix "auth/cert/certs/swarm-controller" s
|
||||||
&& lib.hasInfix "/var/lib/swarm-bao-tls/client-ca.pem" s;
|
&& lib.hasInfix "/var/lib/swarm-bao-tls/client-ca.pem" s;
|
||||||
}
|
}
|
||||||
{
|
|
||||||
# Nothing asserted the PKI script before this, so a third leaf could be
|
|
||||||
# added to it and every case still passed — measured, not assumed: the
|
|
||||||
# commit that added one left `module-eval`'s derivation unchanged.
|
|
||||||
name = "the store mints a leaf for the controller, and the controller is pointed at it";
|
|
||||||
ok =
|
|
||||||
let
|
|
||||||
m = baoControllerHere;
|
|
||||||
pki = m.systemd.services.swarm-bao-pki.script;
|
|
||||||
in
|
|
||||||
lib.hasInfix "controller.pem" pki
|
|
||||||
&&
|
|
||||||
m.services.hyperhive.deploy.swarm-controller.baoClientCertFile
|
|
||||||
== "/var/lib/swarm-bao-pki/controller.pem"
|
|
||||||
&&
|
|
||||||
m.services.hyperhive.deploy.swarm-controller.baoClientKeyFile
|
|
||||||
== "/var/lib/swarm-bao-pki/controller-key.pem";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
# What makes the one above mean something: a controller with no store
|
|
||||||
# has nothing to be pointed at. Naming a path here would be a file this
|
|
||||||
# host never gets, which fails at a TLS handshake rather than at eval.
|
|
||||||
name = "a controller on a host with no store is left without certificate paths";
|
|
||||||
ok =
|
|
||||||
let
|
|
||||||
c = controllerNoStore.services.hyperhive.deploy.swarm-controller;
|
|
||||||
in
|
|
||||||
c.baoClientCertFile == null && c.baoClientKeyFile == null;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
# The CN is an interface between two files: the store writes a role that
|
|
||||||
# matches it, the PKI mints a leaf that carries it. They read one option,
|
|
||||||
# and this is what says so — the fixture's value cannot come from a
|
|
||||||
# default, so matching it in both places is not a coincidence.
|
|
||||||
name = "the cert-auth role and the minted leaf take their subject from one option";
|
|
||||||
ok =
|
|
||||||
let
|
|
||||||
m = baoControllerHere;
|
|
||||||
role = m.containers.swarm-bao.config.systemd.services.swarm-bao-controller-policy.script;
|
|
||||||
pki = m.systemd.services.swarm-bao-pki.script;
|
|
||||||
in
|
|
||||||
lib.hasInfix "cn-marker-not-a-default" role && lib.hasInfix "cn-marker-not-a-default" pki;
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
# The arm that makes the one above mean something. A role's trust anchor
|
# The arm that makes the one above mean something. A role's trust anchor
|
||||||
# is the CA, so with none named there is nothing to write — and the
|
# is the CA, so with none named there is nothing to write — and the
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue