iam: bind CreateServiceAccount ParentUser to the caller (#11218)

* iam: bind CreateServiceAccount target to caller in AuthorizeIamAction

A non-admin holding iam:CreateServiceAccount could pass an arbitrary
ParentUser and mint a service account for any identity, inheriting that
identity permissions. Add a self-target category so a granted non-admin
may only target their own identity; admins remain unrestricted.

* iam: authorize CreateServiceAccount against its ParentUser target

AuthIamManagement passed UserName as the authorization target for every
action, so CreateServiceAccount was authorized with an empty target and
the self-target binding never saw the caller-supplied ParentUser. Pass
ParentUser for that action so the binding takes effect on the live path.

* iam: test CreateServiceAccount binds target to caller

Regression test: a non-admin holding iam:CreateServiceAccount may target
itself but is denied targeting another identity; admins remain
unrestricted.

* iam: authorize CreateServiceAccount against ParentUser on the S3 port

UnifiedPostHandler passed UserName as the authorization target for every
IAM action, so CreateServiceAccount was authorized with an empty target
on the S3-port route and the self-target binding never saw the caller
ParentUser. Extract iamTargetUserName (ParentUser for CreateServiceAccount,
UserName otherwise) and use it from both IAM dispatch surfaces so the
binding applies on the live S3-port path as well as the standalone iam
server.

* iam: test CreateServiceAccount ParentUser binding on the S3 port

End-to-end regression test through UnifiedPostHandler: a non-admin
holding iam:CreateServiceAccount is denied (403) when targeting another
identity and passes authorization when targeting itself.
This commit is contained in:
Chris Lu
2026-09-07 20:25:03 -07:00
committed by GitHub
parent 361fd6b263
commit c0a7dbb2bb
4 changed files with 123 additions and 6 deletions
+1 -1
View File
@@ -742,7 +742,7 @@ func (s3a *S3ApiServer) UnifiedPostHandler(w http.ResponseWriter, r *http.Reques
// UserName comes from the body only, the same place DoActions reads it
// from, so the authorized target and the acted-on target cannot differ.
if s3a.iam.AuthorizeIamAction(r, identity, action, r.PostForm.Get("UserName")) != s3err.ErrNone {
if s3a.iam.AuthorizeIamAction(r, identity, action, iamTargetUserName(action, r)) != s3err.ErrNone {
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return
}