mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-20 04:54:24 +02:00
iam: scope CreateServiceAccount's ParentUser by policy resource (#11736)
* iam: scope CreateServiceAccount's ParentUser by policy resource AuthorizeIamAction evaluated the iam:CreateServiceAccount grant against a hardcoded arn:aws:iam:::* resource, so the policy's Resource never had a say; #11218 then bound the target to the caller outright. Evaluate self-target actions against the target identity's ARN instead: a wildcard resource keeps the deliberate any-target grant, a scoped resource confines issuance to the named parent identity, anything else is denied. buildResourceARN gains the ARN pass-through buildS3ResourceArn already has, so iam: ARNs reach the policy engine intact on the attached-policies route. Closes #11735 * ci: retrigger after unrelated weed/storage metrics flake * iam: fold IAM-ARN matching into awsIAMMatch, drop debug test - remove zz_debug_test.go (local debugging leftover) - fold awsIAMMatchResource into awsIAMMatch: the arn:aws:iam:: value prefix selects case-sensitive matching, so no separate name is needed - trim comments --------- Co-authored-by: batonogov <batonogov@users.noreply.github.com> Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
5 files changed
+140
-50
No files matched your search
@@ -15,6 +15,11 @@ const (
|
||||
dataPlanePolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"*"}]}`
|
||||
iamAdminPolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"iam:*","Resource":"*"}]}`
|
||||
iamCreateSvcAcctPolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["iam:CreateServiceAccount"],"Resource":["*"]}]}`
|
||||
// The wildcard ARN shape this authorization always used; it must keep
|
||||
// matching any parent so existing grants survive.
|
||||
iamCreateSvcAcctWildcardArnPolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["iam:CreateServiceAccount"],"Resource":["arn:aws:iam:::*"]}]}`
|
||||
// Scoped to one parent identity.
|
||||
iamCreateSvcAcctZonePolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["iam:CreateServiceAccount"],"Resource":["arn:aws:iam:::user/team-parent"]}]}`
|
||||
)
|
||||
|
||||
func newIamAuthzTestIam(t *testing.T) *IdentityAccessManagement {
|
||||
@@ -95,22 +100,48 @@ func TestAuthorizeIamActionDeniesAnonymous(t *testing.T) {
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), anonymous, "CreateUser", "victim"))
|
||||
}
|
||||
|
||||
// CreateServiceAccount takes its target from the ParentUser parameter, not
|
||||
// UserName. A non-admin holding only iam:CreateServiceAccount must not be able
|
||||
// to mint a service account for another identity (privilege escalation to that
|
||||
// identity's permissions).
|
||||
func TestAuthorizeIamActionCreateServiceAccountBindsTargetToCaller(t *testing.T) {
|
||||
// CreateServiceAccount takes its target from the ParentUser parameter. A
|
||||
// non-admin holding iam:CreateServiceAccount targets the identities its
|
||||
// policy resource covers: a wildcard resource allows any target, a scoped
|
||||
// resource confines the grant to the named parent identity.
|
||||
func TestAuthorizeIamActionCreateServiceAccountScopedByPolicyResource(t *testing.T) {
|
||||
iam := newIamAuthzTestIam(t)
|
||||
require.NoError(t, iam.PutPolicy("CreateSvcAcctPolicy", iamCreateSvcAcctPolicy))
|
||||
require.NoError(t, iam.PutPolicy("CreateSvcAcctWildcardArnPolicy", iamCreateSvcAcctWildcardArnPolicy))
|
||||
require.NoError(t, iam.PutPolicy("CreateSvcAcctZonePolicy", iamCreateSvcAcctZonePolicy))
|
||||
dev := &Identity{Name: "dev", PolicyNames: []string{"CreateSvcAcctPolicy"}}
|
||||
wildcardArn := &Identity{Name: "wildcard-arn", PolicyNames: []string{"CreateSvcAcctWildcardArnPolicy"}}
|
||||
keymaker := &Identity{Name: "keymaker", PolicyNames: []string{"CreateSvcAcctZonePolicy"}}
|
||||
admin := &Identity{Name: "admin", Actions: []Action{s3_constants.ACTION_ADMIN}}
|
||||
|
||||
// Wildcard resource: any target, including the caller itself.
|
||||
assert.Equal(t, s3err.ErrNone,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), dev, "CreateServiceAccount", "dev"))
|
||||
assert.Equal(t, s3err.ErrNone,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), dev, "CreateServiceAccount", ""))
|
||||
assert.Equal(t, s3err.ErrAccessDenied,
|
||||
assert.Equal(t, s3err.ErrNone,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), dev, "CreateServiceAccount", "admin"))
|
||||
|
||||
// Wildcard ARN: any target.
|
||||
assert.Equal(t, s3err.ErrNone,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), wildcardArn, "CreateServiceAccount", "team-parent"))
|
||||
assert.Equal(t, s3err.ErrNone,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), wildcardArn, "CreateServiceAccount", "admin"))
|
||||
|
||||
// Scoped resource: the named parent only. IAM resource matching is
|
||||
// case-sensitive, so a case variant is a different identity.
|
||||
assert.Equal(t, s3err.ErrNone,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), keymaker, "CreateServiceAccount", "team-parent"))
|
||||
assert.Equal(t, s3err.ErrAccessDenied,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), keymaker, "CreateServiceAccount", "Team-Parent"))
|
||||
assert.Equal(t, s3err.ErrAccessDenied,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), keymaker, "CreateServiceAccount", "admin"))
|
||||
assert.Equal(t, s3err.ErrAccessDenied,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), keymaker, "CreateServiceAccount", "keymaker"))
|
||||
assert.Equal(t, s3err.ErrAccessDenied,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), keymaker, "CreateServiceAccount", ""))
|
||||
|
||||
// Admins remain unrestricted.
|
||||
assert.Equal(t, s3err.ErrNone,
|
||||
iam.AuthorizeIamAction(iamPostRequest(""), admin, "CreateServiceAccount", "victim"))
|
||||
}
|
||||
Reference in new issue
Block a user