mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-13 17:48:29 +02:00
* admin: add IsPolicyAttached helper to detect user/group attachments Introduces AdminServer.IsPolicyAttached, which lists the users and groups that still have a managed policy attached, reusing the existing credential manager ListUsers / ListAttachedUserPolicies / ListGroups / GetGroup methods. This is the building block for rejecting policy deletion while a policy is still referenced, so deleted policy names stop lingering in a user attached policy names list (issue #11225). * admin: reject IAM policy deletion while still attached Guards AdminServer.DeletePolicy with the new IsPolicyAttached check and returns the typed ErrPolicyStillAttached error when the policy is still referenced by a user or group. This matches AWS IAM and the existing IAM API handler behavior, fixing the stale reference where a deleted policy name kept showing up in a user attached policy names list (issue #11225). * admin: return 409 Conflict when deleting an attached IAM policy The admin UI DeletePolicy handler now maps ErrPolicyStillAttached to HTTP 409 Conflict instead of 500, so the dashboard can surface the attachment conflict to the user rather than reporting a generic server error. * admin: skip vanished groups when checking policy attachments IsPolicyAttached now treats a group that disappears between ListGroups and GetGroup (credential.ErrGroupNotFound) as no longer attached instead of failing the whole deletion with HTTP 500, matching the IAM API handler which skips vanished groups. * test: assert policy state after deletion paths Strengthen GetPolicy assertions in the policy deletion tests to check the returned policy is non-nil after a rejected deletion and nil after a successful one, not just that no lookup error occurred (GetPolicy returns nil, nil when a policy is absent).
161 lines
5.3 KiB
Go
161 lines
5.3 KiB
Go
package dash
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/credential"
|
|
_ "github.com/seaweedfs/seaweedfs/weed/credential/memory" // register memory store
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
|
|
)
|
|
|
|
func newAdminServerWithMemoryStore(t *testing.T) *AdminServer {
|
|
t.Helper()
|
|
cm, err := credential.NewCredentialManagerWithDefaults(credential.StoreTypeMemory)
|
|
if err != nil {
|
|
t.Fatalf("failed to create credential manager: %v", err)
|
|
}
|
|
return &AdminServer{credentialManager: cm}
|
|
}
|
|
|
|
func samplePolicyDocument() policy_engine.PolicyDocument {
|
|
return policy_engine.PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []policy_engine.PolicyStatement{{
|
|
Effect: policy_engine.PolicyEffectAllow,
|
|
Action: policy_engine.NewStringOrStringSlice("s3:GetObject"),
|
|
Resource: policy_engine.NewStringOrStringSlicePtr("arn:aws:s3:::test/*"),
|
|
}},
|
|
}
|
|
}
|
|
|
|
func TestIsPolicyAttached(t *testing.T) {
|
|
server := newAdminServerWithMemoryStore(t)
|
|
ctx := context.Background()
|
|
const policyName = "policy_a"
|
|
|
|
if err := server.CreatePolicy(policyName, samplePolicyDocument()); err != nil {
|
|
t.Fatalf("CreatePolicy: %v", err)
|
|
}
|
|
|
|
if attached, err := server.IsPolicyAttached(ctx, policyName); err != nil {
|
|
t.Fatalf("IsPolicyAttached: %v", err)
|
|
} else if len(attached) != 0 {
|
|
t.Fatalf("expected no attachments, got %v", attached)
|
|
}
|
|
|
|
if err := server.credentialManager.CreateUser(ctx, &iam_pb.Identity{Name: "alice"}); err != nil {
|
|
t.Fatalf("CreateUser: %v", err)
|
|
}
|
|
if err := server.credentialManager.AttachUserPolicy(ctx, "alice", policyName); err != nil {
|
|
t.Fatalf("AttachUserPolicy: %v", err)
|
|
}
|
|
|
|
attached, err := server.IsPolicyAttached(ctx, policyName)
|
|
if err != nil {
|
|
t.Fatalf("IsPolicyAttached: %v", err)
|
|
}
|
|
if len(attached) != 1 || attached[0] != "user:alice" {
|
|
t.Fatalf("expected [user:alice], got %v", attached)
|
|
}
|
|
|
|
if err := server.credentialManager.CreateGroup(ctx, &iam_pb.Group{Name: "devs", PolicyNames: []string{policyName}}); err != nil {
|
|
t.Fatalf("CreateGroup: %v", err)
|
|
}
|
|
attached, err = server.IsPolicyAttached(ctx, policyName)
|
|
if err != nil {
|
|
t.Fatalf("IsPolicyAttached: %v", err)
|
|
}
|
|
if len(attached) != 2 {
|
|
t.Fatalf("expected 2 attachments, got %v", attached)
|
|
}
|
|
}
|
|
|
|
func TestDeletePolicyRejectsWhenAttachedToUser(t *testing.T) {
|
|
server := newAdminServerWithMemoryStore(t)
|
|
ctx := context.Background()
|
|
const policyName = "policy_u"
|
|
|
|
if err := server.CreatePolicy(policyName, samplePolicyDocument()); err != nil {
|
|
t.Fatalf("CreatePolicy: %v", err)
|
|
}
|
|
if err := server.credentialManager.CreateUser(ctx, &iam_pb.Identity{Name: "bob"}); err != nil {
|
|
t.Fatalf("CreateUser: %v", err)
|
|
}
|
|
if err := server.credentialManager.AttachUserPolicy(ctx, "bob", policyName); err != nil {
|
|
t.Fatalf("AttachUserPolicy: %v", err)
|
|
}
|
|
|
|
if err := server.DeletePolicy(policyName); !errors.Is(err, ErrPolicyStillAttached) {
|
|
t.Fatalf("expected ErrPolicyStillAttached, got %v", err)
|
|
}
|
|
|
|
if p, err := server.GetPolicy(policyName); err != nil {
|
|
t.Fatalf("policy should still exist after rejected deletion: %v", err)
|
|
} else if p == nil {
|
|
t.Fatal("policy should still exist after rejected deletion, got nil")
|
|
}
|
|
|
|
attached, err := server.credentialManager.ListAttachedUserPolicies(ctx, "bob")
|
|
if err != nil {
|
|
t.Fatalf("ListAttachedUserPolicies: %v", err)
|
|
}
|
|
if len(attached) != 1 || attached[0] != policyName {
|
|
t.Fatalf("expected policy %q to remain attached, got %v", policyName, attached)
|
|
}
|
|
|
|
if err := server.credentialManager.DetachUserPolicy(ctx, "bob", policyName); err != nil {
|
|
t.Fatalf("DetachUserPolicy: %v", err)
|
|
}
|
|
if err := server.DeletePolicy(policyName); err != nil {
|
|
t.Fatalf("DeletePolicy after detach failed: %v", err)
|
|
}
|
|
if p, err := server.GetPolicy(policyName); err != nil {
|
|
t.Fatalf("GetPolicy after detach-delete: %v", err)
|
|
} else if p != nil {
|
|
t.Fatalf("policy should be gone after deletion, got %v", p)
|
|
}
|
|
}
|
|
|
|
func TestDeletePolicyRejectsWhenAttachedToGroup(t *testing.T) {
|
|
server := newAdminServerWithMemoryStore(t)
|
|
ctx := context.Background()
|
|
const policyName = "policy_g"
|
|
|
|
if err := server.CreatePolicy(policyName, samplePolicyDocument()); err != nil {
|
|
t.Fatalf("CreatePolicy: %v", err)
|
|
}
|
|
if err := server.credentialManager.CreateGroup(ctx, &iam_pb.Group{Name: "team_g", PolicyNames: []string{policyName}}); err != nil {
|
|
t.Fatalf("CreateGroup: %v", err)
|
|
}
|
|
|
|
if err := server.DeletePolicy(policyName); !errors.Is(err, ErrPolicyStillAttached) {
|
|
t.Fatalf("expected ErrPolicyStillAttached, got %v", err)
|
|
}
|
|
|
|
if p, err := server.GetPolicy(policyName); err != nil {
|
|
t.Fatalf("policy should still exist after rejected deletion: %v", err)
|
|
} else if p == nil {
|
|
t.Fatal("policy should still exist after rejected deletion, got nil")
|
|
}
|
|
}
|
|
|
|
func TestDeletePolicySucceedsWhenNotAttached(t *testing.T) {
|
|
server := newAdminServerWithMemoryStore(t)
|
|
const policyName = "policy_free"
|
|
|
|
if err := server.CreatePolicy(policyName, samplePolicyDocument()); err != nil {
|
|
t.Fatalf("CreatePolicy: %v", err)
|
|
}
|
|
if err := server.DeletePolicy(policyName); err != nil {
|
|
t.Fatalf("DeletePolicy for unattached policy failed: %v", err)
|
|
}
|
|
if p, err := server.GetPolicy(policyName); err != nil {
|
|
t.Fatalf("GetPolicy after delete: %v", err)
|
|
} else if p != nil {
|
|
t.Fatalf("policy should be gone after deletion, got %v", p)
|
|
}
|
|
}
|