Preserve serviceaccounts/token granularity in CheckAccess DataAction mapping - #27
Merged
Artem Kolomeetc (arxhive) merged 1 commit intoAug 24, 2026
Conversation
…mapping Upstream Kubernetes treats serviceaccounts/token, the TokenRequest API, as a resource distinct from serviceaccounts. The aggregate-to-edit ClusterRole in plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go grants "create" on serviceaccounts/token in its own rule, separate from the write rule that covers the serviceaccounts object, because issuing a bearer token for a ServiceAccount is a credential-minting operation rather than an update of the object. getResourceAndAction collapsed the subresource, so a create on serviceaccounts/token resolved to the serviceaccounts/write DataAction and token issuance was covered by any grant of ServiceAccount write access. Add the pair to securitySensitiveSubresources so it maps to serviceaccounts/token/action, keeping the same granularity already applied to pods exec/attach/portforward/ proxy, services/proxy and nodes/proxy. Writing the ServiceAccount object itself still maps to serviceaccounts/write, and the impersonate verb still resolves through the verb mapping, so ordinary ServiceAccount CRUD and impersonation are unchanged. Roles that cover serviceaccounts with a wildcard continue to match the new action; roles that enumerate serviceaccounts/write as a leaf action no longer imply token issuance. Signed-off-by: Artem Kolomeetc <akolomeetc@microsoft.com>
Bin Xia (bingosummer)
approved these changes
Aug 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
serviceaccounts/token(the Kubernetes TokenRequest API) now maps to its ownCheckAccess DataAction,
<clusterType>/serviceaccounts/token/action, instead ofcollapsing onto
<clusterType>/serviceaccounts/write.Why
Upstream Kubernetes treats
serviceaccounts/tokenas a resource distinct fromserviceaccounts. Inbootstrappolicy/policy.go,the
system:aggregate-to-editClusterRole grantscreateonserviceaccounts/tokenin a rule of its own, separate from the write rule thatcovers the
serviceaccountsobject:Issuing a bearer token for a ServiceAccount is a credential-minting operation,
not an update of the ServiceAccount object, so native Kubernetes RBAC requires
serviceaccounts/tokento be listed explicitly.getResourceAndActioncollapsed the subresource, so token issuance was implied by any grant of
ServiceAccount write access.
This is the same treatment already applied to
pods/exec,pods/attach,pods/portforward,pods/proxy,services/proxyandnodes/proxy, and itmirrors the existing
serviceaccounts/impersonate/actionDataAction, whichalready gates the sibling sub-capability separately.
Behaviour change
create serviceaccounts/tokenserviceaccounts/writeserviceaccounts/token/actioncreate/update serviceaccountsserviceaccounts/writeimpersonate serviceaccountsserviceaccounts/impersonate/actionImpact on role definitions, verified with
az role definition list:Azure RBAC wildcards span
/, soMicrosoft.ContainerService/managedClusters/serviceaccounts/*(RBAC Writer) and
Microsoft.ContainerService/managedClusters/*(RBAC Admin,RBAC Cluster Admin) continue to authorize token issuance. I confirmed this
against the live Azure RBAC engine rather than inferring it.
serviceaccounts/writeas a leaf action no longer implytoken issuance. This applies to custom roles, and is the intended narrowing.
serviceaccounts/read, socreate serviceaccounts/tokenwas already denied.Because the new action is not yet registered in the
Microsoft.ContainerServiceprovider manifest, a follow-up on the RP/CCP side is needed to register
serviceaccounts/token/actionand to decide, per built-in role, whether tokenissuance stays in the wildcard or is carved out. Guard cannot express that
distinction on its own — this PR is the prerequisite that makes the carve-out
possible, since a
notDataActiononserviceaccounts/writewould break allServiceAccount writes.
Rollout sequencing: custom roles that enumerate leaf DataActions lose token
issuance as soon as this ships, until the new action is registered RP-side.
Register the action before or together with the Guard rollout, and note the
behaviour change in the release note at the AKS Guard version bump.
Testing
Static gates:
go build ./...,go vet ./...,gofmt -lcleangolangci-lint runvia the canonicalghcr.io/appscode/golang-dev:1.25image: 0 issuesTest_getDataActionscases: token-create on AKS and Fleet produce thedistinct action; ServiceAccount object writes and the
impersonateverb areasserted unchanged (53/53
Test_getDataActionssubtests pass)End-to-end, in addition to the unit tests: this build and
masterwere run sideby side in Docker against a mock CheckAccess endpoint that evaluates real Azure
role definitions (including
*-spans-/matching) and records every DataActionon the wire. Real SubjectAccessReviews were POSTed to
/subjectaccessreviewsover mTLS.
exactly 2 rows differ from
master, bothserviceaccounts+token. Everyother emitted DataAction is byte-identical, so the blast radius is limited to
the intended case.
token create, and the reverse) that the two now occupy distinct cache entries
and each reaches the PDP. On
masterthe second request in that sequence wasanswered from the first one's cache entry and never reached the PDP at all.
This matters because
serviceaccounts/tokenis not insubresourceAttributeAllowlist, so cache separation comes entirely from theaction string. Also verified on the CheckAccess v2 path and at the unit level
under both values of
allowSubresourceTypeCheck.notDataActionscarve-out onserviceaccounts/token/actiondenies tokencreate while leaving ServiceAccount writes allowed.
test/e2ewas not run: it requires a live AKS staging cluster whose DNS nolonger resolves. It fails identically on
masterand is excluded from CI bySRC_PKGS.One pre-existing issue was noticed and is not addressed here (it reproduces
identically on
masterand is unrelated to this change): at-v>=7,writeAuthzResponseinserver/utils.godereferencesspec.Extra["oid"]whenspecis nil, panicking on the missing-client-cert and malformed-body paths.The panic is recovered by chi and the client still receives HTTP 400, but with
an empty body. Worth a separate PR.