bao: disable the unused approle auth method, declaratively
No Rust ever minted a secret_id; approle was dead attack surface. The bootstrap step's check-then-enable case becomes check-then-disable: if approle is mounted, `bao auth disable approle`; otherwise a no-op. Disabling costs `delete`+`sudo` on `sys/auth/approle`, not `create`/`update` — verified against `bao auth disable -output-policy` on a live dev store. The bootstrap policy grant is narrowed to match. nix/module-eval/bao-grants.nix pins the new shape: the bootstrap policy may disable approle, and the granter's role unit never enables it.
This commit is contained in:
parent
7bc4b25f16
commit
433429ebfd
4 changed files with 41 additions and 11 deletions
|
|
@ -112,8 +112,9 @@ The token file is `services.hyperhive.deploy.bao.bootstrapTokenFile`, which
|
|||
all-local names for you. On a store host that isn't all-local, set it and
|
||||
rebuild first.
|
||||
|
||||
`swarm-bao-granter-role` runs **on the host**. It enables the cert, approle
|
||||
and oidc auth methods, writes the `bao-granter` policy, and creates the
|
||||
`swarm-bao-granter-role` runs **on the host**. It enables the cert and oidc
|
||||
auth methods (and disables `approle` if an older store still has it mounted),
|
||||
writes the `bao-granter` policy, and creates the
|
||||
`bao-granter` role, which accepts the leaf
|
||||
`/var/lib/swarm-bao-pki/granter.pem`. Every
|
||||
`swarm-bao-*-policy` unit then logs in with that leaf. The controller's unit
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
# not grant.
|
||||
|
||||
# The auth mounts. Reading `sys/auth` is how the unit checks, and `sudo` is
|
||||
# what enabling one costs.
|
||||
# what enabling or disabling one costs.
|
||||
path "sys/auth" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
|
@ -23,8 +23,11 @@ path "sys/auth/cert" {
|
|||
capabilities = ["create", "update", "sudo"]
|
||||
}
|
||||
|
||||
# Nothing mints an approle secret_id; the bootstrap step disables the mount
|
||||
# rather than enabling it. `delete` is what `bao auth disable` costs, not
|
||||
# `create`/`update` (verified with `bao auth disable -output-policy`).
|
||||
path "sys/auth/approle" {
|
||||
capabilities = ["create", "update", "sudo"]
|
||||
capabilities = ["delete", "sudo"]
|
||||
}
|
||||
|
||||
path "sys/auth/oidc" {
|
||||
|
|
|
|||
|
|
@ -2687,9 +2687,11 @@ in
|
|||
*) bao auth enable cert ;;
|
||||
esac
|
||||
|
||||
# Nothing mints a secret_id; disabled rather than left unmounted,
|
||||
# since a store from before this change may already have it.
|
||||
case "$mounted" in
|
||||
*'"approle/"'*) ;;
|
||||
*) bao auth enable approle ;;
|
||||
*'"approle/"'*) bao auth disable approle ;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
# The UI's OIDC login; `swarm-bao-operator-viewer-policy` writes its
|
||||
|
|
|
|||
|
|
@ -200,8 +200,9 @@ let
|
|||
# One `bao …` invocation → the path and capabilities it needs, as
|
||||
# `bao <cmd> -output-policy` reports them. Path-specific `sudo` (bao's
|
||||
# root-protected paths, e.g. `pki/root` for a delete) does not follow from
|
||||
# the verb, so only `auth enable` is checked for it. A verb not listed here
|
||||
# needs a path no grant has, so it fails the case until it is taught.
|
||||
# the verb, so only `auth enable`/`auth disable` are checked for it. A verb
|
||||
# not listed here needs a path no grant has, so it fails the case until it
|
||||
# is taught.
|
||||
baoCallNeeds =
|
||||
words:
|
||||
let
|
||||
|
|
@ -234,6 +235,11 @@ let
|
|||
need "sys/auth" [ "read" ]
|
||||
else if a 0 == "auth" && a 1 == "enable" then
|
||||
need "sys/auth/${a 2}" (cu ++ [ "sudo" ])
|
||||
else if a 0 == "auth" && a 1 == "disable" then
|
||||
need "sys/auth/${a 2}" [
|
||||
"delete"
|
||||
"sudo"
|
||||
]
|
||||
else if a 0 == "write" then
|
||||
need (a 1) cu
|
||||
else if a 0 == "read" then
|
||||
|
|
@ -1286,6 +1292,24 @@ let
|
|||
]
|
||||
&& grantFor bootstrapGrants "sys/policies/acl/swarm-controller" == null;
|
||||
}
|
||||
{
|
||||
# Nothing mints a secret_id, so the bootstrap policy's approle grant is
|
||||
# for tearing the mount down, not standing it up: `delete`+`sudo`
|
||||
# (verified against `bao auth disable -output-policy`), not
|
||||
# `create`/`update`. The granter's unit disables an existing mount and
|
||||
# never enables one.
|
||||
name = "the bootstrap policy may disable approle, and the granter's unit never enables it";
|
||||
ok =
|
||||
let
|
||||
g = baoGrantHere.systemd.services.swarm-bao-granter-role.script;
|
||||
in
|
||||
(grantFor bootstrapGrants "sys/auth/approle").caps == [
|
||||
"delete"
|
||||
"sudo"
|
||||
]
|
||||
&& lib.hasInfix "bao auth disable approle" g
|
||||
&& !(lib.hasInfix "bao auth enable approle" g);
|
||||
}
|
||||
{
|
||||
# The controller's whole reach on any PKI mount: one role's issue
|
||||
# endpoint. It cannot rewrite the role, sign a CSR of its choosing or
|
||||
|
|
@ -1409,9 +1433,9 @@ let
|
|||
&& grantFor bootstrapGrants "sys/auth/oidc" != null;
|
||||
}
|
||||
{
|
||||
# Asked before attempted, like the cert and approle mounts, so re-running
|
||||
# the step on a store that has the mount is a no-op; listed, or the UI's
|
||||
# login page shows no OIDC tab.
|
||||
# Asked before attempted, like the cert mount, so re-running the step
|
||||
# on a store that has the mount is a no-op; listed, or the UI's login
|
||||
# page shows no OIDC tab.
|
||||
name = "the granter's role unit enables the oidc mount once, listed on the UI's login page";
|
||||
ok =
|
||||
let
|
||||
|
|
|
|||
Loading…
Reference in a new issue