swarm-bao: grant the agent PKI mount (for #4756)

#4756 moves agent client certificates onto a PKI mount of their own,
`pki-agents`, whose root bao generates internally. The unit that sets
that mount up runs as the bao granter, and the granter's policy is only
written while #4754's one-time bootstrap token is in place. Adding these
grants after an operator has done that step would cost a second token
placement, so they go into the granter's policy here, before it.

Six stanzas: enable and tune the mount, list its issuers, read its CA,
generate its root internally, and write `swarm-*` roles on it. No root
delete or sudo: agent cert-auth roles pin that root by value, so
replacing it must not be something a deploy can do.

Adds deploy.bao.agentPkiMountPath (default `pki-agents`), which the
stanzas are rendered from. The module-eval case pinning the granter's
policy now lists all seventeen stanzas, and the "grants nothing outside"
case also refuses the agent mount's root, issue, sign and a roles/*
glob.
This commit is contained in:
atlas 2026-09-27 17:54:35 +02:00 • committed by mara
commit 5cd7f866f4
2 changed files with 81 additions and 4 deletions

View file

@ -185,10 +185,13 @@ let
# A trailing `*` in a bao ACL path is a pure string-prefix match, and an # A trailing `*` in a bao ACL path is a pure string-prefix match, and an
# exact path wins over any prefix. # exact path wins over any prefix.
# #
# The first three are the per-principal grants. The rest are what # The first three are the per-principal grants. The next eight are what
# `swarm-bao-controller-policy` does besides grants: the KV and pki mounts and # `swarm-bao-controller-policy` does besides grants: the KV and pki mounts and
# the services root. No `sys/auth`: the auth mounts are created with the # the services root. The last six set up the agent PKI mount: the mount, its
# bootstrap token by `swarm-bao-granter-role`. # root and the `swarm-*` role agent certificates are issued through. No
# `root` delete there: every agent's cert-auth role pins that root by value,
# so replacing it would lock every agent out. No `sys/auth`: the auth mounts
# are created with the bootstrap token by `swarm-bao-granter-role`.
# #
# Piped as a shell-quoted argument like `controllerPolicyText`, so # Piped as a shell-quoted argument like `controllerPolicyText`, so
# ../module-eval/bao-grants.nix can read it out of the unit script. # ../module-eval/bao-grants.nix can read it out of the unit script.
@ -236,6 +239,30 @@ let
path "${servicesPkiMountPath}/root/generate/internal" { path "${servicesPkiMountPath}/root/generate/internal" {
capabilities = ["create", "update"] capabilities = ["create", "update"]
} }
path "sys/mounts/${agentPkiMountPath}" {
capabilities = ["create", "update"]
}
path "sys/mounts/${agentPkiMountPath}/tune" {
capabilities = ["create", "update"]
}
path "${agentPkiMountPath}/issuers" {
capabilities = ["list"]
}
path "${agentPkiMountPath}/cert/ca" {
capabilities = ["read"]
}
path "${agentPkiMountPath}/root/generate/internal" {
capabilities = ["create", "update"]
}
path "${agentPkiMountPath}/roles/swarm-*" {
capabilities = ["create", "update"]
}
''; '';
# What a granting unit prints when the store refuses the granter: the # What a granting unit prints when the store refuses the granter: the
@ -473,6 +500,13 @@ let
# and has to spell it the same way. # and has to spell it the same way.
servicesPkiMountPath = baoDeploy.servicesPkiMountPath; servicesPkiMountPath = baoDeploy.servicesPkiMountPath;
# The PKI mount agent client certificates are issued from. Its root is
# generated inside the store, so the agent CA's key never exists outside it.
# A mount of its own because the services mount holds exactly one issuer; a
# root apart from ./glue-bao-tls.nix's CA because that is what keeps an
# agent's certificate from satisfying any host role.
agentPkiMountPath = baoDeploy.agentPkiMountPath;
# Subject of the root generated into that mount. A label for a human reading # Subject of the root generated into that mount. A label for a human reading
# a chain, not an identity anything authenticates against — same fall-through # a chain, not an identity anything authenticates against — same fall-through
# ./swarm-ca.nix:29-37 uses, and for the same reason: a hive that has set # ./swarm-ca.nix:29-37 uses, and for the same reason: a hive that has set
@ -1221,6 +1255,20 @@ in
''; '';
}; };
agentPkiMountPath = lib.mkOption {
type = lib.types.str;
default = "pki-agents";
description = ''
Mount path of the PKI engine agent client certificates are issued
from. Its root is generated inside the store and its key never leaves
it.
An option rather than a literal because the store host sets the mount
up while swarm-controller, possibly on another host, issues through
it: both have to spell it identically.
'';
};
servicesPkiRoleName = lib.mkOption { servicesPkiRoleName = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "swarm-services"; default = "swarm-services";

View file

@ -820,7 +820,7 @@ let
{ {
# The granter's grants, whole. Pinned as the full list, because an added # The granter's grants, whole. Pinned as the full list, because an added
# path or capability is exactly what a presence check misses. # path or capability is exactly what a presence check misses.
name = "the granter's policy is exactly these eleven stanzas"; name = "the granter's policy is exactly these seventeen stanzas";
ok = ok =
let let
cu = [ cu = [
@ -876,6 +876,30 @@ let
path = "pki/root/generate/internal"; path = "pki/root/generate/internal";
caps = cu; caps = cu;
} }
{
path = "sys/mounts/pki-agents";
caps = cu;
}
{
path = "sys/mounts/pki-agents/tune";
caps = cu;
}
{
path = "pki-agents/issuers";
caps = [ "list" ];
}
{
path = "pki-agents/cert/ca";
caps = [ "read" ];
}
{
path = "pki-agents/root/generate/internal";
caps = cu;
}
{
path = "pki-agents/roles/swarm-*";
caps = cu;
}
]; ];
} }
{ {
@ -908,6 +932,10 @@ let
"sys/policies/acl/root" "sys/policies/acl/root"
"pki/issue/swarm-services" "pki/issue/swarm-services"
"pki/sign/swarm-services" "pki/sign/swarm-services"
"pki-agents/root"
"pki-agents/issue/swarm-agent"
"pki-agents/sign/swarm-agent"
"pki-agents/sign-verbatim"
"*" "*"
] ]
&& !(lib.any ( && !(lib.any (
@ -917,6 +945,7 @@ let
"sys/policies/acl/*" "sys/policies/acl/*"
"auth/cert/certs/*" "auth/cert/certs/*"
"pki/roles/*" "pki/roles/*"
"pki-agents/roles/*"
] ]
) granterGrants); ) granterGrants);
} }