mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-11 00:50:43 +02:00
* iam: expose tri-state result from attached policy evaluation evaluateIAMPolicies returned a bool that collapsed explicit Deny and no-match into a single false, so the authorization path could not tell "policies forbid this" from "policies say nothing". Introduce evaluateAttachedIAMPolicies returning Allow/Deny/NoMatch and keep evaluateIAMPolicies as a bool projection for existing callers. This is preparation for unioning native permissions with attached policies while preserving deny-always-wins. * iam: preserve native Admin when IAM policies are attached Attaching an IAM policy routed authorization exclusively to the attached policies, dropping the identity native permissions. A user with native Admin lost all access after attaching a non-granting policy, and stayed locked out if that policy was deleted without being detached first (#11226). Treat a native bare Admin grant as a permission floor that survives attached policies: when the attached policies do not explicitly allow, fall back to isAdmin() on the attached-policy path, and on the IAM integration path allow unless an attached policy explicitly denies. Explicit Deny still wins on both paths. Only bare Admin is consulted because inline policies flatten lossily into Actions (dropping conditions), so scoped actions are not unambiguously native and must keep flowing through the policy engine. * iam: regression tests for native Admin surviving attached policies Reproduces issue #11226: - TestNativeAdminSurvivesAttachedPolicy: a user with native Admin keeps Write access after attaching a policy that does not grant it. - TestNativeAdminSurvivesDeletedPolicy: the same user keeps Write access after the attached policy is deleted without being detached. - TestAttachedPolicyExplicitDenyOverridesNativeAdmin: an explicit Deny in an attached policy still constrains a native admin (deny-always-wins). * iam: apply native Admin floor before IAM principal validation The native Admin floor in authorizeWithIAM ran after the auth-path switch, which denies when no session principal or PrincipalArn is present. An Admin identity without a PrincipalArn (no session token) was therefore denied before the floor executed. Move the floor ahead of the switch and derive the principal for its explicit-deny check with buildPrincipalARN, which already handles identities without a PrincipalArn. Adds a regression case for an Admin identity with an empty PrincipalArn. Addresses CodeRabbit review feedback on PR #11232.
127 lines
4.6 KiB
Go
127 lines
4.6 KiB
Go
package s3api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newNativeFloorTestIAM(t *testing.T) (*IdentityAccessManagement, *Identity) {
|
|
t.Helper()
|
|
mgr := newTestIAMManager(t)
|
|
iam := &IdentityAccessManagement{}
|
|
iam.SetIAMIntegration(NewS3IAMIntegration(mgr, ""))
|
|
|
|
doc, _ := json.Marshal(map[string]interface{}{
|
|
"Version": "2012-10-17",
|
|
"Statement": []map[string]interface{}{
|
|
{"Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*"},
|
|
},
|
|
})
|
|
require.NoError(t, iam.PutPolicy("ListBucketsOnly", string(doc)))
|
|
|
|
identity := &Identity{
|
|
Name: "admin",
|
|
Account: &Account{DisplayName: "admin", Id: "admin"},
|
|
Actions: []Action{s3_constants.ACTION_ADMIN},
|
|
PolicyNames: []string{"ListBucketsOnly"},
|
|
PrincipalArn: "arn:aws:iam::111122223333:user/admin",
|
|
}
|
|
return iam, identity
|
|
}
|
|
|
|
func putObjectRequest() *http.Request {
|
|
return httptest.NewRequest(http.MethodPut, "/mybucket/file.txt", nil)
|
|
}
|
|
|
|
// TestNativeAdminSurvivesAttachedPolicy reproduces issue #11226: attaching an
|
|
// IAM policy to a user with native Admin must not override the native grant.
|
|
// The effective permissions are the union of native permissions and attached
|
|
// policy grants, so a Write the attached policy never mentions still succeeds.
|
|
func TestNativeAdminSurvivesAttachedPolicy(t *testing.T) {
|
|
iam, identity := newNativeFloorTestIAM(t)
|
|
|
|
errCode := iam.VerifyActionPermission(putObjectRequest(), identity,
|
|
s3_constants.ACTION_WRITE, "mybucket", "file.txt")
|
|
assert.Equal(t, s3err.ErrNone, errCode,
|
|
"native Admin must remain effective after attaching a policy")
|
|
}
|
|
|
|
// TestNativeAdminSurvivesDeletedPolicy reproduces the second half of #11226:
|
|
// deleting the attached policy without detaching it first must not leave the
|
|
// user locked out of their native permissions.
|
|
func TestNativeAdminSurvivesDeletedPolicy(t *testing.T) {
|
|
iam, identity := newNativeFloorTestIAM(t)
|
|
|
|
require.NoError(t, iam.DeletePolicy("ListBucketsOnly"))
|
|
|
|
errCode := iam.VerifyActionPermission(putObjectRequest(), identity,
|
|
s3_constants.ACTION_WRITE, "mybucket", "file.txt")
|
|
assert.Equal(t, s3err.ErrNone, errCode,
|
|
"native Admin must remain effective after the attached policy is deleted")
|
|
}
|
|
|
|
// TestNativeAdminSurvivesAttachedPolicyWithoutPrincipalArn covers the IAM
|
|
// integration path when the Admin identity has no PrincipalArn (and no session
|
|
// token), so the auth-path switch would otherwise deny before the native floor.
|
|
func TestNativeAdminSurvivesAttachedPolicyWithoutPrincipalArn(t *testing.T) {
|
|
mgr := newTestIAMManager(t)
|
|
iam := &IdentityAccessManagement{}
|
|
iam.SetIAMIntegration(NewS3IAMIntegration(mgr, ""))
|
|
|
|
doc, _ := json.Marshal(map[string]interface{}{
|
|
"Version": "2012-10-17",
|
|
"Statement": []map[string]interface{}{
|
|
{"Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*"},
|
|
},
|
|
})
|
|
require.NoError(t, iam.PutPolicy("ListBucketsOnly", string(doc)))
|
|
|
|
identity := &Identity{
|
|
Name: "admin",
|
|
Account: &Account{DisplayName: "admin", Id: "admin"},
|
|
Actions: []Action{s3_constants.ACTION_ADMIN},
|
|
PolicyNames: []string{"ListBucketsOnly"},
|
|
}
|
|
|
|
errCode := iam.VerifyActionPermission(putObjectRequest(), identity,
|
|
s3_constants.ACTION_WRITE, "mybucket", "file.txt")
|
|
assert.Equal(t, s3err.ErrNone, errCode,
|
|
"native Admin must remain effective without a PrincipalArn")
|
|
}
|
|
|
|
// TestAttachedPolicyExplicitDenyOverridesNativeAdmin ensures deny-always-wins:
|
|
// an explicit Deny in an attached policy still constrains a native admin.
|
|
func TestAttachedPolicyExplicitDenyOverridesNativeAdmin(t *testing.T) {
|
|
mgr := newTestIAMManager(t)
|
|
iam := &IdentityAccessManagement{}
|
|
iam.SetIAMIntegration(NewS3IAMIntegration(mgr, ""))
|
|
|
|
doc, _ := json.Marshal(map[string]interface{}{
|
|
"Version": "2012-10-17",
|
|
"Statement": []map[string]interface{}{
|
|
{"Effect": "Deny", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::mybucket/*"},
|
|
},
|
|
})
|
|
require.NoError(t, iam.PutPolicy("DenyPutMyBucket", string(doc)))
|
|
|
|
identity := &Identity{
|
|
Name: "admin",
|
|
Account: &Account{DisplayName: "admin", Id: "admin"},
|
|
Actions: []Action{s3_constants.ACTION_ADMIN},
|
|
PolicyNames: []string{"DenyPutMyBucket"},
|
|
PrincipalArn: "arn:aws:iam::111122223333:user/admin",
|
|
}
|
|
|
|
errCode := iam.VerifyActionPermission(putObjectRequest(), identity,
|
|
s3_constants.ACTION_WRITE, "mybucket", "file.txt")
|
|
assert.Equal(t, s3err.ErrAccessDenied, errCode,
|
|
"explicit Deny in an attached policy must override native Admin")
|
|
}
|