Browse Source

iam: add group management handlers to embedded IAM API

Add CreateGroup, DeleteGroup, GetGroup, ListGroups, AddUserToGroup,
RemoveUserFromGroup, AttachGroupPolicy, DetachGroupPolicy,
ListAttachedGroupPolicies, and ListGroupsForUser handlers with
dispatch in ExecuteAction.
pull/8560/head
Chris Lu 2 days ago
parent
commit
38d504d182
  1. 310
      weed/s3api/s3api_embedded_iam.go

310
weed/s3api/s3api_embedded_iam.go

@ -113,6 +113,17 @@ type (
iamListServiceAccountsResponse = iamlib.ListServiceAccountsResponse
iamGetServiceAccountResponse = iamlib.GetServiceAccountResponse
iamUpdateServiceAccountResponse = iamlib.UpdateServiceAccountResponse
// Group response types
iamCreateGroupResponse = iamlib.CreateGroupResponse
iamDeleteGroupResponse = iamlib.DeleteGroupResponse
iamGetGroupResponse = iamlib.GetGroupResponse
iamListGroupsResponse = iamlib.ListGroupsResponse
iamAddUserToGroupResponse = iamlib.AddUserToGroupResponse
iamRemoveUserFromGroupResponse = iamlib.RemoveUserFromGroupResponse
iamAttachGroupPolicyResponse = iamlib.AttachGroupPolicyResponse
iamDetachGroupPolicyResponse = iamlib.DetachGroupPolicyResponse
iamListAttachedGroupPoliciesResponse = iamlib.ListAttachedGroupPoliciesResponse
iamListGroupsForUserResponse = iamlib.ListGroupsForUserResponse
)
// Helper function wrappers using shared package
@ -1405,6 +1416,241 @@ func (e *EmbeddedIamApi) UpdateServiceAccount(s3cfg *iam_pb.S3ApiConfiguration,
return resp, &iamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("service account %s not found", saId)}
}
// Group Management Handlers
func (e *EmbeddedIamApi) CreateGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamCreateGroupResponse, *iamError) {
resp := &iamCreateGroupResponse{}
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 (e *EmbeddedIamApi) DeleteGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamDeleteGroupResponse, *iamError) {
resp := &iamDeleteGroupResponse{}
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). Remove all members first", groupName, len(g.Members))}
}
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 (e *EmbeddedIamApi) GetGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamGetGroupResponse, *iamError) {
resp := &iamGetGroupResponse{}
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 {
memberName := member
resp.GetGroupResult.Users = append(resp.GetGroupResult.Users, &iam.User{UserName: &memberName})
}
return resp, nil
}
}
return resp, &iamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
}
func (e *EmbeddedIamApi) ListGroups(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) *iamListGroupsResponse {
resp := &iamListGroupsResponse{}
for _, g := range s3cfg.Groups {
name := g.Name
resp.ListGroupsResult.Groups = append(resp.ListGroupsResult.Groups, &iam.Group{GroupName: &name})
}
return resp
}
func (e *EmbeddedIamApi) AddUserToGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamAddUserToGroupResponse, *iamError) {
resp := &iamAddUserToGroupResponse{}
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")}
}
// Verify user exists
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 {
// Check if already a member (idempotent)
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 (e *EmbeddedIamApi) RemoveUserFromGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamRemoveUserFromGroupResponse, *iamError) {
resp := &iamRemoveUserFromGroupResponse{}
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 (e *EmbeddedIamApi) AttachGroupPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamAttachGroupPolicyResponse, *iamError) {
resp := &iamAttachGroupPolicyResponse{}
groupName := values.Get("GroupName")
policyArn := values.Get("PolicyArn")
if groupName == "" {
return resp, &iamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
}
policyName, err := iamPolicyNameFromArn(policyArn)
if err != nil {
return resp, &iamError{Code: iam.ErrCodeInvalidInputException, Error: err}
}
// Verify policy exists
policyFound := false
for _, p := range s3cfg.Policies {
if p.Name == policyName {
policyFound = true
break
}
}
if !policyFound {
return resp, &iamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("policy %s not found", policyName)}
}
for _, g := range s3cfg.Groups {
if g.Name == groupName {
// Check if already attached (idempotent)
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 (e *EmbeddedIamApi) DetachGroupPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamDetachGroupPolicyResponse, *iamError) {
resp := &iamDetachGroupPolicyResponse{}
groupName := values.Get("GroupName")
policyArn := values.Get("PolicyArn")
if groupName == "" {
return resp, &iamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("GroupName is required")}
}
policyName, err := iamPolicyNameFromArn(policyArn)
if err != nil {
return resp, &iamError{Code: iam.ErrCodeInvalidInputException, Error: err}
}
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 (e *EmbeddedIamApi) ListAttachedGroupPolicies(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamListAttachedGroupPoliciesResponse, *iamError) {
resp := &iamListAttachedGroupPoliciesResponse{}
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
resp.ListAttachedGroupPoliciesResult.AttachedPolicies = append(resp.ListAttachedGroupPoliciesResult.AttachedPolicies, &iam.AttachedPolicy{
PolicyName: &pn,
})
}
return resp, nil
}
}
return resp, &iamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("group %s does not exist", groupName)}
}
func (e *EmbeddedIamApi) ListGroupsForUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamListGroupsForUserResponse, *iamError) {
resp := &iamListGroupsForUserResponse{}
userName := values.Get("UserName")
if userName == "" {
return resp, &iamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("UserName is required")}
}
// Verify user exists
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 {
for _, m := range g.Members {
if m == userName {
name := g.Name
resp.ListGroupsForUserResult.Groups = append(resp.ListGroupsForUserResult.Groups, &iam.Group{GroupName: &name})
break
}
}
}
return resp, nil
}
// handleImplicitUsername adds username who signs the request to values if 'username' is not specified.
// According to AWS documentation: "If you do not specify a user name, IAM determines the user name
// implicitly based on the Amazon Web Services access key ID signing the request."
@ -1558,7 +1804,8 @@ func (e *EmbeddedIamApi) ExecuteAction(ctx context.Context, values url.Values, s
action := values.Get("Action")
if e.readOnly {
switch action {
case "ListUsers", "ListAccessKeys", "GetUser", "GetUserPolicy", "ListAttachedUserPolicies", "ListPolicies", "GetPolicy", "ListPolicyVersions", "GetPolicyVersion", "ListServiceAccounts", "GetServiceAccount":
case "ListUsers", "ListAccessKeys", "GetUser", "GetUserPolicy", "ListAttachedUserPolicies", "ListPolicies", "GetPolicy", "ListPolicyVersions", "GetPolicyVersion", "ListServiceAccounts", "GetServiceAccount",
"GetGroup", "ListGroups", "ListAttachedGroupPolicies", "ListGroupsForUser":
// Allowed read-only actions
default:
return nil, &iamError{Code: s3err.GetAPIError(s3err.ErrAccessDenied).Code, Error: fmt.Errorf("IAM write operations are disabled on this server")}
@ -1745,6 +1992,67 @@ func (e *EmbeddedIamApi) ExecuteAction(ctx context.Context, values url.Values, s
if iamErr != nil {
return nil, iamErr
}
// Group actions
case "CreateGroup":
var iamErr *iamError
response, iamErr = e.CreateGroup(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
case "DeleteGroup":
var iamErr *iamError
response, iamErr = e.DeleteGroup(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
case "GetGroup":
var iamErr *iamError
response, iamErr = e.GetGroup(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
changed = false
case "ListGroups":
response = e.ListGroups(s3cfg, values)
changed = false
case "AddUserToGroup":
var iamErr *iamError
response, iamErr = e.AddUserToGroup(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
case "RemoveUserFromGroup":
var iamErr *iamError
response, iamErr = e.RemoveUserFromGroup(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
case "AttachGroupPolicy":
var iamErr *iamError
response, iamErr = e.AttachGroupPolicy(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
case "DetachGroupPolicy":
var iamErr *iamError
response, iamErr = e.DetachGroupPolicy(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
case "ListAttachedGroupPolicies":
var iamErr *iamError
response, iamErr = e.ListAttachedGroupPolicies(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
changed = false
case "ListGroupsForUser":
var iamErr *iamError
response, iamErr = e.ListGroupsForUser(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
changed = false
default:
return nil, &iamError{Code: s3err.GetAPIError(s3err.ErrNotImplemented).Code, Error: errors.New(s3err.GetAPIError(s3err.ErrNotImplemented).Description)}
}

Loading…
Cancel
Save