feat(iam): implement group inline policy actions (#8992)

* feat(iam): implement group inline policy actions

Add PutGroupPolicy, GetGroupPolicy, DeleteGroupPolicy, and
ListGroupPolicies to both embedded and standalone IAM servers.

The standalone IAM stores group inline policies in a new
GroupInlinePolicies field in the Policies JSON, mirroring the
existing user inline policy pattern. DeleteGroup now also checks
for inline policies before allowing deletion.

* fix: address review feedback for group inline policies

- Embedded IAM: return NotImplemented for group inline policies
  instead of silently succeeding as no-ops (Gemini + CodeRabbit)
- Standalone IAM: recompute member actions after PutGroupPolicy
  and DeleteGroupPolicy (Gemini)
- Add parameter validation for GroupName/PolicyName/PolicyDocument
  on PutGroupPolicy, DeleteGroupPolicy, ListGroupPolicies (Gemini)
- Add UserName validation for ListUserPolicies in standalone IAM
- Call cleanupGroupInlinePolicies from DeleteGroup (Gemini)
- Migrate GroupInlinePolicies on group rename in UpdateGroup (CodeRabbit)
- Fix integration test cleanup order (CodeRabbit)

* fix: persist recomputed actions and improve error handling

- Set changed=true for PutGroupPolicy/DeleteGroupPolicy in standalone
  IAM DoActions so recomputed member actions are persisted (Gemini critical)
- Make cleanupGroupInlinePolicies accept policies parameter to avoid
  redundant I/O, return error (Gemini)
- Make migrateGroupInlinePolicies return error, handle in caller (Gemini)

* fix: include group policies in action recomputation

Extend computeAllActionsForUser to also aggregate group inline
policies and group managed policies when s3cfg is provided.
Previously, group inline policies were stored but never reflected
in member Identity.Actions. (CodeRabbit critical)

* perf: use identity index in recomputeActionsForGroupMembers for O(N+M)

* fix: skip group inline policy integration test on embedded IAM

The embedded IAM returns NotImplemented for group inline policies.
Skip TestIAMGroupInlinePolicy when running against embedded mode
to avoid CI failures in the group integration test matrix.
This commit is contained in:
Chris Lu authored and GitHub committed 2026-04-08 15:57:04 -07:00
1 parent 3af571a5f3
commit e21d7602c3
7 files changed
+573 -10

No files matched your search

+58 -1
View File
@@ -125,6 +125,10 @@ type (
iamAttachGroupPolicyResponse = iamlib.AttachGroupPolicyResponse
iamDetachGroupPolicyResponse = iamlib.DetachGroupPolicyResponse
iamListAttachedGroupPoliciesResponse = iamlib.ListAttachedGroupPoliciesResponse
iamPutGroupPolicyResponse = iamlib.PutGroupPolicyResponse
iamGetGroupPolicyResponse = iamlib.GetGroupPolicyResponse
iamDeleteGroupPolicyResponse = iamlib.DeleteGroupPolicyResponse
iamListGroupPoliciesResponse = iamlib.ListGroupPoliciesResponse
iamListGroupsForUserResponse = iamlib.ListGroupsForUserResponse
)
@@ -1771,6 +1775,31 @@ func (e *EmbeddedIamApi) ListGroupsForUser(s3cfg *iam_pb.S3ApiConfiguration, val
return resp, nil
}
// notImplementedError returns a NotImplemented IAM error for the embedded server.
func notImplementedGroupInlineError() *iamError {
return &iamError{Code: s3err.GetAPIError(s3err.ErrNotImplemented).Code, Error: fmt.Errorf("group inline policies are not supported in embedded IAM mode; use the standalone IAM server or managed policies (AttachGroupPolicy)")}
}
// PutGroupPolicy is not supported in embedded IAM mode.
func (e *EmbeddedIamApi) PutGroupPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamPutGroupPolicyResponse, *iamError) {
return &iamPutGroupPolicyResponse{}, notImplementedGroupInlineError()
}
// GetGroupPolicy is not supported in embedded IAM mode.
func (e *EmbeddedIamApi) GetGroupPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamGetGroupPolicyResponse, *iamError) {
return &iamGetGroupPolicyResponse{}, notImplementedGroupInlineError()
}
// DeleteGroupPolicy is not supported in embedded IAM mode.
func (e *EmbeddedIamApi) DeleteGroupPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamDeleteGroupPolicyResponse, *iamError) {
return &iamDeleteGroupPolicyResponse{}, notImplementedGroupInlineError()
}
// ListGroupPolicies is not supported in embedded IAM mode.
func (e *EmbeddedIamApi) ListGroupPolicies(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (*iamListGroupPoliciesResponse, *iamError) {
return &iamListGroupPoliciesResponse{}, notImplementedGroupInlineError()
}
// 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."
@@ -1925,7 +1954,7 @@ func (e *EmbeddedIamApi) ExecuteAction(ctx context.Context, values url.Values, s
if e.readOnly {
switch action {
case "ListUsers", "ListAccessKeys", "GetUser", "GetUserPolicy", "ListUserPolicies", "ListAttachedUserPolicies", "ListPolicies", "GetPolicy", "ListPolicyVersions", "GetPolicyVersion", "ListServiceAccounts", "GetServiceAccount",
"GetGroup", "ListGroups", "ListAttachedGroupPolicies", "ListGroupsForUser":
"GetGroup", "ListGroups", "ListAttachedGroupPolicies", "GetGroupPolicy", "ListGroupPolicies", "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")}
@@ -2179,6 +2208,34 @@ func (e *EmbeddedIamApi) ExecuteAction(ctx context.Context, values url.Values, s
return nil, iamErr
}
changed = false
case "PutGroupPolicy":
var iamErr *iamError
response, iamErr = e.PutGroupPolicy(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
changed = false
case "GetGroupPolicy":
var iamErr *iamError
response, iamErr = e.GetGroupPolicy(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
changed = false
case "DeleteGroupPolicy":
var iamErr *iamError
response, iamErr = e.DeleteGroupPolicy(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
changed = false
case "ListGroupPolicies":
var iamErr *iamError
response, iamErr = e.ListGroupPolicies(s3cfg, values)
if iamErr != nil {
return nil, iamErr
}
changed = false
case "ListGroupsForUser":
var iamErr *iamError
response, iamErr = e.ListGroupsForUser(s3cfg, values)