diff --git a/docs/2-handbook/swarm/semi-spread-deploy.md b/docs/2-handbook/swarm/semi-spread-deploy.md index 8441ca5cf..412f0087c 100644 --- a/docs/2-handbook/swarm/semi-spread-deploy.md +++ b/docs/2-handbook/swarm/semi-spread-deploy.md @@ -170,10 +170,12 @@ addresses for you, so the key steps above go away. Three things change: ## Reading the store Only `swarm-central` runs the secret store, and every service on the other -machines reads it with a client certificate that the store's host mints. The -example names each leaf under `/var/lib/swarm-pki/` on the machine that uses -it. After the first deploy of `swarm-central`, copy the files from -`/var/lib/swarm-bao-pki/` there over the VPN (for a hive, from +machines reads it with a client certificate that the store's host mints, and +checks the store against its CA (`ca.pem`). The example names each leaf, and +`ca.pem`, under `/var/lib/swarm-pki/` on the machine that uses it. Without +`ca.pem` the machines don't trust the store's certificate. After the first +deploy of `swarm-central`, copy the files from `/var/lib/swarm-bao-pki/` there +over the VPN (for a hive, `ca.pem` and the files from `/var/lib/swarm-bao-pki/hives//`), then deploy the other machines. The private keys travel over a channel you trust and stay readable by root only. [`credentials.md`](credentials.md) lists which leaf each service reads and what diff --git a/docs/examples/semi-spread/hive.nix b/docs/examples/semi-spread/hive.nix index 498e09e18..6298778b2 100644 --- a/docs/examples/semi-spread/hive.nix +++ b/docs/examples/semi-spread/hive.nix @@ -5,11 +5,12 @@ let hyperhive = config.services.hyperhive; name = lib.removePrefix "hive-" config.networking.hostName; - # The leaves for this hive, copied from swarm-central, where the secret store - # runs: `client.pem` and `client-key.pem` from + # The store's CA (`ca.pem`, from `/var/lib/swarm-bao-pki/`) and the leaves + # for this hive, copied from swarm-central, where the secret store runs: + # `client.pem` and `client-key.pem` from # `/var/lib/swarm-bao-pki/hives//`, along with that directory's # `hive-leaf.pem` and `hive-leaf-key.pem`, which the hive's gateway - # certificate comes from. Both pairs sit in one directory. + # certificate comes from. All of them sit in one directory. pki = "/var/lib/swarm-pki"; in { @@ -20,6 +21,7 @@ in hiveName = name; }; bao = { + serverCaFile = "${pki}/ca.pem"; clientCertFile = "${pki}/client.pem"; clientKeyFile = "${pki}/client-key.pem"; }; diff --git a/docs/examples/semi-spread/swarm-heavyduty.nix b/docs/examples/semi-spread/swarm-heavyduty.nix index 07170dc25..9dda80eea 100644 --- a/docs/examples/semi-spread/swarm-heavyduty.nix +++ b/docs/examples/semi-spread/swarm-heavyduty.nix @@ -3,8 +3,8 @@ let hyperhive = config.services.hyperhive; - # Leaves copied from swarm-central, where the secret store runs. See - # swarm-services.nix for the helper's naming. + # The store's CA and the leaves, copied from swarm-central, where the secret + # store runs. See swarm-services.nix for the helper's naming. pki = "/var/lib/swarm-pki"; pair = option: stem: { "${option}ClientCertFile" = "${pki}/${stem}.pem"; @@ -23,6 +23,7 @@ in }); deploy.bao = lib.mergeAttrsList [ + { serverCaFile = "${pki}/ca.pem"; } (pair "builder" "builder") (pair "ciRunner" "ci-runner") (pair "attic" "attic") diff --git a/docs/examples/semi-spread/swarm-services.nix b/docs/examples/semi-spread/swarm-services.nix index c05497ed8..83122442f 100644 --- a/docs/examples/semi-spread/swarm-services.nix +++ b/docs/examples/semi-spread/swarm-services.nix @@ -4,9 +4,10 @@ let hyperhive = config.services.hyperhive; - # The secret store runs on swarm-central, so each service here reads it with - # a leaf copied from there. `pair "grafanaOidc" "grafana-oidc"` names the - # options `deploy.bao.grafanaOidcClientCertFile` and `…KeyFile` and the files + # The secret store runs on swarm-central, so each service here checks it + # against the CA (`ca.pem`) and reads it with a leaf, both copied from there. + # `pair "grafanaOidc" "grafana-oidc"` names the options + # `deploy.bao.grafanaOidcClientCertFile` and `…KeyFile` and the files # `grafana-oidc.pem` and `grafana-oidc-key.pem`. pki = "/var/lib/swarm-pki"; pair = option: stem: { @@ -37,7 +38,10 @@ in }); deploy.bao = lib.mergeAttrsList [ - { enable = false; } + { + enable = false; + serverCaFile = "${pki}/ca.pem"; + } (pair "forgePublisher" "forge-publisher") (pair "servicesIssuer" "services-issuer") (pair "grafanaOidc" "grafana-oidc") diff --git a/nix/module-eval/semi-spread.nix b/nix/module-eval/semi-spread.nix index 040961b03..3ed274e4e 100644 --- a/nix/module-eval/semi-spread.nix +++ b/nix/module-eval/semi-spread.nix @@ -488,6 +488,20 @@ let && lib.all (p: lib.any (a: lib.hasSuffix "/128" a) p.allowedIPs) wg.peers ) (builtins.attrNames all); } + { + # The store's certificate is signed by its own CA, which only the store's + # host has by default; every machine without the store names the copy. + name = "every machine without the store checks it against the CA copied from swarm-central"; + ok = + lib.all (n: all.${n}.services.hyperhive.deploy.bao.serverCaFile == "/var/lib/swarm-pki/ca.pem") [ + "swarm-services" + "swarm-heavyduty" + "hive-one" + "hive-two" + ] + # Control: the machine with the store keeps the CA it minted. + && central.services.hyperhive.deploy.bao.serverCaFile == "/var/lib/swarm-bao-pki/ca.pem"; + } { name = "a hive resolves the internal names to the IPv6 addresses as well"; ok =