diff --git a/cmd/admin-handlers-users.go b/cmd/admin-handlers-users.go index 8530046b9..eb032672d 100644 --- a/cmd/admin-handlers-users.go +++ b/cmd/admin-handlers-users.go @@ -859,7 +859,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re var sp *policy.Policy if len(updateReq.NewPolicy) > 0 { - sp, err = policy.ParseConfig(bytes.NewReader(updateReq.NewPolicy)) + sp, err = policy.ParseConfigStrict(bytes.NewReader(updateReq.NewPolicy)) if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -1729,7 +1729,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request return } - iamPolicy, err := policy.ParseConfig(bytes.NewReader(iamPolicyBytes)) + iamPolicy, err := policy.ParseConfigStrict(bytes.NewReader(iamPolicyBytes)) if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -2981,7 +2981,7 @@ func commonAddServiceAccount(r *http.Request, ldap bool) (context.Context, auth. var sp *policy.Policy if len(createReq.Policy) > 0 { - sp, err = policy.ParseConfig(bytes.NewReader(createReq.Policy)) + sp, err = policy.ParseConfigStrict(bytes.NewReader(createReq.Policy)) if err != nil { return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", toAdminAPIErr(ctx, err) } diff --git a/cmd/admin-handlers-users_test.go b/cmd/admin-handlers-users_test.go index 828264583..350edf823 100644 --- a/cmd/admin-handlers-users_test.go +++ b/cmd/admin-handlers-users_test.go @@ -204,6 +204,7 @@ func TestIAMInternalIDPServerSuite(t *testing.T) { suite.TestUserCreate(c) suite.TestUserPolicyEscalationBug(c) suite.TestPolicyCreate(c) + suite.TestServiceAccountBareARNPolicyRejected(c) suite.TestCannedPolicies(c) suite.TestGroupAddRemove(c) suite.TestServiceAccountOpsByAdmin(c) @@ -600,6 +601,20 @@ func (s *TestSuiteIAM) TestPolicyCreate(c *check) { c.Fatalf("invalid policy creation success") } + for i, resource := range []string{"arn:aws:s3:::", "*arn:aws:s3:::"} { + barePolicyBytes := fmt.Appendf(nil, `{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Deny", + "Action": ["s3:GetObject"], + "Resource": ["%s"] + }] +}`, resource) + if err = s.adm.AddCannedPolicy(ctx, fmt.Sprintf("%s-bare-%d", policy, i), barePolicyBytes); err == nil { + c.Fatalf("bare ARN policy creation succeeded for %q", resource) + } + } + // 3. Create a user, associate policy and verify access accessKey, secretKey := mustGenerateCredentials(c) err = s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled) @@ -653,6 +668,51 @@ func (s *TestSuiteIAM) TestPolicyCreate(c *check) { } } +func (s *TestSuiteIAM) TestServiceAccountBareARNPolicyRejected(c *check) { + ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) + defer cancel() + + barePolicy := []byte(`{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": ["s3:GetObject"], + "NotResource": ["arn:aws:s3:::"] + }] +}`) + if _, err := s.adm.AddServiceAccount(ctx, madmin.AddServiceAccountReq{ + TargetUser: globalActiveCred.AccessKey, + Policy: barePolicy, + }); err == nil { + c.Fatal("service account creation accepted a bare ARN policy") + } + + validPolicy := []byte(`{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": ["s3:GetObject"], + "Resource": ["arn:aws:s3:::*"] + }] +}`) + credentials, err := s.adm.AddServiceAccount(ctx, madmin.AddServiceAccountReq{ + TargetUser: globalActiveCred.AccessKey, + Policy: validPolicy, + }) + if err != nil { + c.Fatalf("service account creation rejected an explicit resource: %v", err) + } + defer func() { + _ = s.adm.DeleteServiceAccount(ctx, credentials.AccessKey) + }() + + if err = s.adm.UpdateServiceAccount(ctx, credentials.AccessKey, madmin.UpdateServiceAccountReq{ + NewPolicy: barePolicy, + }); err == nil { + c.Fatal("service account update accepted a bare ARN policy") + } +} + func (s *TestSuiteIAM) TestCannedPolicies(c *check) { ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout) defer cancel() diff --git a/go.mod b/go.mod index 2e027f488..284a827b6 100644 --- a/go.mod +++ b/go.mod @@ -11,9 +11,9 @@ replace github.com/minio/mc => github.com/pgsty/mc v0.0.0-20260806055018-b0021fd // Use Pigsty's maintained SILO package fork while preserving upstream import paths. // This retains the LDAP TLS fix tracked in https://github.com/pgsty/silo/issues/15. -// v3.11.0 follows upstream minio/pkg's 3.11 line and carries the -// minio/minio#20449 bucket-write boundary hardening. -replace github.com/minio/pkg/v3 => github.com/pgsty/silo-pkg/v3 v3.11.0 +// v3.12.0 carries the minio/minio#20449 bucket-write boundary hardening and +// rejects bare ARN prefixes on strict policy-write paths. +replace github.com/minio/pkg/v3 => github.com/pgsty/silo-pkg/v3 v3.12.0 // v22.7.0 does not compile on NetBSD because its unix implementation uses // CLOCK_MONOTONIC, which is unavailable there. Keep the last portable release @@ -77,7 +77,7 @@ require ( github.com/minio/madmin-go/v3 v3.0.110 github.com/minio/minio-go/v7 v7.0.99 github.com/minio/mux v1.9.2 - github.com/minio/pkg/v3 v3.11.0 + github.com/minio/pkg/v3 v3.12.0 github.com/minio/selfupdate v0.6.0 github.com/minio/simdjson-go v0.4.5 github.com/minio/sio v0.4.3 @@ -210,7 +210,6 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect github.com/googleapis/gax-go/v2 v2.22.0 // indirect - github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect diff --git a/go.sum b/go.sum index 82c73ff4e..72ba8c3d9 100644 --- a/go.sum +++ b/go.sum @@ -549,8 +549,8 @@ github.com/pgsty/mc v0.0.0-20260806055018-b0021fd01ccb h1:ixZKprZQpUIVrUlOy6H7oL github.com/pgsty/mc v0.0.0-20260806055018-b0021fd01ccb/go.mod h1:cTbS+9jGR4Qs7xTf5DEhmCTbzcDWrKMs8ZmTUnCU49E= github.com/pgsty/silo-console v0.0.0-20260806061103-72fc0a5ea52a h1:JfEQJkBdTwCXSrCv8P0R5tMpzxxhSdUTvg54mHwzxpA= github.com/pgsty/silo-console v0.0.0-20260806061103-72fc0a5ea52a/go.mod h1:7J8wCQsNT5S7GqCHnQqgj0T7Nagp1fklJhfBJI+v0XI= -github.com/pgsty/silo-pkg/v3 v3.11.0 h1:wjN5d+tWD8Twq+e7k/KBBVhnWXC8xTIlfTcnGIKkmjc= -github.com/pgsty/silo-pkg/v3 v3.11.0/go.mod h1:E2AB4oOgfDeb9In1KDBTrn9wzfvr0WzoPkbXW7wbwBQ= +github.com/pgsty/silo-pkg/v3 v3.12.0 h1:1Bjqjb3KCt0oYhBLpH7W/e/5khTUoIgXWA12An1fbUc= +github.com/pgsty/silo-pkg/v3 v3.12.0/go.mod h1:ohHtpAK7kBCffdMT4oqUbtkoZMHxA7znTCPNjrYw86I= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pierrec/lz4/v4 v4.1.29 h1:CDQY6qZOLI4DW0Nx6R1vRrifrCeQHnNXkMb0hZWXFjg=