UNPKG

@cloud-copilot/iam-collect

Version:

Collect IAM information from AWS Accounts

436 lines 18 kB
import { GetAccountAuthorizationDetailsCommand, GetOpenIDConnectProviderCommand, GetPolicyCommand, GetPolicyVersionCommand, GetSAMLProviderCommand, IAMClient, ListInstanceProfilesCommand, ListOpenIDConnectProvidersCommand, ListPolicyTagsCommand, ListSAMLProvidersCommand } from '@aws-sdk/client-iam'; import { splitArnParts } from '@cloud-copilot/iam-utils'; import { AwsClientPool } from '../../aws/ClientPool.js'; import {} from '../../aws/coreAuth.js'; import { AbstractClient } from '../../customClients/AbstractClient.js'; import { awsConfigCommand } from '../AwsConfigClientContext.js'; import { executeConfigQuery, parseConfigItem, resourceStatusWhereClause } from '../awsConfigUtils.js'; /** * AWS Config-based IAM client implementation */ export class AwsConfigIamClient extends AbstractClient { constructor(options, customContext) { super(options, customContext); } /** * Register all IAM command implementations */ registerCommands() { this.registerCommand(AwsConfigGetAccountAuthorizationDetailsCommand); this.registerCommand(AwsConfigGetOpenIDConnectProviderCommand); this.registerCommand(AwsConfigGetSAMLProviderCommand); this.registerCommand(AwsConfigListInstanceProfilesCommand); this.registerCommand(AwsConfigListOpenIDConnectProvidersCommand); this.registerCommand(AwsConfigListSAMLProvidersCommand); this.registerCommand(AwsConfigListPolicyTagsCommand); } } AwsConfigIamClient.clientName = IAMClient.name; /** * Config-based implementation of IAM GetAccountAuthorizationDetailsCommand * * Aggregates IAM data from multiple Config resource types: * - AWS::IAM::Role for roles * - AWS::IAM::User for users * - AWS::IAM::Group for groups * - AWS::IAM::Policy for managed policies */ const AwsConfigGetAccountAuthorizationDetailsCommand = awsConfigCommand({ command: GetAccountAuthorizationDetailsCommand, execute: async (input, context) => { const nativeIamClient = AwsClientPool.defaultInstance.client(IAMClient, context.configCredentials, context.region, undefined); const awsManagedPoliciesToFetch = new Set(); // Query roles from Config const rolesQuery = ` SELECT configuration.arn, configuration.roleName, configuration.path, configuration.roleId, configuration.createDate, configuration.assumeRolePolicyDocument, configuration.attachedManagedPolicies, configuration.instanceProfileList, configuration.rolePolicyList, configuration.permissionsBoundary, configuration.roleLastUsed, tags WHERE resourceType = 'AWS::IAM::Role' AND accountId = '${context.accountId}' AND ${resourceStatusWhereClause} `; // Query users from Config const usersQuery = ` SELECT configuration.arn, configuration.userName, configuration.path, configuration.userId, configuration.createDate, configuration.attachedManagedPolicies, configuration.groupList, configuration.userPolicyList, configuration.permissionsBoundary, tags WHERE resourceType = 'AWS::IAM::User' AND accountId = '${context.accountId}' AND ${resourceStatusWhereClause} `; // Query groups from Config const groupsQuery = ` SELECT configuration.arn, configuration.groupName, configuration.path, configuration.groupId, configuration.createDate, configuration.attachedManagedPolicies, configuration.groupPolicyList, tags WHERE resourceType = 'AWS::IAM::Group' AND accountId = '${context.accountId}' AND ${resourceStatusWhereClause} `; // Query policies from Config const policiesQuery = ` SELECT configuration.policyName, configuration.policyId, configuration.arn, configuration.path, configuration.createDate, configuration.updateDate, configuration.policyVersionList, configuration.attachmentCount, configuration.permissionsBoundaryUsageCount, configuration.isAttachable, configuration.description, tags WHERE resourceType = 'AWS::IAM::Policy' AND accountId = '${context.accountId}' AND ${resourceStatusWhereClause} `; // Execute all queries const [rolesResults, usersResults, groupsResults, policiesResults] = await Promise.all([ executeConfigQuery(rolesQuery, context), executeConfigQuery(usersQuery, context), executeConfigQuery(groupsQuery, context), executeConfigQuery(policiesQuery, context) ]); // Transform Config data to IAM format and collect AWS managed policies const roles = rolesResults.map((resultString) => { const { configuration, tags } = parseConfigItem(resultString); // Collect AWS managed policies from this role const attachedPolicies = configuration.attachedManagedPolicies || []; attachedPolicies.forEach((policy) => { if (policy.policyArn && isAwsManagedPolicy(policy.policyArn)) { awsManagedPoliciesToFetch.add(policy.policyArn); } }); return { Arn: configuration.arn, RoleName: configuration.roleName, Path: configuration.path, RoleId: configuration.roleId, CreateDate: configuration.createDate ? new Date(configuration.createDate) : undefined, AssumeRolePolicyDocument: configuration.assumeRolePolicyDocument, AttachedManagedPolicies: configuration.attachedManagedPolicies?.map((ap) => ({ PolicyName: ap.policyName, PolicyArn: ap.policyArn })), InstanceProfileList: configuration.instanceProfileList, RolePolicyList: configuration.rolePolicyList.map((rp) => ({ PolicyName: rp.policyName, PolicyDocument: rp.policyDocument })), Tags: tags, PermissionsBoundary: configuration.permissionsBoundary, RoleLastUsed: configuration.roleLastUsed }; }); const users = usersResults.map((resultString) => { const { configuration, tags } = parseConfigItem(resultString); // Collect AWS managed policies from this user const attachedPolicies = configuration.attachedManagedPolicies || []; attachedPolicies.forEach((policy) => { if (policy.policyArn && isAwsManagedPolicy(policy.policyArn)) { awsManagedPoliciesToFetch.add(policy.policyArn); } }); return { Arn: configuration.arn, UserName: configuration.userName, Path: configuration.path, UserId: configuration.userId, CreateDate: configuration.createDate ? new Date(configuration.createDate) : undefined, AttachedManagedPolicies: configuration.attachedManagedPolicies.map((ap) => ({ PolicyName: ap.policyName, PolicyArn: ap.policyArn })), GroupList: configuration.groupList, UserPolicyList: configuration.userPolicyList.map((up) => ({ PolicyName: up.policyName, PolicyDocument: up.policyDocument })), Tags: tags, PermissionsBoundary: configuration.permissionsBoundary }; }); const groups = groupsResults.map((resultString) => { const { configuration, tags } = parseConfigItem(resultString); // Collect AWS managed policies from this group const attachedPolicies = configuration.attachedManagedPolicies || []; attachedPolicies.forEach((policy) => { if (policy.policyArn && isAwsManagedPolicy(policy.policyArn)) { awsManagedPoliciesToFetch.add(policy.policyArn); } }); return { Arn: configuration.arn, GroupName: configuration.groupName, Path: configuration.path, GroupId: configuration.groupId, CreateDate: configuration.createDate ? new Date(configuration.createDate) : undefined, AttachedManagedPolicies: putInArray(configuration.attachedManagedPolicies).map((ap) => ({ PolicyName: ap.policyName, PolicyArn: ap.policyArn })), Tags: tags ? Object.entries(tags).map(([key, value]) => ({ Key: key, Value: value })) : [], GroupPolicyList: putInArray(configuration.groupPolicyList).map((gp) => ({ PolicyName: gp.policyName, PolicyDocument: gp.policyDocument })) }; }); const policies = policiesResults.map((resultString) => { const { configuration, tags } = parseConfigItem(resultString); return { PolicyName: configuration.policyName, PolicyId: configuration.policyId, Arn: configuration.arn, Path: configuration.path, CreateDate: configuration.createDate ? new Date(configuration.createDate) : undefined, UpdateDate: configuration.updateDate ? new Date(configuration.updateDate) : undefined, PolicyVersionList: putInArray(configuration.policyVersionList).map((pv) => ({ Document: pv.document, VersionId: pv.versionId, IsDefaultVersion: pv.isDefaultVersion })), AttachmentCount: configuration.attachmentCount, PermissionsBoundaryUsageCount: configuration.permissionsBoundaryUsageCount, IsAttachable: configuration.isAttachable, Description: configuration.description, Tags: tags }; }); // Fetch AWS managed policy details using native IAM client const awsManagedPolicies = []; for (const policyArn of awsManagedPoliciesToFetch) { const policyDetails = await nativeIamClient.send(new GetPolicyCommand({ PolicyArn: policyArn })); const policyDocument = await nativeIamClient.send(new GetPolicyVersionCommand({ PolicyArn: policyArn, VersionId: policyDetails.Policy?.DefaultVersionId })); if (policyDocument.PolicyVersion) { awsManagedPolicies.push({ ...policyDetails.Policy, PolicyVersionList: [ { Document: policyDocument.PolicyVersion.Document, VersionId: policyDocument.PolicyVersion.VersionId, IsDefaultVersion: policyDocument.PolicyVersion.IsDefaultVersion } ] }); } } // Combine customer-managed policies from Config with AWS managed policies from native IAM const allPolicies = [...policies, ...awsManagedPolicies]; return { UserDetailList: users, GroupDetailList: groups, RoleDetailList: roles, Policies: allPolicies, IsTruncated: false }; } }); /** * Config-based implementation of IAM ListInstanceProfilesCommand * * Note: AWS Config doesn't track InstanceProfile as a separate resource type. * Instance profiles are extracted from Role configuration data. */ const AwsConfigListInstanceProfilesCommand = awsConfigCommand({ command: ListInstanceProfilesCommand, execute: async (input, context) => { // Get roles from Config and extract instance profiles const query = ` SELECT configuration.instanceProfileList WHERE resourceType = 'AWS::IAM::Role' AND accountId = '${context.accountId}' AND ${resourceStatusWhereClause} `; const results = await executeConfigQuery(query, context); // Extract unique instance profiles from all roles const instanceProfilesMap = new Map(); results.forEach((resultString) => { const { configuration } = parseConfigItem(resultString); const instanceProfiles = configuration.instanceProfileList || []; instanceProfiles.forEach((profile) => { if (profile.arn && !instanceProfilesMap.has(profile.arn)) { instanceProfilesMap.set(profile.arn, { Path: profile.path, InstanceProfileName: profile.instanceProfileName, InstanceProfileId: profile.instanceProfileId, Arn: profile.arn, CreateDate: profile.createDate ? new Date(profile.createDate) : undefined, Roles: profile.roles?.map((r) => ({ Arn: r.arn })) || [], Tags: [] // Tags not available in role's instance profile data }); } }); }); return { InstanceProfiles: Array.from(instanceProfilesMap.values()), IsTruncated: false // TODO: Handle pagination properly }; } }); /** * Config-based implementation of IAM ListOpenIDConnectProvidersCommand */ const AwsConfigListOpenIDConnectProvidersCommand = awsConfigCommand({ command: ListOpenIDConnectProvidersCommand, execute: async (input, context) => { const query = ` SELECT configuration.Arn, configuration.Url, configuration.ClientIdList, configuration.ThumbprintList, tags WHERE resourceType = 'AWS::IAM::OIDCProvider' AND accountId = '${context.accountId}' AND ${resourceStatusWhereClause} `; const results = await executeConfigQuery(query, context); const providers = results.map((resultString) => { const { configuration, tags } = parseConfigItem(resultString); context.putCache(configuration.Arn, 'configuration', { configuration, tags }); return { Arn: configuration.Arn }; }); return { OpenIDConnectProviderList: providers }; } }); /** * Config-based implementation of IAM GetOpenIDConnectProviderCommand */ const AwsConfigGetOpenIDConnectProviderCommand = awsConfigCommand({ command: GetOpenIDConnectProviderCommand, execute: async (input, context) => { const { configuration, tags } = context.getCache(input.OpenIDConnectProviderArn, 'configuration'); return { Url: configuration.Url, ClientIDList: configuration.ClientIdList, ThumbprintList: configuration.ThumbprintList, Tags: tags }; } }); /** * Config-based implementation of IAM ListSAMLProvidersCommand */ const AwsConfigListSAMLProvidersCommand = awsConfigCommand({ command: ListSAMLProvidersCommand, execute: async (input, context) => { const query = ` SELECT arn, configuration.SamlMetadataDocument, tags WHERE resourceType = 'AWS::IAM::SAMLProvider' AND accountId = '${context.accountId}' AND ${resourceStatusWhereClause} `; const results = await executeConfigQuery(query, context); const providers = results.map((resultString) => { const { configItem, configuration, tags } = parseConfigItem(resultString); context.putCache(configItem.arn, 'configuration', { configuration, tags }); return { Arn: configItem.arn }; }); return { SAMLProviderList: providers }; } }); /** * Config-based implementation of IAM GetSAMLProviderCommand */ const AwsConfigGetSAMLProviderCommand = awsConfigCommand({ command: GetSAMLProviderCommand, execute: async (input, context) => { const { configuration, tags } = context.getCache(input.SAMLProviderArn, 'configuration'); return { SAMLMetadataDocument: configuration.SamlMetadataDocument, Tags: tags }; } }); /** * Config-based implementation of IAM ListPolicyTagsCommand */ const AwsConfigListPolicyTagsCommand = awsConfigCommand({ command: ListPolicyTagsCommand, execute: async (input, context) => { const policyName = input.PolicyArn.split('/').pop(); const query = ` SELECT tags WHERE resourceType = 'AWS::IAM::Policy' AND accountId = '${context.accountId}' AND resourceName = '${policyName}' AND ${resourceStatusWhereClause} `; const results = await executeConfigQuery(query, context); if (results.length === 0) { throw new Error('Policy not found'); } const { tags } = parseConfigItem(results[0]); return { Tags: tags, IsTruncated: false }; } }); /** * Given an AWS IAM policy ARN, determine if it is an AWS-managed policy * * @param policyArn the ARN of the IAM policy * @returns true if the policy is AWS-managed, false otherwise */ function isAwsManagedPolicy(policyArn) { return splitArnParts(policyArn).accountId === 'aws'; } function putInArray(item) { if (item === undefined || item === null) { return []; } return Array.isArray(item) ? item : [item]; } //# sourceMappingURL=AwsConfigIamClient.js.map