You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
329 lines
12 KiB
329 lines
12 KiB
package iamapi
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"github.com/aws/aws-sdk-go/service/iam"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
|
)
|
|
|
|
func (iama *IamApiServer) CreateGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*CreateGroupResponse, *IamError) {
|
|
resp := &CreateGroupResponse{}
|
|
groupName := values.Get("GroupName")
|
|
if groupName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
|
|
}
|
|
for _, g := range s3cfg.Groups {
|
|
if g.Name == groupName {
|
|
return resp, &IamError{Code: iam.ErrCodeEntityAlreadyExistsException, Error: fmt.Errorf("group %s already exists", groupName)}
|
|
}
|
|
}
|
|
s3cfg.Groups = append(s3cfg.Groups, &iam_pb.Group{Name: groupName})
|
|
resp.CreateGroupResult.Group.GroupName = &groupName
|
|
return resp, nil
|
|
}
|
|
|
|
func (iama *IamApiServer) DeleteGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*DeleteGroupResponse, *IamError) {
|
|
resp := &DeleteGroupResponse{}
|
|
groupName := values.Get("GroupName")
|
|
if groupName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
|
|
}
|
|
for i, g := range s3cfg.Groups {
|
|
if g.Name == groupName {
|
|
if len(g.Members) > 0 {
|
|
return resp, &IamError{Code: iam.ErrCodeDeleteConflictException, Error: fmt.Errorf("cannot delete group %s: group has %d member(s)", groupName, len(g.Members))}
|
|
}
|
|
if len(g.PolicyNames) > 0 {
|
|
return resp, &IamError{Code: iam.ErrCodeDeleteConflictException, Error: fmt.Errorf("cannot delete group %s: group has %d attached policy(ies)", groupName, len(g.PolicyNames))}
|
|
}
|
|
s3cfg.Groups = append(s3cfg.Groups[:i], s3cfg.Groups[i+1:]...)
|
|
return resp, nil
|
|
}
|
|
}
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
|
|
}
|
|
|
|
func (iama *IamApiServer) UpdateGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*UpdateGroupResponse, *IamError) {
|
|
resp := &UpdateGroupResponse{}
|
|
groupName := values.Get("GroupName")
|
|
if groupName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
|
|
}
|
|
for _, g := range s3cfg.Groups {
|
|
if g.Name == groupName {
|
|
if disabled := values.Get("Disabled"); disabled != "" {
|
|
if disabled != "true" && disabled != "false" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("Disabled must be 'true' or 'false'")}
|
|
}
|
|
g.Disabled = disabled == "true"
|
|
}
|
|
if newName := values.Get("NewGroupName"); newName != "" && newName != g.Name {
|
|
for _, other := range s3cfg.Groups {
|
|
if other.Name == newName {
|
|
return resp, &IamError{Code: iam.ErrCodeEntityAlreadyExistsException, Error: fmt.Errorf("group %s already exists", newName)}
|
|
}
|
|
}
|
|
g.Name = newName
|
|
}
|
|
return resp, nil
|
|
}
|
|
}
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
|
|
}
|
|
|
|
func (iama *IamApiServer) GetGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*GetGroupResponse, *IamError) {
|
|
resp := &GetGroupResponse{}
|
|
groupName := values.Get("GroupName")
|
|
if groupName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
|
|
}
|
|
for _, g := range s3cfg.Groups {
|
|
if g.Name == groupName {
|
|
resp.GetGroupResult.Group.GroupName = &g.Name
|
|
for _, member := range g.Members {
|
|
m := member
|
|
resp.GetGroupResult.Users = append(resp.GetGroupResult.Users, &iam.User{UserName: &m})
|
|
}
|
|
return resp, nil
|
|
}
|
|
}
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
|
|
}
|
|
|
|
func (iama *IamApiServer) ListGroups(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) *ListGroupsResponse {
|
|
resp := &ListGroupsResponse{}
|
|
for _, g := range s3cfg.Groups {
|
|
name := g.Name
|
|
resp.ListGroupsResult.Groups = append(resp.ListGroupsResult.Groups, &iam.Group{GroupName: &name})
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func (iama *IamApiServer) AddUserToGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*AddUserToGroupResponse, *IamError) {
|
|
resp := &AddUserToGroupResponse{}
|
|
groupName := values.Get("GroupName")
|
|
userName := values.Get("UserName")
|
|
if groupName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
|
|
}
|
|
if userName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("UserName is required")}
|
|
}
|
|
userFound := false
|
|
for _, ident := range s3cfg.Identities {
|
|
if ident.Name == userName {
|
|
userFound = true
|
|
break
|
|
}
|
|
}
|
|
if !userFound {
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("user %s does not exist", userName)}
|
|
}
|
|
for _, g := range s3cfg.Groups {
|
|
if g.Name == groupName {
|
|
for _, m := range g.Members {
|
|
if m == userName {
|
|
return resp, nil
|
|
}
|
|
}
|
|
g.Members = append(g.Members, userName)
|
|
return resp, nil
|
|
}
|
|
}
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
|
|
}
|
|
|
|
func (iama *IamApiServer) RemoveUserFromGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*RemoveUserFromGroupResponse, *IamError) {
|
|
resp := &RemoveUserFromGroupResponse{}
|
|
groupName := values.Get("GroupName")
|
|
userName := values.Get("UserName")
|
|
if groupName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
|
|
}
|
|
if userName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("UserName is required")}
|
|
}
|
|
for _, g := range s3cfg.Groups {
|
|
if g.Name == groupName {
|
|
for i, m := range g.Members {
|
|
if m == userName {
|
|
g.Members = append(g.Members[:i], g.Members[i+1:]...)
|
|
return resp, nil
|
|
}
|
|
}
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("user %s is not a member of group %s", userName, groupName)}
|
|
}
|
|
}
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
|
|
}
|
|
|
|
func (iama *IamApiServer) AttachGroupPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*AttachGroupPolicyResponse, *IamError) {
|
|
resp := &AttachGroupPolicyResponse{}
|
|
groupName := values.Get("GroupName")
|
|
policyArn := values.Get("PolicyArn")
|
|
if groupName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
|
|
}
|
|
policyName, iamErr := parsePolicyArn(policyArn)
|
|
if iamErr != nil {
|
|
return resp, iamErr
|
|
}
|
|
// Verify policy exists in the persisted policies store
|
|
policies := Policies{}
|
|
if pErr := iama.s3ApiConfig.GetPolicies(&policies); pErr != nil && !errors.Is(pErr, filer_pb.ErrNotFound) {
|
|
return resp, &IamError{Code: iam.ErrCodeServiceFailureException, Error: pErr}
|
|
}
|
|
if _, exists := policies.Policies[policyName]; !exists {
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("policy %s not found", policyName)}
|
|
}
|
|
for _, g := range s3cfg.Groups {
|
|
if g.Name == groupName {
|
|
for _, p := range g.PolicyNames {
|
|
if p == policyName {
|
|
return resp, nil
|
|
}
|
|
}
|
|
g.PolicyNames = append(g.PolicyNames, policyName)
|
|
return resp, nil
|
|
}
|
|
}
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
|
|
}
|
|
|
|
func (iama *IamApiServer) DetachGroupPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*DetachGroupPolicyResponse, *IamError) {
|
|
resp := &DetachGroupPolicyResponse{}
|
|
groupName := values.Get("GroupName")
|
|
policyArn := values.Get("PolicyArn")
|
|
if groupName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
|
|
}
|
|
policyName, iamErr := parsePolicyArn(policyArn)
|
|
if iamErr != nil {
|
|
return resp, iamErr
|
|
}
|
|
for _, g := range s3cfg.Groups {
|
|
if g.Name == groupName {
|
|
for i, p := range g.PolicyNames {
|
|
if p == policyName {
|
|
g.PolicyNames = append(g.PolicyNames[:i], g.PolicyNames[i+1:]...)
|
|
return resp, nil
|
|
}
|
|
}
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("policy %s is not attached to group %s", policyName, groupName)}
|
|
}
|
|
}
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
|
|
}
|
|
|
|
func (iama *IamApiServer) ListAttachedGroupPolicies(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*ListAttachedGroupPoliciesResponse, *IamError) {
|
|
resp := &ListAttachedGroupPoliciesResponse{}
|
|
groupName := values.Get("GroupName")
|
|
if groupName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
|
|
}
|
|
for _, g := range s3cfg.Groups {
|
|
if g.Name == groupName {
|
|
for _, policyName := range g.PolicyNames {
|
|
pn := policyName
|
|
policyArn := policyArnPrefix + pn
|
|
resp.ListAttachedGroupPoliciesResult.AttachedPolicies = append(resp.ListAttachedGroupPoliciesResult.AttachedPolicies, &iam.AttachedPolicy{
|
|
PolicyName: &pn,
|
|
PolicyArn: &policyArn,
|
|
})
|
|
}
|
|
return resp, nil
|
|
}
|
|
}
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
|
|
}
|
|
|
|
func (iama *IamApiServer) ListGroupsForUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*ListGroupsForUserResponse, *IamError) {
|
|
resp := &ListGroupsForUserResponse{}
|
|
userName := values.Get("UserName")
|
|
if userName == "" {
|
|
return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("UserName is required")}
|
|
}
|
|
userFound := false
|
|
for _, ident := range s3cfg.Identities {
|
|
if ident.Name == userName {
|
|
userFound = true
|
|
break
|
|
}
|
|
}
|
|
if !userFound {
|
|
return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("user %s does not exist", userName)}
|
|
}
|
|
// Build reverse index for efficient lookup
|
|
userGroupsIndex := buildUserGroupsIndex(s3cfg)
|
|
for _, gName := range userGroupsIndex[userName] {
|
|
name := gName
|
|
resp.ListGroupsForUserResult.Groups = append(resp.ListGroupsForUserResult.Groups, &iam.Group{GroupName: &name})
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// removeUserFromAllGroups removes a user from all groups they belong to.
|
|
// Uses a reverse index for efficient lookup of which groups to modify.
|
|
func removeUserFromAllGroups(s3cfg *iam_pb.S3ApiConfiguration, userName string) {
|
|
userGroupsIndex := buildUserGroupsIndex(s3cfg)
|
|
groupNames, found := userGroupsIndex[userName]
|
|
if !found {
|
|
return
|
|
}
|
|
// Build a set for fast group name lookup
|
|
targetGroups := make(map[string]bool, len(groupNames))
|
|
for _, gn := range groupNames {
|
|
targetGroups[gn] = true
|
|
}
|
|
for _, g := range s3cfg.Groups {
|
|
if !targetGroups[g.Name] {
|
|
continue
|
|
}
|
|
for i, m := range g.Members {
|
|
if m == userName {
|
|
g.Members = append(g.Members[:i], g.Members[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// updateUserInGroups updates group membership references when a user is renamed.
|
|
func updateUserInGroups(s3cfg *iam_pb.S3ApiConfiguration, oldUserName, newUserName string) {
|
|
for _, g := range s3cfg.Groups {
|
|
for i, m := range g.Members {
|
|
if m == oldUserName {
|
|
g.Members[i] = newUserName
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// isPolicyAttachedToAnyGroup checks if a policy is attached to any group.
|
|
func isPolicyAttachedToAnyGroup(s3cfg *iam_pb.S3ApiConfiguration, policyName string) (string, bool) {
|
|
for _, g := range s3cfg.Groups {
|
|
for _, p := range g.PolicyNames {
|
|
if p == policyName {
|
|
return g.Name, true
|
|
}
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// buildUserGroupsIndex builds a reverse index mapping usernames to group names.
|
|
func buildUserGroupsIndex(s3cfg *iam_pb.S3ApiConfiguration) map[string][]string {
|
|
index := make(map[string][]string)
|
|
for _, g := range s3cfg.Groups {
|
|
for _, m := range g.Members {
|
|
index[m] = append(index[m], g.Name)
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|