@ -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 ) }
}