mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +00:00
fix: honour MaxItems and Marker on IAM list actions (#1125)
This commit is contained in:
@@ -3,6 +3,7 @@ package gateway_iam
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
|
||||
@@ -19,11 +20,31 @@ func GetGroup(accountID string, input *iam.GetGroupInput, svc handlers_iam.IAMSe
|
||||
if input.GroupName == nil || *input.GroupName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.GetGroup(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.GetGroup(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Users, out.Marker = paginate(p, out.Users, userKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func ListGroups(accountID string, input *iam.ListGroupsInput, svc handlers_iam.IAMService) (*iam.ListGroupsOutput, error) {
|
||||
return svc.ListGroups(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListGroups(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Groups, out.Marker = paginate(p, out.Groups, groupKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func DeleteGroup(accountID string, input *iam.DeleteGroupInput, svc handlers_iam.IAMService) (*iam.DeleteGroupOutput, error) {
|
||||
@@ -57,7 +78,17 @@ func ListGroupsForUser(accountID string, input *iam.ListGroupsForUserInput, svc
|
||||
if input.UserName == nil || *input.UserName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListGroupsForUser(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListGroupsForUser(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Groups, out.Marker = paginate(p, out.Groups, groupKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func AttachGroupPolicy(accountID string, input *iam.AttachGroupPolicyInput, svc handlers_iam.IAMService) (*iam.AttachGroupPolicyOutput, error) {
|
||||
@@ -84,7 +115,17 @@ func ListAttachedGroupPolicies(accountID string, input *iam.ListAttachedGroupPol
|
||||
if input.GroupName == nil || *input.GroupName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListAttachedGroupPolicies(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListAttachedGroupPolicies(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.AttachedPolicies, out.Marker = paginate(p, out.AttachedPolicies, attachedPolicyKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func PutGroupPolicy(accountID string, input *iam.PutGroupPolicyInput, svc handlers_iam.IAMService) (*iam.PutGroupPolicyOutput, error) {
|
||||
@@ -129,5 +170,15 @@ func ListGroupPolicies(accountID string, input *iam.ListGroupPoliciesInput, svc
|
||||
if input.GroupName == nil || *input.GroupName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListGroupPolicies(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListGroupPolicies(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.PolicyNames, out.Marker = paginate(p, out.PolicyNames, aws.StringValue)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -34,10 +34,16 @@ func GetInstanceProfile(accountID string, input *iam.GetInstanceProfileInput, sv
|
||||
}
|
||||
|
||||
func ListInstanceProfiles(accountID string, input *iam.ListInstanceProfilesInput, svc handlers_iam.IAMService) (*iam.ListInstanceProfilesOutput, error) {
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListInstanceProfiles(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.InstanceProfiles, out.Marker = paginate(p, out.InstanceProfiles, instanceProfileKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
encodeInstanceProfileDocuments(out.InstanceProfiles...)
|
||||
return out, nil
|
||||
}
|
||||
@@ -82,10 +88,16 @@ func ListInstanceProfilesForRole(accountID string, input *iam.ListInstanceProfil
|
||||
if input.RoleName == nil || *input.RoleName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListInstanceProfilesForRole(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.InstanceProfiles, out.Marker = paginate(p, out.InstanceProfiles, instanceProfileKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
encodeInstanceProfileDocuments(out.InstanceProfiles...)
|
||||
return out, nil
|
||||
}
|
||||
@@ -134,5 +146,15 @@ func ListInstanceProfileTags(accountID string, input *iam.ListInstanceProfileTag
|
||||
if input.InstanceProfileName == nil || *input.InstanceProfileName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListInstanceProfileTags(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListInstanceProfileTags(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Tags, out.Marker = paginate(p, out.Tags, tagKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package gateway_iam
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
|
||||
@@ -19,7 +20,17 @@ func ListAccessKeys(accountID string, input *iam.ListAccessKeysInput, svc handle
|
||||
if input.UserName == nil || *input.UserName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListAccessKeys(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListAccessKeys(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.AccessKeyMetadata, out.Marker = paginate(p, out.AccessKeyMetadata, accessKeyKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func DeleteAccessKey(accountID string, input *iam.DeleteAccessKeyInput, svc handlers_iam.IAMService) (*iam.DeleteAccessKeyOutput, error) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package gateway_iam
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
|
||||
@@ -57,5 +58,15 @@ func ListOpenIDConnectProviderTags(accountID string, input *iam.ListOpenIDConnec
|
||||
if input.OpenIDConnectProviderArn == nil || *input.OpenIDConnectProviderArn == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListOpenIDConnectProviderTags(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListOpenIDConnectProviderTags(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Tags, out.Marker = paginate(p, out.Tags, tagKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package gateway_iam
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/base64"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
)
|
||||
|
||||
// AWS's page size when MaxItems is omitted, and the largest MaxItems it accepts.
|
||||
const (
|
||||
defaultMaxItems = 100
|
||||
maxMaxItems = 1000
|
||||
)
|
||||
|
||||
// pager is a validated Marker/MaxItems pair. Pages are cut in the gateway, after
|
||||
// the service has filtered, so in-process callers of the service still see the
|
||||
// whole list.
|
||||
type pager struct {
|
||||
after string
|
||||
resuming bool
|
||||
size int
|
||||
}
|
||||
|
||||
// newPager validates the request before the service runs, so a bad Marker or
|
||||
// MaxItems fails the call whatever the listed entity's state.
|
||||
func newPager(marker *string, maxItems *int64) (pager, error) {
|
||||
p := pager{size: defaultMaxItems}
|
||||
if maxItems != nil {
|
||||
n := *maxItems
|
||||
if n < 1 {
|
||||
return pager{}, awserrors.Errorf(awserrors.ErrorValidationError,
|
||||
"1 validation error detected: Value '%d' at 'maxItems' failed to satisfy constraint: Member must have value greater than or equal to 1", n)
|
||||
}
|
||||
if n > maxMaxItems {
|
||||
return pager{}, awserrors.Errorf(awserrors.ErrorValidationError,
|
||||
"1 validation error detected: Value '%d' at 'maxItems' failed to satisfy constraint: Member must have value less than or equal to %d", n, maxMaxItems)
|
||||
}
|
||||
p.size = int(n)
|
||||
}
|
||||
if marker != nil {
|
||||
after, err := base64.RawURLEncoding.DecodeString(*marker)
|
||||
if err != nil || len(after) == 0 {
|
||||
return pager{}, awserrors.Errorf(awserrors.ErrorValidationError, "Invalid Marker.")
|
||||
}
|
||||
p.after, p.resuming = string(after), true
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// paginate sorts items by key and returns the page after the marker, with the
|
||||
// marker for the next page, nil when this page is the last.
|
||||
func paginate[T any](p pager, items []T, key func(T) string) ([]T, *string) {
|
||||
return paginateFunc(p, items, key, strings.Compare)
|
||||
}
|
||||
|
||||
// paginateFunc is paginate for a list whose order is not ascending by key. The
|
||||
// marker holds the last key returned rather than an offset, so a page boundary
|
||||
// survives entries being added or removed between calls.
|
||||
func paginateFunc[T any](p pager, items []T, key func(T) string, cmp func(a, b string) int) ([]T, *string) {
|
||||
slices.SortStableFunc(items, func(a, b T) int { return cmp(key(a), key(b)) })
|
||||
start := 0
|
||||
if p.resuming {
|
||||
i, found := slices.BinarySearchFunc(items, p.after, func(item T, after string) int { return cmp(key(item), after) })
|
||||
if found {
|
||||
i++
|
||||
}
|
||||
start = i
|
||||
}
|
||||
end := min(start+p.size, len(items))
|
||||
page := items[start:end]
|
||||
if end == len(items) {
|
||||
return page, nil
|
||||
}
|
||||
return page, aws.String(base64.RawURLEncoding.EncodeToString([]byte(key(page[len(page)-1]))))
|
||||
}
|
||||
|
||||
// Sort keys. A managed policy's name is unique only within its account, so the
|
||||
// ARN breaks the tie between a customer policy and an AWS one of the same name.
|
||||
func userKey(u *iam.User) string { return aws.StringValue(u.UserName) }
|
||||
func roleKey(r *iam.Role) string { return aws.StringValue(r.RoleName) }
|
||||
func groupKey(g *iam.Group) string { return aws.StringValue(g.GroupName) }
|
||||
func tagKey(t *iam.Tag) string { return aws.StringValue(t.Key) }
|
||||
func accessKeyKey(k *iam.AccessKeyMetadata) string { return aws.StringValue(k.AccessKeyId) }
|
||||
func instanceProfileKey(p *iam.InstanceProfile) string { return aws.StringValue(p.InstanceProfileName) }
|
||||
func policyKey(p *iam.Policy) string {
|
||||
return aws.StringValue(p.PolicyName) + "\x00" + aws.StringValue(p.Arn)
|
||||
}
|
||||
func attachedPolicyKey(p *iam.AttachedPolicy) string {
|
||||
return aws.StringValue(p.PolicyName) + "\x00" + aws.StringValue(p.PolicyArn)
|
||||
}
|
||||
|
||||
func policyVersionKey(v *iam.PolicyVersion) string { return aws.StringValue(v.VersionId) }
|
||||
|
||||
// newestVersionFirst orders version IDs (v1, v2, ...) by descending number, the
|
||||
// order ListPolicyVersions returns them in.
|
||||
func newestVersionFirst(a, b string) int {
|
||||
return cmp.Compare(versionNumber(b), versionNumber(a))
|
||||
}
|
||||
|
||||
func versionNumber(id string) int {
|
||||
n, _ := strconv.Atoi(strings.TrimPrefix(id, "v"))
|
||||
return n
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package gateway_iam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// listStub serves fixed lists in whatever order the test gives, as the KV
|
||||
// bucket does, and counts service calls.
|
||||
type listStub struct {
|
||||
stubIAMService
|
||||
|
||||
users []string
|
||||
versions []string
|
||||
entities func() *iam.ListEntitiesForPolicyOutput
|
||||
calls int
|
||||
}
|
||||
|
||||
func (s *listStub) ListUsers(_ string, _ *iam.ListUsersInput) (*iam.ListUsersOutput, error) {
|
||||
s.calls++
|
||||
users := make([]*iam.User, 0, len(s.users))
|
||||
for _, name := range s.users {
|
||||
users = append(users, &iam.User{UserName: aws.String(name)})
|
||||
}
|
||||
return &iam.ListUsersOutput{Users: users, IsTruncated: aws.Bool(false)}, nil
|
||||
}
|
||||
|
||||
func (s *listStub) ListPolicyVersions(_ string, _ *iam.ListPolicyVersionsInput) (*iam.ListPolicyVersionsOutput, error) {
|
||||
versions := make([]*iam.PolicyVersion, 0, len(s.versions))
|
||||
for _, id := range s.versions {
|
||||
versions = append(versions, &iam.PolicyVersion{VersionId: aws.String(id)})
|
||||
}
|
||||
return &iam.ListPolicyVersionsOutput{Versions: versions, IsTruncated: aws.Bool(false)}, nil
|
||||
}
|
||||
|
||||
func (s *listStub) ListEntitiesForPolicy(_ string, _ *iam.ListEntitiesForPolicyInput) (*iam.ListEntitiesForPolicyOutput, error) {
|
||||
return s.entities(), nil
|
||||
}
|
||||
|
||||
func userNames(users []*iam.User) []string {
|
||||
names := make([]string, 0, len(users))
|
||||
for _, u := range users {
|
||||
names = append(names, aws.StringValue(u.UserName))
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func listUsers(t *testing.T, svc *listStub, marker *string, maxItems int64) *iam.ListUsersOutput {
|
||||
t.Helper()
|
||||
out, err := ListUsers(testAccountID, &iam.ListUsersInput{Marker: marker, MaxItems: aws.Int64(maxItems)}, svc)
|
||||
require.NoError(t, err)
|
||||
return out
|
||||
}
|
||||
|
||||
func TestListUsers_MaxItemsTruncatesAndMarkerResumes(t *testing.T) {
|
||||
svc := &listStub{users: []string{"carol", "alice", "bob"}}
|
||||
|
||||
first := listUsers(t, svc, nil, 2)
|
||||
assert.Equal(t, []string{"alice", "bob"}, userNames(first.Users))
|
||||
assert.True(t, aws.BoolValue(first.IsTruncated))
|
||||
require.NotNil(t, first.Marker)
|
||||
|
||||
second := listUsers(t, svc, first.Marker, 2)
|
||||
assert.Equal(t, []string{"carol"}, userNames(second.Users))
|
||||
assert.False(t, aws.BoolValue(second.IsTruncated))
|
||||
assert.Nil(t, second.Marker)
|
||||
}
|
||||
|
||||
func TestListUsers_DefaultPageIs100(t *testing.T) {
|
||||
svc := &listStub{}
|
||||
for i := range 101 {
|
||||
svc.users = append(svc.users, fmt.Sprintf("user%03d", i))
|
||||
}
|
||||
|
||||
out, err := ListUsers(testAccountID, &iam.ListUsersInput{}, svc)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, out.Users, 100)
|
||||
assert.True(t, aws.BoolValue(out.IsTruncated))
|
||||
require.NotNil(t, out.Marker)
|
||||
}
|
||||
|
||||
func TestListUsers_ExactFitIsNotTruncated(t *testing.T) {
|
||||
svc := &listStub{users: []string{"a", "b"}}
|
||||
|
||||
out := listUsers(t, svc, nil, 2)
|
||||
assert.Len(t, out.Users, 2)
|
||||
assert.False(t, aws.BoolValue(out.IsTruncated))
|
||||
assert.Nil(t, out.Marker)
|
||||
}
|
||||
|
||||
// The marker names the last user returned, so the next page neither repeats nor
|
||||
// skips a user when the list changes around the boundary between calls.
|
||||
func TestListUsers_MarkerSurvivesChangesBetweenPages(t *testing.T) {
|
||||
svc := &listStub{users: []string{"a", "b", "c", "d"}}
|
||||
first := listUsers(t, svc, nil, 2)
|
||||
require.Equal(t, []string{"a", "b"}, userNames(first.Users))
|
||||
|
||||
svc.users = []string{"d", "c", "aa", "a"}
|
||||
second := listUsers(t, svc, first.Marker, 2)
|
||||
assert.Equal(t, []string{"c", "d"}, userNames(second.Users))
|
||||
assert.False(t, aws.BoolValue(second.IsTruncated))
|
||||
}
|
||||
|
||||
func TestListUsers_RejectsBadPagingBeforeListing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input *iam.ListUsersInput
|
||||
}{
|
||||
{"MaxItems zero", &iam.ListUsersInput{MaxItems: aws.Int64(0)}},
|
||||
{"MaxItems above 1000", &iam.ListUsersInput{MaxItems: aws.Int64(1001)}},
|
||||
{"empty Marker", &iam.ListUsersInput{Marker: aws.String("")}},
|
||||
{"undecodable Marker", &iam.ListUsersInput{Marker: aws.String("not a marker!")}},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
svc := &listStub{users: []string{"a"}}
|
||||
_, err := ListUsers(testAccountID, tc.input, svc)
|
||||
require.Error(t, err)
|
||||
assert.True(t, awserrors.IsErrorCode(err, awserrors.ErrorValidationError), "got %v", err)
|
||||
assert.Zero(t, svc.calls)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListUsers_MaxItemsBoundsAccepted(t *testing.T) {
|
||||
svc := &listStub{users: []string{"a", "b"}}
|
||||
assert.Len(t, listUsers(t, svc, nil, 1).Users, 1)
|
||||
assert.Len(t, listUsers(t, svc, nil, 1000).Users, 2)
|
||||
}
|
||||
|
||||
func TestListPolicyVersions_PagesNewestFirst(t *testing.T) {
|
||||
svc := &listStub{versions: []string{"v2", "v10", "v9"}}
|
||||
ids := func(out *iam.ListPolicyVersionsOutput) []string {
|
||||
var got []string
|
||||
for _, v := range out.Versions {
|
||||
got = append(got, aws.StringValue(v.VersionId))
|
||||
}
|
||||
return got
|
||||
}
|
||||
|
||||
first, err := ListPolicyVersions(testAccountID, &iam.ListPolicyVersionsInput{PolicyArn: aws.String("arn"), MaxItems: aws.Int64(2)}, svc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"v10", "v9"}, ids(first))
|
||||
assert.True(t, aws.BoolValue(first.IsTruncated))
|
||||
|
||||
second, err := ListPolicyVersions(testAccountID, &iam.ListPolicyVersionsInput{PolicyArn: aws.String("arn"), MaxItems: aws.Int64(2), Marker: first.Marker}, svc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"v2"}, ids(second))
|
||||
assert.False(t, aws.BoolValue(second.IsTruncated))
|
||||
}
|
||||
|
||||
// Groups, roles and users share one MaxItems, so a page can end partway
|
||||
// through one list and the next page picks up in it.
|
||||
func TestListEntitiesForPolicy_PagesAcrossAllThreeLists(t *testing.T) {
|
||||
svc := &listStub{entities: func() *iam.ListEntitiesForPolicyOutput {
|
||||
return &iam.ListEntitiesForPolicyOutput{
|
||||
PolicyUsers: []*iam.PolicyUser{{UserName: aws.String("u1")}},
|
||||
PolicyRoles: []*iam.PolicyRole{{RoleName: aws.String("r2")}, {RoleName: aws.String("r1")}},
|
||||
PolicyGroups: []*iam.PolicyGroup{{GroupName: aws.String("g1")}},
|
||||
}
|
||||
}}
|
||||
input := &iam.ListEntitiesForPolicyInput{PolicyArn: aws.String("arn"), MaxItems: aws.Int64(2)}
|
||||
|
||||
first, err := ListEntitiesForPolicy(testAccountID, input, svc)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, first.PolicyGroups, 1)
|
||||
require.Len(t, first.PolicyRoles, 1)
|
||||
assert.Equal(t, "r1", aws.StringValue(first.PolicyRoles[0].RoleName))
|
||||
assert.Empty(t, first.PolicyUsers)
|
||||
assert.NotNil(t, first.PolicyUsers)
|
||||
assert.True(t, aws.BoolValue(first.IsTruncated))
|
||||
|
||||
input.Marker = first.Marker
|
||||
second, err := ListEntitiesForPolicy(testAccountID, input, svc)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, second.PolicyGroups)
|
||||
require.Len(t, second.PolicyRoles, 1)
|
||||
assert.Equal(t, "r2", aws.StringValue(second.PolicyRoles[0].RoleName))
|
||||
require.Len(t, second.PolicyUsers, 1)
|
||||
assert.False(t, aws.BoolValue(second.IsTruncated))
|
||||
assert.Nil(t, second.Marker)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package gateway_iam
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
|
||||
@@ -46,7 +47,17 @@ func ListPolicyVersions(accountID string, input *iam.ListPolicyVersionsInput, sv
|
||||
if input.PolicyArn == nil || *input.PolicyArn == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListPolicyVersions(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListPolicyVersions(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Versions, out.Marker = paginateFunc(p, out.Versions, policyVersionKey, newestVersionFirst)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func CreatePolicyVersion(accountID string, input *iam.CreatePolicyVersionInput, svc handlers_iam.IAMService) (*iam.CreatePolicyVersionOutput, error) {
|
||||
@@ -80,7 +91,17 @@ func DeletePolicyVersion(accountID string, input *iam.DeletePolicyVersionInput,
|
||||
}
|
||||
|
||||
func ListPolicies(accountID string, input *iam.ListPoliciesInput, svc handlers_iam.IAMService) (*iam.ListPoliciesOutput, error) {
|
||||
return svc.ListPolicies(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListPolicies(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Policies, out.Marker = paginate(p, out.Policies, policyKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func DeletePolicy(accountID string, input *iam.DeletePolicyInput, svc handlers_iam.IAMService) (*iam.DeletePolicyOutput, error) {
|
||||
@@ -94,7 +115,52 @@ func ListEntitiesForPolicy(accountID string, input *iam.ListEntitiesForPolicyInp
|
||||
if input.PolicyArn == nil || *input.PolicyArn == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListEntitiesForPolicy(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListEntitiesForPolicy(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pageEntitiesForPolicy(p, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// policyEntity is one row of ListEntitiesForPolicy's three lists, which page as
|
||||
// a single sequence: groups, then roles, then users, each by name.
|
||||
type policyEntity struct {
|
||||
key string
|
||||
group *iam.PolicyGroup
|
||||
role *iam.PolicyRole
|
||||
user *iam.PolicyUser
|
||||
}
|
||||
|
||||
func pageEntitiesForPolicy(p pager, out *iam.ListEntitiesForPolicyOutput) {
|
||||
all := make([]policyEntity, 0, len(out.PolicyGroups)+len(out.PolicyRoles)+len(out.PolicyUsers))
|
||||
for _, g := range out.PolicyGroups {
|
||||
all = append(all, policyEntity{key: iam.EntityTypeGroup + "\x00" + aws.StringValue(g.GroupName), group: g})
|
||||
}
|
||||
for _, r := range out.PolicyRoles {
|
||||
all = append(all, policyEntity{key: iam.EntityTypeRole + "\x00" + aws.StringValue(r.RoleName), role: r})
|
||||
}
|
||||
for _, u := range out.PolicyUsers {
|
||||
all = append(all, policyEntity{key: iam.EntityTypeUser + "\x00" + aws.StringValue(u.UserName), user: u})
|
||||
}
|
||||
|
||||
page, marker := paginate(p, all, func(e policyEntity) string { return e.key })
|
||||
out.PolicyGroups, out.PolicyRoles, out.PolicyUsers = []*iam.PolicyGroup{}, []*iam.PolicyRole{}, []*iam.PolicyUser{}
|
||||
for _, e := range page {
|
||||
switch {
|
||||
case e.group != nil:
|
||||
out.PolicyGroups = append(out.PolicyGroups, e.group)
|
||||
case e.role != nil:
|
||||
out.PolicyRoles = append(out.PolicyRoles, e.role)
|
||||
default:
|
||||
out.PolicyUsers = append(out.PolicyUsers, e.user)
|
||||
}
|
||||
}
|
||||
out.Marker, out.IsTruncated = marker, aws.Bool(marker != nil)
|
||||
}
|
||||
|
||||
func AttachUserPolicy(accountID string, input *iam.AttachUserPolicyInput, svc handlers_iam.IAMService) (*iam.AttachUserPolicyOutput, error) {
|
||||
@@ -121,7 +187,17 @@ func ListAttachedUserPolicies(accountID string, input *iam.ListAttachedUserPolic
|
||||
if input.UserName == nil || *input.UserName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListAttachedUserPolicies(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListAttachedUserPolicies(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.AttachedPolicies, out.Marker = paginate(p, out.AttachedPolicies, attachedPolicyKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func TagPolicy(accountID string, input *iam.TagPolicyInput, svc handlers_iam.IAMService) (*iam.TagPolicyOutput, error) {
|
||||
@@ -148,5 +224,15 @@ func ListPolicyTags(accountID string, input *iam.ListPolicyTagsInput, svc handle
|
||||
if input.PolicyArn == nil || *input.PolicyArn == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListPolicyTags(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListPolicyTags(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Tags, out.Marker = paginate(p, out.Tags, tagKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package gateway_iam
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
|
||||
@@ -36,10 +37,16 @@ func GetRole(accountID string, input *iam.GetRoleInput, svc handlers_iam.IAMServ
|
||||
}
|
||||
|
||||
func ListRoles(accountID string, input *iam.ListRolesInput, svc handlers_iam.IAMService) (*iam.ListRolesOutput, error) {
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListRoles(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Roles, out.Marker = paginate(p, out.Roles, roleKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
encodeRoleDocuments(out.Roles...)
|
||||
return out, nil
|
||||
}
|
||||
@@ -92,7 +99,17 @@ func ListAttachedRolePolicies(accountID string, input *iam.ListAttachedRolePolic
|
||||
if input.RoleName == nil || *input.RoleName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListAttachedRolePolicies(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListAttachedRolePolicies(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.AttachedPolicies, out.Marker = paginate(p, out.AttachedPolicies, attachedPolicyKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func PutRolePolicy(accountID string, input *iam.PutRolePolicyInput, svc handlers_iam.IAMService) (*iam.PutRolePolicyOutput, error) {
|
||||
@@ -137,7 +154,17 @@ func ListRolePolicies(accountID string, input *iam.ListRolePoliciesInput, svc ha
|
||||
if input.RoleName == nil || *input.RoleName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListRolePolicies(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListRolePolicies(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.PolicyNames, out.Marker = paginate(p, out.PolicyNames, aws.StringValue)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func TagRole(accountID string, input *iam.TagRoleInput, svc handlers_iam.IAMService) (*iam.TagRoleOutput, error) {
|
||||
@@ -164,5 +191,15 @@ func ListRoleTags(accountID string, input *iam.ListRoleTagsInput, svc handlers_i
|
||||
if input.RoleName == nil || *input.RoleName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListRoleTags(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListRoleTags(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Tags, out.Marker = paginate(p, out.Tags, tagKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package gateway_iam
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
|
||||
@@ -23,7 +24,17 @@ func GetUser(accountID string, input *iam.GetUserInput, svc handlers_iam.IAMServ
|
||||
}
|
||||
|
||||
func ListUsers(accountID string, input *iam.ListUsersInput, svc handlers_iam.IAMService) (*iam.ListUsersOutput, error) {
|
||||
return svc.ListUsers(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListUsers(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Users, out.Marker = paginate(p, out.Users, userKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func DeleteUser(accountID string, input *iam.DeleteUserInput, svc handlers_iam.IAMService) (*iam.DeleteUserOutput, error) {
|
||||
@@ -75,7 +86,17 @@ func ListUserPolicies(accountID string, input *iam.ListUserPoliciesInput, svc ha
|
||||
if input.UserName == nil || *input.UserName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListUserPolicies(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListUserPolicies(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.PolicyNames, out.Marker = paginate(p, out.PolicyNames, aws.StringValue)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func TagUser(accountID string, input *iam.TagUserInput, svc handlers_iam.IAMService) (*iam.TagUserOutput, error) {
|
||||
@@ -102,5 +123,15 @@ func ListUserTags(accountID string, input *iam.ListUserTagsInput, svc handlers_i
|
||||
if input.UserName == nil || *input.UserName == "" {
|
||||
return nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
return svc.ListUserTags(accountID, input)
|
||||
p, err := newPager(input.Marker, input.MaxItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := svc.ListUserTags(accountID, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Tags, out.Marker = paginate(p, out.Tags, tagKey)
|
||||
out.IsTruncated = aws.Bool(out.Marker != nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -505,7 +505,7 @@ func (s *IAMServiceImpl) DeleteGroupPolicy(accountID string, input *iam.DeleteGr
|
||||
}
|
||||
|
||||
// ListGroupPolicies returns the names of a group's inline policies, sorted for
|
||||
// deterministic output. Pagination is not implemented: IsTruncated is always false.
|
||||
// deterministic output. Returns the whole list; the gateway pages it.
|
||||
func (s *IAMServiceImpl) ListGroupPolicies(accountID string, input *iam.ListGroupPoliciesInput) (*iam.ListGroupPoliciesOutput, error) {
|
||||
ctx := context.Background()
|
||||
group, err := s.getGroup(ctx, accountID, *input.GroupName)
|
||||
|
||||
@@ -330,8 +330,7 @@ func (s *IAMServiceImpl) UntagInstanceProfile(accountID string, input *iam.Untag
|
||||
return &iam.UntagInstanceProfileOutput{}, nil
|
||||
}
|
||||
|
||||
// ListInstanceProfileTags returns an instance profile's tags. Pagination is
|
||||
// not implemented: IsTruncated is always false.
|
||||
// ListInstanceProfileTags returns an instance profile's tags, all of them; the gateway pages the list.
|
||||
func (s *IAMServiceImpl) ListInstanceProfileTags(accountID string, input *iam.ListInstanceProfileTagsInput) (*iam.ListInstanceProfileTagsOutput, error) {
|
||||
ctx := context.Background()
|
||||
profile, err := s.getInstanceProfile(ctx, accountID, *input.InstanceProfileName)
|
||||
|
||||
@@ -306,8 +306,7 @@ func (s *IAMServiceImpl) UntagOpenIDConnectProvider(accountID string, input *iam
|
||||
return &iam.UntagOpenIDConnectProviderOutput{}, nil
|
||||
}
|
||||
|
||||
// ListOpenIDConnectProviderTags returns an OIDC provider's tags. Pagination is
|
||||
// not implemented: IsTruncated is always false.
|
||||
// ListOpenIDConnectProviderTags returns an OIDC provider's tags, all of them; the gateway pages the list.
|
||||
func (s *IAMServiceImpl) ListOpenIDConnectProviderTags(accountID string, input *iam.ListOpenIDConnectProviderTagsInput) (*iam.ListOpenIDConnectProviderTagsOutput, error) {
|
||||
ctx := context.Background()
|
||||
record, err := s.getOIDCProvider(ctx, accountID, aws.StringValue(input.OpenIDConnectProviderArn))
|
||||
|
||||
@@ -228,7 +228,7 @@ func (s *IAMServiceImpl) GetPolicyVersion(accountID string, input *iam.GetPolicy
|
||||
}
|
||||
|
||||
// ListPolicyVersions returns every version newest first, without documents, as
|
||||
// AWS does. Pagination is not implemented: IsTruncated is always false.
|
||||
// AWS does. Returns the whole list; the gateway pages it.
|
||||
func (s *IAMServiceImpl) ListPolicyVersions(accountID string, input *iam.ListPolicyVersionsInput) (*iam.ListPolicyVersionsOutput, error) {
|
||||
ctx := context.Background()
|
||||
policy, err := s.getPolicyByARN(ctx, accountID, *input.PolicyArn)
|
||||
|
||||
@@ -442,7 +442,7 @@ func (s *IAMServiceImpl) DeleteRolePolicy(accountID string, input *iam.DeleteRol
|
||||
}
|
||||
|
||||
// ListRolePolicies returns the names of a role's inline policies, sorted for
|
||||
// deterministic output. Pagination is not implemented: IsTruncated is always false.
|
||||
// deterministic output. Returns the whole list; the gateway pages it.
|
||||
func (s *IAMServiceImpl) ListRolePolicies(accountID string, input *iam.ListRolePoliciesInput) (*iam.ListRolePoliciesOutput, error) {
|
||||
ctx := context.Background()
|
||||
role, err := s.getRole(ctx, accountID, *input.RoleName)
|
||||
@@ -503,8 +503,7 @@ func (s *IAMServiceImpl) UntagRole(accountID string, input *iam.UntagRoleInput)
|
||||
return &iam.UntagRoleOutput{}, nil
|
||||
}
|
||||
|
||||
// ListRoleTags returns a role's tags. Pagination is not implemented:
|
||||
// IsTruncated is always false.
|
||||
// ListRoleTags returns a role's tags, all of them; the gateway pages the list.
|
||||
func (s *IAMServiceImpl) ListRoleTags(accountID string, input *iam.ListRoleTagsInput) (*iam.ListRoleTagsOutput, error) {
|
||||
ctx := context.Background()
|
||||
role, err := s.getRole(ctx, accountID, *input.RoleName)
|
||||
|
||||
@@ -1685,7 +1685,7 @@ func (s *IAMServiceImpl) DeleteUserPolicy(accountID string, input *iam.DeleteUse
|
||||
}
|
||||
|
||||
// ListUserPolicies returns the names of a user's inline policies, sorted for
|
||||
// deterministic output. Pagination is not implemented: IsTruncated is always false.
|
||||
// deterministic output. Returns the whole list; the gateway pages it.
|
||||
func (s *IAMServiceImpl) ListUserPolicies(accountID string, input *iam.ListUserPoliciesInput) (*iam.ListUserPoliciesOutput, error) {
|
||||
ctx := context.Background()
|
||||
user, err := s.getUser(ctx, accountID, *input.UserName)
|
||||
@@ -1765,8 +1765,7 @@ func (s *IAMServiceImpl) UntagUser(accountID string, input *iam.UntagUserInput)
|
||||
return &iam.UntagUserOutput{}, nil
|
||||
}
|
||||
|
||||
// ListUserTags returns a user's tags. Pagination is not implemented:
|
||||
// IsTruncated is always false.
|
||||
// ListUserTags returns a user's tags, all of them; the gateway pages the list.
|
||||
func (s *IAMServiceImpl) ListUserTags(accountID string, input *iam.ListUserTagsInput) (*iam.ListUserTagsOutput, error) {
|
||||
ctx := context.Background()
|
||||
user, err := s.getUser(ctx, accountID, *input.UserName)
|
||||
@@ -1818,8 +1817,7 @@ func (s *IAMServiceImpl) UntagPolicy(accountID string, input *iam.UntagPolicyInp
|
||||
return &iam.UntagPolicyOutput{}, nil
|
||||
}
|
||||
|
||||
// ListPolicyTags returns a policy's tags. Pagination is not implemented:
|
||||
// IsTruncated is always false.
|
||||
// ListPolicyTags returns a policy's tags, all of them; the gateway pages the list.
|
||||
func (s *IAMServiceImpl) ListPolicyTags(accountID string, input *iam.ListPolicyTagsInput) (*iam.ListPolicyTagsOutput, error) {
|
||||
ctx := context.Background()
|
||||
policy, err := s.getPolicyByARN(ctx, accountID, *input.PolicyArn)
|
||||
|
||||
@@ -229,7 +229,7 @@ describe("queryFn", () => {
|
||||
it("iamUsersQueryOptions sends ListUsersCommand", async () => {
|
||||
await callQueryFn(iamUsersQueryOptions)
|
||||
expect(mockSend).toHaveBeenCalledOnce()
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({})
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ MaxItems: 1000 })
|
||||
})
|
||||
|
||||
it("iamUserQueryOptions sends GetUserCommand with userName", async () => {
|
||||
@@ -248,7 +248,10 @@ describe("queryFn", () => {
|
||||
|
||||
it("iamPoliciesQueryOptions sends ListPoliciesCommand with Local scope", async () => {
|
||||
await callQueryFn(iamPoliciesQueryOptions)
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ Scope: "Local" })
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({
|
||||
Scope: "Local",
|
||||
MaxItems: 1000,
|
||||
})
|
||||
})
|
||||
|
||||
it("iamPolicyQueryOptions sends GetPolicyCommand with policyArn", async () => {
|
||||
@@ -275,7 +278,7 @@ describe("queryFn", () => {
|
||||
|
||||
it("iamRolesQueryOptions sends ListRolesCommand", async () => {
|
||||
await callQueryFn(iamRolesQueryOptions)
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({})
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ MaxItems: 1000 })
|
||||
})
|
||||
|
||||
it("iamRoleQueryOptions sends GetRoleCommand with roleName", async () => {
|
||||
@@ -294,7 +297,7 @@ describe("queryFn", () => {
|
||||
|
||||
it("iamInstanceProfilesQueryOptions sends ListInstanceProfilesCommand", async () => {
|
||||
await callQueryFn(iamInstanceProfilesQueryOptions)
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({})
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ MaxItems: 1000 })
|
||||
})
|
||||
|
||||
it("iamInstanceProfileQueryOptions sends GetInstanceProfileCommand", async () => {
|
||||
@@ -313,13 +316,14 @@ describe("queryFn", () => {
|
||||
|
||||
it("iamGroupsQueryOptions sends ListGroupsCommand", async () => {
|
||||
await callQueryFn(iamGroupsQueryOptions)
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({})
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ MaxItems: 1000 })
|
||||
})
|
||||
|
||||
it("iamGroupQueryOptions sends GetGroupCommand with groupName", async () => {
|
||||
await callQueryFn(iamGroupQueryOptions("my-group"))
|
||||
expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({
|
||||
GroupName: "my-group",
|
||||
MaxItems: 1000,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -28,10 +28,13 @@ import { queryOptions } from "@tanstack/react-query"
|
||||
import { getIamClient } from "@/lib/awsClient"
|
||||
import { decodePolicyDocument } from "@/lib/json"
|
||||
|
||||
// IAM returns 100 items when MaxItems is omitted; 1000 is the most it allows.
|
||||
const LIST_MAX_ITEMS = 1000
|
||||
|
||||
export const iamUsersQueryOptions = queryOptions({
|
||||
queryKey: ["iam", "users"],
|
||||
queryFn: async () => {
|
||||
const command = new ListUsersCommand({})
|
||||
const command = new ListUsersCommand({ MaxItems: LIST_MAX_ITEMS })
|
||||
return await getIamClient().send(command)
|
||||
},
|
||||
staleTime: 300_000,
|
||||
@@ -60,7 +63,10 @@ export const iamAccessKeysQueryOptions = (userName: string) =>
|
||||
export const iamPoliciesQueryOptions = queryOptions({
|
||||
queryKey: ["iam", "policies"],
|
||||
queryFn: async () => {
|
||||
const command = new ListPoliciesCommand({ Scope: "Local" })
|
||||
const command = new ListPoliciesCommand({
|
||||
Scope: "Local",
|
||||
MaxItems: LIST_MAX_ITEMS,
|
||||
})
|
||||
return await getIamClient().send(command)
|
||||
},
|
||||
staleTime: 300_000,
|
||||
@@ -136,7 +142,7 @@ export const iamUserPolicyQueryOptions = (
|
||||
export const iamRolesQueryOptions = queryOptions({
|
||||
queryKey: ["iam", "roles"],
|
||||
queryFn: async () => {
|
||||
const command = new ListRolesCommand({})
|
||||
const command = new ListRolesCommand({ MaxItems: LIST_MAX_ITEMS })
|
||||
return await getIamClient().send(command)
|
||||
},
|
||||
staleTime: 300_000,
|
||||
@@ -196,7 +202,9 @@ export const iamRolePolicyQueryOptions = (
|
||||
export const iamInstanceProfilesQueryOptions = queryOptions({
|
||||
queryKey: ["iam", "instance-profiles"],
|
||||
queryFn: async () => {
|
||||
const command = new ListInstanceProfilesCommand({})
|
||||
const command = new ListInstanceProfilesCommand({
|
||||
MaxItems: LIST_MAX_ITEMS,
|
||||
})
|
||||
return await getIamClient().send(command)
|
||||
},
|
||||
staleTime: 300_000,
|
||||
@@ -229,7 +237,7 @@ export const iamInstanceProfilesForRoleQueryOptions = (roleName: string) =>
|
||||
export const iamGroupsQueryOptions = queryOptions({
|
||||
queryKey: ["iam", "groups"],
|
||||
queryFn: async () => {
|
||||
const command = new ListGroupsCommand({})
|
||||
const command = new ListGroupsCommand({ MaxItems: LIST_MAX_ITEMS })
|
||||
return await getIamClient().send(command)
|
||||
},
|
||||
staleTime: 300_000,
|
||||
@@ -239,7 +247,10 @@ export const iamGroupQueryOptions = (groupName: string) =>
|
||||
queryOptions({
|
||||
queryKey: ["iam", "groups", groupName],
|
||||
queryFn: async () => {
|
||||
const command = new GetGroupCommand({ GroupName: groupName })
|
||||
const command = new GetGroupCommand({
|
||||
GroupName: groupName,
|
||||
MaxItems: LIST_MAX_ITEMS,
|
||||
})
|
||||
return await getIamClient().send(command)
|
||||
},
|
||||
staleTime: 300_000,
|
||||
|
||||
Reference in New Issue
Block a user