fix(#3882): pin the gid that owns the TPM node, on both sides of the boundary
#3880 bound /dev/tpmrm0 into the store's container, which was necessary and not sufficient: a bind mount preserves the host's ownership, openbao runs as a DynamicUser with an empty CapabilityBoundingSet (so no CAP_DAC_OVERRIDE), and the host applies no tpm udev rule at all — measured, 0 hits across all 41 host closures against 62 rule files as a control. The node therefore keeps the kernel default and the seal cannot open it. A name cannot fix this. NixOS allocates system groups at activation, per machine, so `tss` — or any group declared on both sides — gets two different ids, and the device node carries the number. mara picked pinning a gid with an overridable default (deploy.bao.tpmGid). The default sits above the range NixOS auto-assigns system groups from (400-999, measured in update-users-groups.pl) and above the normal-user range, and below systemd's DynamicUser range (61184-65519), so it collides with nothing any of those allocate. The module-eval case compares the two sides rather than checking each against a literal: the property is that they AGREE, not what they agree on. Its absence arm is a shamir store, which never opens a TPM and must not claim a device node's group — without it, pinning unconditionally would look identical.
This commit is contained in:
parent
e395737a25
commit
c044a33be9
2 changed files with 88 additions and 4 deletions
|
|
@ -231,7 +231,7 @@ let
|
|||
# read-only to the seal however it is owned, and the group is the only
|
||||
# handle on a uid allocated at start. Dropping either surfaces as a
|
||||
# pkcs11 error deep in a library, naming neither the mount nor the user.
|
||||
name = "the store's seal may write the token directory, and is in its group";
|
||||
name = "the store's seal may write the token directory, and is in both its groups";
|
||||
ok =
|
||||
let
|
||||
sc = (baoUnits baoPkcs11).openbao.serviceConfig;
|
||||
|
|
@ -240,7 +240,30 @@ let
|
|||
# key being gone, and a select would abort the whole run with a nix
|
||||
# trace instead of failing this case by name.
|
||||
builtins.elem "/var/lib/swarm-bao-token" (sc.ReadWritePaths or [ ])
|
||||
&& builtins.elem "swarm-bao-token" (sc.SupplementaryGroups or [ ]);
|
||||
&& builtins.elem "swarm-bao-token" (sc.SupplementaryGroups or [ ])
|
||||
&& builtins.elem "swarm-bao-tpm" (sc.SupplementaryGroups or [ ]);
|
||||
}
|
||||
{
|
||||
# The device node belongs to the HOST and is matched by NUMBER, while the
|
||||
# unit that opens it lives in the container — so the two sides holding
|
||||
# the same gid is the entire mechanism. Letting either side auto-allocate
|
||||
# renders cleanly, deploys cleanly, and leaves a 0660 node the seal
|
||||
# cannot open. Compared rather than each checked against a literal: the
|
||||
# property is that they AGREE, not what they agree on.
|
||||
name = "the TPM group has the same gid on the host and inside the container";
|
||||
ok =
|
||||
let
|
||||
host = baoPkcs11.users.groups.swarm-bao-tpm.gid or null;
|
||||
inner = baoPkcs11.containers.swarm-bao.config.users.groups.swarm-bao-tpm.gid or null;
|
||||
in
|
||||
host != null && host == inner;
|
||||
}
|
||||
{
|
||||
# Absence arm for the case above — a shamir store never opens a TPM, so
|
||||
# it must not claim a device node's group. Without this, pinning the gid
|
||||
# unconditionally would look identical.
|
||||
name = "a shamir store claims no TPM device group";
|
||||
ok = !(baoShamir.users.groups ? swarm-bao-tpm);
|
||||
}
|
||||
{
|
||||
# The store's mTLS identity is a separate trust domain from both CAs in
|
||||
|
|
|
|||
Loading…
Reference in a new issue