diff --git a/lib/srv/discovery/fetchers/aws-sync/merge.go b/lib/srv/discovery/fetchers/aws-sync/merge.go index 0ad2d198e9b8b..6847243e13782 100644 --- a/lib/srv/discovery/fetchers/aws-sync/merge.go +++ b/lib/srv/discovery/fetchers/aws-sync/merge.go @@ -58,24 +58,24 @@ func MergeResources(results ...*Resources) *Resources { } func deduplicateResources(result *Resources) { - result.Users = deduplicateSlice(result.Users, usersUniqueKeyFunc) - result.UserInlinePolicies = deduplicateSlice(result.UserInlinePolicies, usersInlinePolicyUniqueKeyFunc) - result.UserAttachedPolicies = deduplicateSlice(result.UserAttachedPolicies, usersAttachedPoliciesUniqueKeyFunc) - result.UserGroups = deduplicateSlice(result.UserGroups, userGroupUniqueKeyFunc) - result.Groups = deduplicateSlice(result.Groups, groupUniqueKeyFunc) - result.GroupInlinePolicies = deduplicateSlice(result.GroupInlinePolicies, groupInlinePolicyUniqueKeyFunc) - result.GroupAttachedPolicies = deduplicateSlice(result.GroupAttachedPolicies, groupAttachedPolicyUniqueKeyFunc) - result.Instances = deduplicateSlice(result.Instances, instancesUniqueKeyFunc) - result.Policies = deduplicateSlice(result.Policies, policyUniqueKeyFunc) - result.S3Buckets = deduplicateSlice(result.S3Buckets, s3UniqueKeyFunc) - result.Roles = deduplicateSlice(result.Roles, roleUniqueKeyFunc) - result.RoleInlinePolicies = deduplicateSlice(result.RoleInlinePolicies, roleInlinePolicyUniqueKeyFunc) - result.RoleAttachedPolicies = deduplicateSlice(result.RoleAttachedPolicies, roleAttachedPoliciesUniqueKeyFunc) - result.InstanceProfiles = deduplicateSlice(result.InstanceProfiles, instanceProfileUniqueKeyFunc) - result.AssociatedAccessPolicies = deduplicateSlice(result.AssociatedAccessPolicies, associatedAccessPolicyUniqueKeyFunc) - result.EKSClusters = deduplicateSlice(result.EKSClusters, eksClusterUniqueKeyFunc) - result.AccessEntries = deduplicateSlice(result.AccessEntries, accessEntryUniqueKeyFunc) - result.RDSDatabases = deduplicateSlice(result.RDSDatabases, rdsUniqueKeyFunc) - result.SAMLProviders = deduplicateSlice(result.SAMLProviders, samlUniqueKeyFunc) - result.OIDCProviders = deduplicateSlice(result.OIDCProviders, oidcUniqueKeyFunc) + result.Users = deduplicateSlice(result.Users, usersKey) + result.UserInlinePolicies = deduplicateSlice(result.UserInlinePolicies, userInlinePolKey) + result.UserAttachedPolicies = deduplicateSlice(result.UserAttachedPolicies, userAttchPolKey) + result.UserGroups = deduplicateSlice(result.UserGroups, userGroupKey) + result.Groups = deduplicateSlice(result.Groups, groupKey) + result.GroupInlinePolicies = deduplicateSlice(result.GroupInlinePolicies, grpInlinePolKey) + result.GroupAttachedPolicies = deduplicateSlice(result.GroupAttachedPolicies, grpAttchPolKey) + result.Instances = deduplicateSlice(result.Instances, instanceKey) + result.Policies = deduplicateSlice(result.Policies, policyKey) + result.S3Buckets = deduplicateSlice(result.S3Buckets, s3bucketKey) + result.Roles = deduplicateSlice(result.Roles, roleKey) + result.RoleInlinePolicies = deduplicateSlice(result.RoleInlinePolicies, roleInlinePolKey) + result.RoleAttachedPolicies = deduplicateSlice(result.RoleAttachedPolicies, roleAttchPolKey) + result.InstanceProfiles = deduplicateSlice(result.InstanceProfiles, instanceProfKey) + result.AssociatedAccessPolicies = deduplicateSlice(result.AssociatedAccessPolicies, assocAccPolKey) + result.EKSClusters = deduplicateSlice(result.EKSClusters, eksClusterKey) + result.AccessEntries = deduplicateSlice(result.AccessEntries, accessEntryKey) + result.RDSDatabases = deduplicateSlice(result.RDSDatabases, rdsDbKey) + result.SAMLProviders = deduplicateSlice(result.SAMLProviders, samlProvKey) + result.OIDCProviders = deduplicateSlice(result.OIDCProviders, oidcProvKey) } diff --git a/lib/srv/discovery/fetchers/aws-sync/merge_test.go b/lib/srv/discovery/fetchers/aws-sync/merge_test.go index 97659caef78c8..b109782ed1333 100644 --- a/lib/srv/discovery/fetchers/aws-sync/merge_test.go +++ b/lib/srv/discovery/fetchers/aws-sync/merge_test.go @@ -45,9 +45,9 @@ func TestMergeResources(t *testing.T) { result := MergeResources(&oldResults, &newResults) expected := Resources{ - Users: deduplicateSlice(append(oldUsers, newUsers...), usersUniqueKeyFunc), - Roles: deduplicateSlice(append(oldRoles, newRoles...), roleUniqueKeyFunc), - Instances: deduplicateSlice(append(oldEC2, newEC2...), instancesUniqueKeyFunc), + Users: deduplicateSlice(append(oldUsers, newUsers...), usersKey), + Roles: deduplicateSlice(append(oldRoles, newRoles...), roleKey), + Instances: deduplicateSlice(append(oldEC2, newEC2...), instanceKey), } require.Empty(t, cmp.Diff(&expected, result, protocmp.Transform(), cmpopts.EquateEmpty())) } diff --git a/lib/srv/discovery/fetchers/aws-sync/reconcile.go b/lib/srv/discovery/fetchers/aws-sync/reconcile.go index 726585a36763f..d9bfc3330ce69 100644 --- a/lib/srv/discovery/fetchers/aws-sync/reconcile.go +++ b/lib/srv/discovery/fetchers/aws-sync/reconcile.go @@ -22,642 +22,277 @@ import ( "fmt" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" accessgraphv1alpha "github.com/gravitational/teleport/gen/proto/go/accessgraph/v1alpha" ) +func newResourceList() *accessgraphv1alpha.AWSResourceList { + return &accessgraphv1alpha.AWSResourceList{ + Resources: make([]*accessgraphv1alpha.AWSResource, 0), + } +} + // ReconcileResults reconciles two Resources objects and returns the operations // required to reconcile them into the new state. // It returns two AWSResourceList objects, one for resources to upsert and one // for resources to delete. func ReconcileResults(old *Resources, new *Resources) (upsert, delete *accessgraphv1alpha.AWSResourceList) { - upsert, delete = &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - - for _, results := range []*reconcileIntermeditateResult{ - reconcileUsers(old.Users, new.Users), - reconcileUserInlinePolicies(old.UserInlinePolicies, new.UserInlinePolicies), - reconcileUserAttachedPolicies(old.UserAttachedPolicies, new.UserAttachedPolicies), - reconcileUserGroups(old.UserGroups, new.UserGroups), - reconcileGroups(old.Groups, new.Groups), - reconcileGroupInlinePolicies(old.GroupInlinePolicies, new.GroupInlinePolicies), - reconcileGroupAttachedPolicies(old.GroupAttachedPolicies, new.GroupAttachedPolicies), - reconcilePolicies(old.Policies, new.Policies), - reconcileInstances(old.Instances, new.Instances), - reconcileS3(old.S3Buckets, new.S3Buckets), - reconcileRoles(old.Roles, new.Roles), - reconcileRoleInlinePolicies(old.RoleInlinePolicies, new.RoleInlinePolicies), - reconcileRoleAttachedPolicies(old.RoleAttachedPolicies, new.RoleAttachedPolicies), - reconcileInstanceProfiles(old.InstanceProfiles, new.InstanceProfiles), - reconcileEKSClusters(old.EKSClusters, new.EKSClusters), - reconcileAssociatedAccessPolicy(old.AssociatedAccessPolicies, new.AssociatedAccessPolicies), - reconcileAccessEntry(old.AccessEntries, new.AccessEntries), - reconcileAWSRDS(old.RDSDatabases, new.RDSDatabases), - reconcileSAMLProviders(old.SAMLProviders, new.SAMLProviders), - reconcileOIDCProviders(old.OIDCProviders, new.OIDCProviders), - } { - upsert.Resources = append(upsert.Resources, results.upsert.Resources...) - delete.Resources = append(delete.Resources, results.delete.Resources...) + upsert, delete = newResourceList(), newResourceList() + reconciledResources := []*reconcilePair{ + reconcile(old.Users, new.Users, usersKey, usersWrap), + reconcile(old.UserInlinePolicies, new.UserInlinePolicies, userInlinePolKey, userInlinePolWrap), + reconcile(old.UserAttachedPolicies, new.UserAttachedPolicies, userAttchPolKey, userAttchPolWrap), + reconcile(old.UserGroups, new.UserGroups, userGroupKey, userGroupWrap), + reconcile(old.Groups, new.Groups, groupKey, groupWrap), + reconcile(old.GroupInlinePolicies, new.GroupInlinePolicies, grpInlinePolKey, grpInlinePolWrap), + reconcile(old.GroupAttachedPolicies, new.GroupAttachedPolicies, grpAttchPolKey, grpAttchPolWrap), + reconcile(old.Policies, new.Policies, policyKey, policyWrap), + reconcile(old.Instances, new.Instances, instanceKey, instanceWrap), + reconcile(old.S3Buckets, new.S3Buckets, s3bucketKey, s3bucketWrap), + reconcile(old.Roles, new.Roles, roleKey, roleWrap), + reconcile(old.RoleInlinePolicies, new.RoleInlinePolicies, roleInlinePolKey, roleInlinePolWrap), + reconcile(old.RoleAttachedPolicies, new.RoleAttachedPolicies, roleAttchPolKey, roleAttchPolWrap), + reconcile(old.InstanceProfiles, new.InstanceProfiles, instanceProfKey, instanceProfWrap), + reconcile(old.EKSClusters, new.EKSClusters, eksClusterKey, eksClusterWrap), + reconcile(old.AssociatedAccessPolicies, new.AssociatedAccessPolicies, assocAccPolKey, assocAccPolWrap), + reconcile(old.AccessEntries, new.AccessEntries, accessEntryKey, accessEntryWrap), + reconcile(old.RDSDatabases, new.RDSDatabases, rdsDbKey, rdsDbWrap), + reconcile(old.SAMLProviders, new.SAMLProviders, samlProvKey, samlProvWrap), + reconcile(old.OIDCProviders, new.OIDCProviders, oidcProvKey, oidcProvWrap), + } + for _, res := range reconciledResources { + upsert.Resources = append(upsert.Resources, res.upsert.Resources...) + delete.Resources = append(delete.Resources, res.delete.Resources...) } - return upsert, delete } -type reconcileIntermeditateResult struct { +type reconcilePair struct { upsert, delete *accessgraphv1alpha.AWSResourceList } -func instancesUniqueKeyFunc(instance *accessgraphv1alpha.AWSInstanceV1) string { - return fmt.Sprintf("%s;%s", instance.InstanceId, instance.Region) +func deduplicateSlice[T any](s []T, key func(T) string) []T { + out := make([]T, 0, len(s)) + seen := make(map[string]struct{}) + for _, v := range s { + if _, ok := seen[key(v)]; ok { + continue + } + seen[key(v)] = struct{}{} + out = append(out, v) + } + return out } -func reconcileInstances(old []*accessgraphv1alpha.AWSInstanceV1, new []*accessgraphv1alpha.AWSInstanceV1) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} +func reconcile[T proto.Message]( + oldItems []T, + newItems []T, + keyFn func(T) string, + wrapFn func(T) *accessgraphv1alpha.AWSResource, +) *reconcilePair { + // Remove duplicates from the new items + newItems = deduplicateSlice(newItems, keyFn) + upsertRes := newResourceList() + deleteRes := newResourceList() + + // Delete all old items if there are no new items + if len(newItems) == 0 { + for _, item := range oldItems { + deleteRes.Resources = append(deleteRes.Resources, wrapFn(item)) + } + return &reconcilePair{upsertRes, deleteRes} + } - toAdd, toRemove := reconcile(old, new, instancesUniqueKeyFunc) + // Create all new items if there are no old items + if len(oldItems) == 0 { + for _, item := range newItems { + upsertRes.Resources = append(upsertRes.Resources, wrapFn(item)) + } + return &reconcilePair{upsertRes, deleteRes} + } + + // Map old and new items by their key + oldMap := make(map[string]T, len(oldItems)) + for _, item := range oldItems { + oldMap[keyFn(item)] = item + } + newMap := make(map[string]T, len(newItems)) + for _, item := range newItems { + newMap[keyFn(item)] = item + } - for _, instance := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_Instance{ - Instance: instance, - }, - }) + // Append new or modified items to the upsert list + for _, item := range newItems { + if oldItem, ok := oldMap[keyFn(item)]; !ok || !proto.Equal(oldItem, item) { + upsertRes.Resources = append(upsertRes.Resources, wrapFn(item)) + } } - for _, instance := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_Instance{ - Instance: instance, - }, - }) + + // Append removed items to the delete list + for _, item := range oldItems { + if _, ok := newMap[keyFn(item)]; !ok { + deleteRes.Resources = append(deleteRes.Resources, wrapFn(item)) + } } - return &reconcileIntermeditateResult{upsert, delete} + return &reconcilePair{upsertRes, deleteRes} } -func usersUniqueKeyFunc(user *accessgraphv1alpha.AWSUserV1) string { - return fmt.Sprintf("%s;%s", user.AccountId, user.Arn) +func instanceKey(instance *accessgraphv1alpha.AWSInstanceV1) string { + return fmt.Sprintf("%s;%s", instance.InstanceId, instance.Region) } -func reconcileUsers( - old []*accessgraphv1alpha.AWSUserV1, - new []*accessgraphv1alpha.AWSUserV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} +func instanceWrap(instance *accessgraphv1alpha.AWSInstanceV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_Instance{Instance: instance}} +} - toAdd, toRemove := reconcile(old, new, usersUniqueKeyFunc) - for _, user := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_User{ - User: user, - }, - }) - } - for _, user := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_User{ - User: user, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func usersKey(user *accessgraphv1alpha.AWSUserV1) string { + return fmt.Sprintf("%s;%s", user.AccountId, user.Arn) } -func usersInlinePolicyUniqueKeyFunc(policy *accessgraphv1alpha.AWSUserInlinePolicyV1) string { - return fmt.Sprintf("%s;%s;%s", policy.AccountId, policy.GetUser().GetUserName(), policy.PolicyName) +func usersWrap(user *accessgraphv1alpha.AWSUserV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_User{User: user}} } -func reconcileUserInlinePolicies( - old []*accessgraphv1alpha.AWSUserInlinePolicyV1, - new []*accessgraphv1alpha.AWSUserInlinePolicyV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} +func userInlinePolKey(policy *accessgraphv1alpha.AWSUserInlinePolicyV1) string { + return fmt.Sprintf("%s;%s;%s", policy.AccountId, policy.GetUser().GetUserName(), policy.PolicyName) +} - toAdd, toRemove := reconcile(old, new, usersInlinePolicyUniqueKeyFunc) - for _, policy := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_UserInlinePolicy{ - UserInlinePolicy: policy, - }, - }) - } - for _, policy := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_UserInlinePolicy{ - UserInlinePolicy: policy, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func userInlinePolWrap(policy *accessgraphv1alpha.AWSUserInlinePolicyV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_UserInlinePolicy{UserInlinePolicy: policy}} } -func usersAttachedPoliciesUniqueKeyFunc(policy *accessgraphv1alpha.AWSUserAttachedPolicies) string { +func userAttchPolKey(policy *accessgraphv1alpha.AWSUserAttachedPolicies) string { return fmt.Sprintf("%s;%s", policy.AccountId, policy.User.Arn) } -func reconcileUserAttachedPolicies( - old []*accessgraphv1alpha.AWSUserAttachedPolicies, - new []*accessgraphv1alpha.AWSUserAttachedPolicies, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - - toAdd, toRemove := reconcile(old, new, usersAttachedPoliciesUniqueKeyFunc) - for _, policy := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_UserAttachedPolicies{ - UserAttachedPolicies: policy, - }, - }) - } - for _, policy := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_UserAttachedPolicies{ - UserAttachedPolicies: policy, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func userAttchPolWrap(policy *accessgraphv1alpha.AWSUserAttachedPolicies) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_UserAttachedPolicies{UserAttachedPolicies: policy}} } -func userGroupUniqueKeyFunc(group *accessgraphv1alpha.AWSUserGroupsV1) string { +func userGroupKey(group *accessgraphv1alpha.AWSUserGroupsV1) string { return fmt.Sprintf("%s;%s", group.User.AccountId, group.User.Arn) } -func reconcileUserGroups( - old []*accessgraphv1alpha.AWSUserGroupsV1, - new []*accessgraphv1alpha.AWSUserGroupsV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - - toAdd, toRemove := reconcile(old, new, userGroupUniqueKeyFunc) - for _, group := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_UserGroups{ - UserGroups: group, - }, - }) - } - for _, group := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_UserGroups{ - UserGroups: group, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func userGroupWrap(group *accessgraphv1alpha.AWSUserGroupsV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_UserGroups{UserGroups: group}} } -func groupUniqueKeyFunc(group *accessgraphv1alpha.AWSGroupV1) string { +func groupKey(group *accessgraphv1alpha.AWSGroupV1) string { return fmt.Sprintf("%s;%s", group.AccountId, group.Arn) } -func reconcileGroups( - old []*accessgraphv1alpha.AWSGroupV1, - new []*accessgraphv1alpha.AWSGroupV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - - toAdd, toRemove := reconcile(old, new, groupUniqueKeyFunc) - for _, group := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_Group{ - Group: group, - }, - }) - } - for _, group := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_Group{ - Group: group, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func groupWrap(group *accessgraphv1alpha.AWSGroupV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_Group{Group: group}} } -func groupInlinePolicyUniqueKeyFunc(policy *accessgraphv1alpha.AWSGroupInlinePolicyV1) string { +func grpInlinePolKey(policy *accessgraphv1alpha.AWSGroupInlinePolicyV1) string { return fmt.Sprintf("%s;%s;%s", policy.Group.Name, policy.PolicyName, policy.AccountId) } -func reconcileGroupInlinePolicies( - old []*accessgraphv1alpha.AWSGroupInlinePolicyV1, - new []*accessgraphv1alpha.AWSGroupInlinePolicyV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - - toAdd, toRemove := reconcile(old, new, groupInlinePolicyUniqueKeyFunc) - for _, policy := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_GroupInlinePolicy{ - GroupInlinePolicy: policy, - }, - }) - } - for _, policy := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_GroupInlinePolicy{ - GroupInlinePolicy: policy, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} + +func grpInlinePolWrap(policy *accessgraphv1alpha.AWSGroupInlinePolicyV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_GroupInlinePolicy{GroupInlinePolicy: policy}} } -func groupAttachedPolicyUniqueKeyFunc(policy *accessgraphv1alpha.AWSGroupAttachedPolicies) string { +func grpAttchPolKey(policy *accessgraphv1alpha.AWSGroupAttachedPolicies) string { return fmt.Sprintf("%s;%s", policy.Group.GetAccountId(), policy.Group.Arn) } -func reconcileGroupAttachedPolicies( - old []*accessgraphv1alpha.AWSGroupAttachedPolicies, - new []*accessgraphv1alpha.AWSGroupAttachedPolicies, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - - toAdd, toRemove := reconcile(old, new, groupAttachedPolicyUniqueKeyFunc) - for _, policy := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_GroupAttachedPolicies{ - GroupAttachedPolicies: policy, - }, - }) - } - for _, policy := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_GroupAttachedPolicies{ - GroupAttachedPolicies: policy, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func grpAttchPolWrap(policy *accessgraphv1alpha.AWSGroupAttachedPolicies) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_GroupAttachedPolicies{GroupAttachedPolicies: policy}} } -func policyUniqueKeyFunc(policy *accessgraphv1alpha.AWSPolicyV1) string { +func policyKey(policy *accessgraphv1alpha.AWSPolicyV1) string { return fmt.Sprintf("%s;%s", policy.AccountId, policy.Arn) } -func reconcilePolicies( - old []*accessgraphv1alpha.AWSPolicyV1, - new []*accessgraphv1alpha.AWSPolicyV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, policyUniqueKeyFunc) - for _, policy := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_Policy{ - Policy: policy, - }, - }) - } - for _, policy := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_Policy{ - Policy: policy, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func policyWrap(policy *accessgraphv1alpha.AWSPolicyV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_Policy{Policy: policy}} } -func reconcile[T protoreflect.ProtoMessage](old []T, new []T, key func(T) string) (upsert, delete []T) { - // deduplicateSlice removes duplicates from a slice of T. - new = deduplicateSlice(new, key) - - if len(old) == 0 { - return new, nil - } - if len(new) == 0 { - return nil, old - } - - oldMap := make(map[string]T, len(old)) - for _, item := range old { - oldMap[key(item)] = item - } - - newMap := make(map[string]T, len(new)) - for _, item := range new { - newMap[key(item)] = item - } - - for _, item := range new { - if oldItem, ok := oldMap[key(item)]; !ok || !proto.Equal(oldItem, item) { - upsert = append(upsert, item) - } - } - for _, item := range old { - if _, ok := newMap[key(item)]; !ok { - delete = append(delete, item) - } - } - return upsert, delete -} - -func s3UniqueKeyFunc(s3 *accessgraphv1alpha.AWSS3BucketV1) string { +func s3bucketKey(s3 *accessgraphv1alpha.AWSS3BucketV1) string { return fmt.Sprintf("%s;%s", s3.AccountId, s3.Name) } -func reconcileS3(old []*accessgraphv1alpha.AWSS3BucketV1, new []*accessgraphv1alpha.AWSS3BucketV1) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - - toAdd, toRemove := reconcile(old, new, s3UniqueKeyFunc) - - for _, s3 := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_S3Bucket{ - S3Bucket: s3, - }, - }) - } - for _, s3 := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_S3Bucket{ - S3Bucket: s3, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func s3bucketWrap(s3 *accessgraphv1alpha.AWSS3BucketV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_S3Bucket{S3Bucket: s3}} } -func roleUniqueKeyFunc(role *accessgraphv1alpha.AWSRoleV1) string { +func roleKey(role *accessgraphv1alpha.AWSRoleV1) string { return fmt.Sprintf("%s;%s", role.AccountId, role.Arn) } -func reconcileRoles(old []*accessgraphv1alpha.AWSRoleV1, new []*accessgraphv1alpha.AWSRoleV1) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, roleUniqueKeyFunc) - - for _, role := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_Role{ - Role: role, - }, - }) - } - for _, role := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_Role{ - Role: role, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func roleWrap(role *accessgraphv1alpha.AWSRoleV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_Role{Role: role}} } -func roleInlinePolicyUniqueKeyFunc(policy *accessgraphv1alpha.AWSRoleInlinePolicyV1) string { +func roleInlinePolKey(policy *accessgraphv1alpha.AWSRoleInlinePolicyV1) string { return fmt.Sprintf("%s;%s;%s", policy.AccountId, policy.GetAwsRole().Arn, policy.PolicyName) } -func reconcileRoleInlinePolicies( - old []*accessgraphv1alpha.AWSRoleInlinePolicyV1, - new []*accessgraphv1alpha.AWSRoleInlinePolicyV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, roleInlinePolicyUniqueKeyFunc) - - for _, policy := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_RoleInlinePolicy{ - RoleInlinePolicy: policy, - }, - }) - } - for _, policy := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_RoleInlinePolicy{ - RoleInlinePolicy: policy, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func roleInlinePolWrap(policy *accessgraphv1alpha.AWSRoleInlinePolicyV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_RoleInlinePolicy{RoleInlinePolicy: policy}} } -func roleAttachedPoliciesUniqueKeyFunc(policy *accessgraphv1alpha.AWSRoleAttachedPolicies) string { +func roleAttchPolKey(policy *accessgraphv1alpha.AWSRoleAttachedPolicies) string { return fmt.Sprintf("%s;%s", policy.GetAwsRole().GetArn(), policy.AccountId) } -func reconcileRoleAttachedPolicies( - old []*accessgraphv1alpha.AWSRoleAttachedPolicies, - new []*accessgraphv1alpha.AWSRoleAttachedPolicies, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, roleAttachedPoliciesUniqueKeyFunc) - - for _, policy := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_RoleAttachedPolicies{ - RoleAttachedPolicies: policy, - }, - }) - } - for _, policy := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_RoleAttachedPolicies{ - RoleAttachedPolicies: policy, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func roleAttchPolWrap(policy *accessgraphv1alpha.AWSRoleAttachedPolicies) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_RoleAttachedPolicies{RoleAttachedPolicies: policy}} } -func instanceProfileUniqueKeyFunc(profile *accessgraphv1alpha.AWSInstanceProfileV1) string { +func instanceProfKey(profile *accessgraphv1alpha.AWSInstanceProfileV1) string { return fmt.Sprintf("%s;%s", profile.AccountId, profile.InstanceProfileId) } -func reconcileInstanceProfiles( - old []*accessgraphv1alpha.AWSInstanceProfileV1, - new []*accessgraphv1alpha.AWSInstanceProfileV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, instanceProfileUniqueKeyFunc) - - for _, profile := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_InstanceProfile{ - InstanceProfile: profile, - }, - }) - } - for _, profile := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_InstanceProfile{ - InstanceProfile: profile, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} + +func instanceProfWrap(profile *accessgraphv1alpha.AWSInstanceProfileV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_InstanceProfile{InstanceProfile: profile}} } -func eksClusterUniqueKeyFunc(cluster *accessgraphv1alpha.AWSEKSClusterV1) string { +func eksClusterKey(cluster *accessgraphv1alpha.AWSEKSClusterV1) string { return fmt.Sprintf("%s;%s", cluster.AccountId, cluster.Arn) } -func reconcileEKSClusters( - old []*accessgraphv1alpha.AWSEKSClusterV1, - new []*accessgraphv1alpha.AWSEKSClusterV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, eksClusterUniqueKeyFunc) - - for _, cluster := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_EksCluster{ - EksCluster: cluster, - }, - }) - } - for _, cluster := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_EksCluster{ - EksCluster: cluster, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func eksClusterWrap(cluster *accessgraphv1alpha.AWSEKSClusterV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_EksCluster{EksCluster: cluster}} } -func associatedAccessPolicyUniqueKeyFunc(profile *accessgraphv1alpha.AWSEKSAssociatedAccessPolicyV1) string { - return fmt.Sprintf("%s;%s;%s;%s", profile.AccountId, profile.Cluster.Arn, profile.PrincipalArn, profile.PolicyArn) +func assocAccPolKey(policy *accessgraphv1alpha.AWSEKSAssociatedAccessPolicyV1) string { + return fmt.Sprintf("%s;%s;%s;%s", policy.AccountId, policy.Cluster.Arn, policy.PrincipalArn, policy.PolicyArn) } -func reconcileAssociatedAccessPolicy( - old []*accessgraphv1alpha.AWSEKSAssociatedAccessPolicyV1, - new []*accessgraphv1alpha.AWSEKSAssociatedAccessPolicyV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, associatedAccessPolicyUniqueKeyFunc) - - for _, profile := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_EksClusterAssociatedPolicy{ - EksClusterAssociatedPolicy: profile, - }, - }) - } - for _, profile := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_EksClusterAssociatedPolicy{ - EksClusterAssociatedPolicy: profile, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func assocAccPolWrap(policy *accessgraphv1alpha.AWSEKSAssociatedAccessPolicyV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_EksClusterAssociatedPolicy{EksClusterAssociatedPolicy: policy}} } -func accessEntryUniqueKeyFunc(profile *accessgraphv1alpha.AWSEKSClusterAccessEntryV1) string { - return fmt.Sprintf("%s;%s;%s;%s", profile.AccountId, profile.Cluster.Arn, profile.PrincipalArn, profile.AccessEntryArn) +func accessEntryKey(entry *accessgraphv1alpha.AWSEKSClusterAccessEntryV1) string { + return fmt.Sprintf("%s;%s;%s;%s", entry.AccountId, entry.Cluster.Arn, entry.PrincipalArn, entry.AccessEntryArn) } -func reconcileAccessEntry( - old []*accessgraphv1alpha.AWSEKSClusterAccessEntryV1, - new []*accessgraphv1alpha.AWSEKSClusterAccessEntryV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, accessEntryUniqueKeyFunc) - for _, profile := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_EksClusterAccessEntry{ - EksClusterAccessEntry: profile, - }, - }) - } - for _, profile := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_EksClusterAccessEntry{ - EksClusterAccessEntry: profile, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func accessEntryWrap(entry *accessgraphv1alpha.AWSEKSClusterAccessEntryV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_EksClusterAccessEntry{EksClusterAccessEntry: entry}} } -func rdsUniqueKeyFunc(profile *accessgraphv1alpha.AWSRDSDatabaseV1) string { - return fmt.Sprintf("%s;%s", profile.AccountId, profile.Arn) +func rdsDbKey(db *accessgraphv1alpha.AWSRDSDatabaseV1) string { + return fmt.Sprintf("%s;%s", db.AccountId, db.Arn) } -func reconcileAWSRDS( - old []*accessgraphv1alpha.AWSRDSDatabaseV1, - new []*accessgraphv1alpha.AWSRDSDatabaseV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, rdsUniqueKeyFunc) - - for _, profile := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_Rds{ - Rds: profile, - }, - }) - } - for _, profile := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_Rds{ - Rds: profile, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func rdsDbWrap(db *accessgraphv1alpha.AWSRDSDatabaseV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_Rds{Rds: db}} } -func samlUniqueKeyFunc(provider *accessgraphv1alpha.AWSSAMLProviderV1) string { +func samlProvKey(provider *accessgraphv1alpha.AWSSAMLProviderV1) string { return fmt.Sprintf("%s;%s", provider.AccountId, provider.Arn) } -func reconcileSAMLProviders( - old []*accessgraphv1alpha.AWSSAMLProviderV1, - new []*accessgraphv1alpha.AWSSAMLProviderV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, samlUniqueKeyFunc) - - for _, provider := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_SamlProvider{ - SamlProvider: provider, - }, - }) - } - for _, provider := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_SamlProvider{ - SamlProvider: provider, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} +func samlProvWrap(provider *accessgraphv1alpha.AWSSAMLProviderV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_SamlProvider{SamlProvider: provider}} } -func oidcUniqueKeyFunc(provider *accessgraphv1alpha.AWSOIDCProviderV1) string { +func oidcProvKey(provider *accessgraphv1alpha.AWSOIDCProviderV1) string { return fmt.Sprintf("%s;%s", provider.AccountId, provider.Arn) } -func reconcileOIDCProviders( - old []*accessgraphv1alpha.AWSOIDCProviderV1, - new []*accessgraphv1alpha.AWSOIDCProviderV1, -) *reconcileIntermeditateResult { - upsert, delete := &accessgraphv1alpha.AWSResourceList{}, &accessgraphv1alpha.AWSResourceList{} - toAdd, toRemove := reconcile(old, new, oidcUniqueKeyFunc) - - for _, provider := range toAdd { - upsert.Resources = append(upsert.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_OidcProvider{ - OidcProvider: provider, - }, - }) - } - for _, provider := range toRemove { - delete.Resources = append(delete.Resources, &accessgraphv1alpha.AWSResource{ - Resource: &accessgraphv1alpha.AWSResource_OidcProvider{ - OidcProvider: provider, - }, - }) - } - return &reconcileIntermeditateResult{upsert, delete} -} -func deduplicateSlice[T any](s []T, key func(T) string) []T { - out := make([]T, 0, len(s)) - seen := make(map[string]struct{}) - for _, v := range s { - if _, ok := seen[key(v)]; ok { - continue - } - seen[key(v)] = struct{}{} - out = append(out, v) - } - return out +func oidcProvWrap(provider *accessgraphv1alpha.AWSOIDCProviderV1) *accessgraphv1alpha.AWSResource { + return &accessgraphv1alpha.AWSResource{Resource: &accessgraphv1alpha.AWSResource_OidcProvider{OidcProvider: provider}} }