swarm-bao: deny the secret publisher the callout xkey's seed
`swarm-secret-publisher` holds create and update on `secret/data/swarm/services/*`, which covers the callout xkey's seed at `swarm/services/swarm-nats/callout/xkey-seed`. It never writes that path, but a write there would replace the key the queue's server seals every callout request to. An exact-path `deny` outranks the prefix. A create-only grant would not do: KV v2 honours create versus update, so it would stop an overwrite, but it would still let the publisher plant a seed of its choosing whenever the path is empty, on a first boot or after a key replacement. The publisher needs no access to the path at all.
This commit is contained in:
parent
06784e55fa
commit
531d44d34b
2 changed files with 26 additions and 1 deletions
|
|
@ -453,7 +453,7 @@ let
|
||||||
secretPublisherPolicyName = "swarm-secret-publisher";
|
secretPublisherPolicyName = "swarm-secret-publisher";
|
||||||
secretPublisherCn = baoDeploy.secretPublisherCommonName;
|
secretPublisherCn = baoDeploy.secretPublisherCommonName;
|
||||||
|
|
||||||
# Three grants, and every narrowing in each is load-bearing.
|
# Three grants and one denial, and every narrowing in each is load-bearing.
|
||||||
#
|
#
|
||||||
# `secret/data/` is KV v2's ACL prefix, inserted by the engine rather than
|
# `secret/data/` is KV v2's ACL prefix, inserted by the engine rather than
|
||||||
# written by the caller — same trap as the controller's grant above.
|
# written by the caller — same trap as the controller's grant above.
|
||||||
|
|
@ -468,6 +468,10 @@ let
|
||||||
#
|
#
|
||||||
# Write-only. It copies secrets in and never reads one back; a read
|
# Write-only. It copies secrets in and never reads one back; a read
|
||||||
# capability would let a file-copier recover every hive's credentials.
|
# capability would let a file-copier recover every hive's credentials.
|
||||||
|
#
|
||||||
|
# The callout xkey's seed sits under `swarm/services/` and is no secret this
|
||||||
|
# principal copies, so its exact path, which outranks the prefix, is denied:
|
||||||
|
# a write there would replace the key every queue request is sealed to.
|
||||||
secretPublisherPolicyText = ''
|
secretPublisherPolicyText = ''
|
||||||
path "${credentialMountPath}/data/swarm/hives/*" {
|
path "${credentialMountPath}/data/swarm/hives/*" {
|
||||||
capabilities = ["create", "update"]
|
capabilities = ["create", "update"]
|
||||||
|
|
@ -477,6 +481,10 @@ let
|
||||||
capabilities = ["create", "update"]
|
capabilities = ["create", "update"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path "${credentialMountPath}/data/${xkeySeedLeaf}" {
|
||||||
|
capabilities = ["deny"]
|
||||||
|
}
|
||||||
|
|
||||||
path "${credentialMountPath}/data/${controllerClientLeaf}" {
|
path "${credentialMountPath}/data/${controllerClientLeaf}" {
|
||||||
capabilities = ["create", "update"]
|
capabilities = ["create", "update"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -425,6 +425,23 @@ let
|
||||||
&& !(lib.hasInfix "secret/data/swarm/*" s)
|
&& !(lib.hasInfix "secret/data/swarm/*" s)
|
||||||
&& !(lib.hasInfix "sys/policies/acl" s);
|
&& !(lib.hasInfix "sys/policies/acl" s);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
# The publisher's `services/*` write covers the callout xkey's seed, and
|
||||||
|
# only the exact-path denial, which outranks the prefix, keeps it from
|
||||||
|
# replacing the key. The control is an OIDC leaf beside it, which the
|
||||||
|
# prefix still grants.
|
||||||
|
name = "the publisher may not write the callout xkey's seed";
|
||||||
|
ok =
|
||||||
|
let
|
||||||
|
grants = grantsIn baoGrantHere.systemd.services.swarm-bao-secret-publisher-policy.script;
|
||||||
|
in
|
||||||
|
(grantFor grants "secret/data/swarm/services/swarm-nats/callout/xkey-seed").caps == [ "deny" ]
|
||||||
|
&&
|
||||||
|
(grantFor grants "secret/data/swarm/services/swarm-nats/oidc/client").caps == [
|
||||||
|
"create"
|
||||||
|
"update"
|
||||||
|
];
|
||||||
|
}
|
||||||
{
|
{
|
||||||
# The ordering is load-bearing and invisible at runtime: the controller's
|
# The ordering is load-bearing and invisible at runtime: the controller's
|
||||||
# unit creates the KV and cert-auth mounts this one writes into, so
|
# unit creates the KV and cert-auth mounts this one writes into, so
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue