mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-22 06:22:38 +00:00
iam: refuse DeleteUser while access keys remain
Deleting a user with live credentials orphaned its access keys; AWS answers DeleteConflict until they are removed first. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
3dcc74e93d
commit
85c706e015
@@ -228,6 +228,19 @@ func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName
|
||||
resp = &DeleteUserResponse{}
|
||||
for i, ident := range s3cfg.Identities {
|
||||
if userName == ident.Name {
|
||||
// AWS IAM behavior: prevent deletion if user has dependent resources.
|
||||
if len(ident.ServiceAccountIds) > 0 {
|
||||
return resp, &IamError{
|
||||
Code: iam.ErrCodeDeleteConflictException,
|
||||
Error: fmt.Errorf("cannot delete user %s: user has %d service account(s). Delete service accounts first", userName, len(ident.ServiceAccountIds)),
|
||||
}
|
||||
}
|
||||
if len(ident.Credentials) > 0 {
|
||||
return resp, &IamError{
|
||||
Code: iam.ErrCodeDeleteConflictException,
|
||||
Error: fmt.Errorf("cannot delete user %s: user has %d access key(s). Delete access keys first", userName, len(ident.Credentials)),
|
||||
}
|
||||
}
|
||||
// Clean up any inline policies stored for this user
|
||||
policies := Policies{}
|
||||
if pErr := iama.s3ApiConfig.GetPolicies(&policies); pErr != nil && !errors.Is(pErr, filer_pb.ErrNotFound) {
|
||||
|
||||
@@ -488,6 +488,29 @@ func TestDeleteUser(t *testing.T) {
|
||||
out := DeleteUserResponse{}
|
||||
response, err := executeRequest(req.HTTPRequest, out)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, http.StatusConflict, response.Code)
|
||||
|
||||
listReq, _ := iam.New(session.New()).ListAccessKeysRequest(&iam.ListAccessKeysInput{UserName: userName})
|
||||
_ = listReq.Build()
|
||||
listOut := ListAccessKeysResponse{}
|
||||
listResp, err := executeRequest(listReq.HTTPRequest, listOut)
|
||||
assert.Equal(t, nil, err)
|
||||
var listResult ListAccessKeysResponse
|
||||
assert.Equal(t, nil, xml.Unmarshal(listResp.Body.Bytes(), &listResult))
|
||||
for _, key := range listResult.ListAccessKeysResult.AccessKeyMetadata {
|
||||
delKeyReq, _ := iam.New(session.New()).DeleteAccessKeyRequest(&iam.DeleteAccessKeyInput{
|
||||
UserName: userName,
|
||||
AccessKeyId: key.AccessKeyId,
|
||||
})
|
||||
_ = delKeyReq.Build()
|
||||
_, err := executeRequest(delKeyReq.HTTPRequest, out)
|
||||
assert.Equal(t, nil, err)
|
||||
}
|
||||
|
||||
req, _ = iam.New(session.New()).DeleteUserRequest(params)
|
||||
_ = req.Build()
|
||||
response, err = executeRequest(req.HTTPRequest, out)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
}
|
||||
|
||||
|
||||
@@ -355,8 +355,8 @@ func (e *EmbeddedIamApi) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName s
|
||||
resp := &iamDeleteUserResponse{}
|
||||
for i, ident := range s3cfg.Identities {
|
||||
if userName == ident.Name {
|
||||
// AWS IAM behavior: prevent deletion if user has service accounts
|
||||
// This ensures explicit cleanup and prevents orphaned resources
|
||||
// AWS IAM behavior: prevent deletion if user has dependent resources.
|
||||
// This ensures explicit cleanup and prevents orphaned resources.
|
||||
if len(ident.ServiceAccountIds) > 0 {
|
||||
return resp, &iamError{
|
||||
Code: iam.ErrCodeDeleteConflictException,
|
||||
@@ -364,6 +364,13 @@ func (e *EmbeddedIamApi) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName s
|
||||
userName, len(ident.ServiceAccountIds)),
|
||||
}
|
||||
}
|
||||
if len(ident.Credentials) > 0 {
|
||||
return resp, &iamError{
|
||||
Code: iam.ErrCodeDeleteConflictException,
|
||||
Error: fmt.Errorf("cannot delete user %s: user has %d access key(s). Delete access keys first",
|
||||
userName, len(ident.Credentials)),
|
||||
}
|
||||
}
|
||||
s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
|
||||
// Remove user from all groups
|
||||
for _, g := range s3cfg.Groups {
|
||||
|
||||
@@ -1692,7 +1692,34 @@ func TestEmbeddedIamFullWorkflow(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
// 5. Delete user
|
||||
var accessKeyId string
|
||||
for _, ident := range api.mockConfig.Identities {
|
||||
if ident.Name == "WorkflowUser" && len(ident.Credentials) > 0 {
|
||||
accessKeyId = ident.Credentials[0].AccessKey
|
||||
}
|
||||
}
|
||||
assert.NotEmpty(t, accessKeyId)
|
||||
|
||||
// 5. Deleting a user that still has an access key is refused
|
||||
t.Run("DeleteUserConflict", func(t *testing.T) {
|
||||
params := &iam.DeleteUserInput{UserName: aws.String("WorkflowUser")}
|
||||
req, _ := iam.New(session.New()).DeleteUserRequest(params)
|
||||
_ = req.Build()
|
||||
response, err := executeEmbeddedIamRequest(api, req.HTTPRequest, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusConflict, response.Code)
|
||||
})
|
||||
|
||||
// 6. Delete the access key, then the user
|
||||
t.Run("DeleteAccessKey", func(t *testing.T) {
|
||||
params := &iam.DeleteAccessKeyInput{UserName: aws.String("WorkflowUser"), AccessKeyId: aws.String(accessKeyId)}
|
||||
req, _ := iam.New(session.New()).DeleteAccessKeyRequest(params)
|
||||
_ = req.Build()
|
||||
response, err := executeEmbeddedIamRequest(api, req.HTTPRequest, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
t.Run("DeleteUser", func(t *testing.T) {
|
||||
params := &iam.DeleteUserInput{UserName: aws.String("WorkflowUser")}
|
||||
req, _ := iam.New(session.New()).DeleteUserRequest(params)
|
||||
|
||||
Reference in New Issue
Block a user