UNPKG

@davidpellerin/accountfactory

Version:

AWS Organizations setup and management tool for creating and managing multi-account setups

158 lines (143 loc) 4.89 kB
import { AttachUserPolicyCommand, CreateAccessKeyCommand, CreateLoginProfileCommand, GetUserCommand, IAMClient, } from '@aws-sdk/client-iam'; import { AssumeRoleCommand } from '@aws-sdk/client-sts'; import { ADMIN_POLICY_ARN, ORGANIZATION_ROLE_NAME } from '../constants.js'; import { PasswordService } from '../utils/passwordService.js'; import { logger } from '../utils/logger.js'; export class IAMService { constructor(iamClient, secretsManagerClient, stsClient, injectedLogger = logger) { if (!iamClient) { throw new Error('IAMClient is required'); } if (!secretsManagerClient) { throw new Error('SecretsManagerClient is required'); } if (!stsClient) { throw new Error('STSClient is required'); } this.iamClient = iamClient; this.secretsManagerClient = secretsManagerClient; this.stsClient = stsClient; this.logger = injectedLogger; this.logger.debug('IAMService initialized with all required dependencies'); } async createNewUser(username) { try { let password; // Generate and set password try { password = PasswordService.generatePassword(); await this.iamClient.send( new CreateLoginProfileCommand({ UserName: username, Password: password, PasswordResetRequired: true, }) ); } catch (error) { if (error?.name === 'EntityAlreadyExists' || error?.$metadata?.httpStatusCode === 409) { this.logger.warning( `Login profile already exists for user ${username}, skipping password creation` ); password = '**EXISTING PASSWORD NOT CHANGED**'; } else { throw error; } } // Attach admin policy await this.iamClient.send( new AttachUserPolicyCommand({ UserName: username, PolicyArn: ADMIN_POLICY_ARN, }) ); // Create access key const accessKeyResponse = await this.iamClient.send( new CreateAccessKeyCommand({ UserName: username, }) ); return { password, accessKeyId: accessKeyResponse.AccessKey.AccessKeyId, secretAccessKey: accessKeyResponse.AccessKey.SecretAccessKey, }; } catch (error) { this.logger.error(`Error creating new user: ${error.message}`); throw error; } } async checkIfIamUserExists(username) { try { await this.iamClient.send(new GetUserCommand({ UserName: username })); return true; } catch (error) { if (error.name === 'NoSuchEntityException') { return false; } throw error; } } async createIAMUser(accountId, username) { try { this.logger.info(`Creating IAM user ${username} in account ${accountId}`); await this.getIAMClientForAccount(accountId); // Check if user exists and handle accordingly const userExists = await this.checkIfIamUserExists(username); if (userExists) { this.logger.info( `IAM User already exists. Skipping user creation for ${username} in account ${accountId}` ); return false; } else { this.logger.info( `User ${username} does not exist in account ${accountId}. Creating new user...` ); // Create new user and get credentials this.logger.info(`Creating new user ${username} in account ${accountId}`); const credentials = await this.createNewUser(username); // Store credentials in Secrets Manager this.logger.info( `Storing credentials in Secrets Manager for user ${username} in account ${accountId}` ); await this.secretsManagerClient.storeCredentialsInSecretsManager( accountId, username, credentials ); return true; } } catch (error) { this.logger.error(`Error creating user in account ${accountId}: ${error.message}`); throw error; } } async getIAMClientForAccount(accountId) { try { // Assume the OrganizationAccountAccessRole in target account const assumeRoleResponse = await this.stsClient.send( new AssumeRoleCommand({ RoleArn: `arn:aws:iam::${accountId}:role/${ORGANIZATION_ROLE_NAME}`, RoleSessionName: 'CreateIAMUser', DurationSeconds: 3600, }) ); // Return IAM client with temporary credentials return new IAMClient({ credentials: { accessKeyId: assumeRoleResponse.Credentials.AccessKeyId, secretAccessKey: assumeRoleResponse.Credentials.SecretAccessKey, sessionToken: assumeRoleResponse.Credentials.SessionToken, }, }); } catch (error) { this.logger.error(`Failed to get IAM client for account ${accountId}: ${error.message}`); throw error; } } }