Skip to content

Commit

Permalink
wip:additional regions
Browse files Browse the repository at this point in the history
  • Loading branch information
YannickVR committed Oct 14, 2024
1 parent 91df0b6 commit 55d449d
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 3 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"author": "Olaf Conijn",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-account": "^3.637.0",
"@aws-sdk/client-cloudformation": "^3.637.0",
"@aws-sdk/client-ec2": "^3.637.0",
"@aws-sdk/client-eventbridge": "^3.637.0",
Expand Down
34 changes: 32 additions & 2 deletions src/aws-provider/aws-organization-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ import * as IAM from '@aws-sdk/client-iam';
import * as Organizations from '@aws-sdk/client-organizations';
import * as Support from '@aws-sdk/client-support';
import * as STS from '@aws-sdk/client-sts';
import * as Account from "@aws-sdk/client-account";
import { AwsUtil } from '../util/aws-util';
import { ConsoleUtil } from '../util/console-util';
import { GetOrganizationAccessRoleInTargetAccount, ICrossAccountConfig } from './aws-account-access';
import { performAndRetryIfNeeded } from './util';

export type AWSObjectType = 'Account' | 'OrganizationalUnit' | 'Policy' | string;
export interface OptInRegion {
RegionName?: string;
}

export type OptInRegions = OptInRegion[];

export type SupportLevel = 'enterprise' | 'business' | 'developer' | 'basic' | string;
interface IAWSTags {
Expand All @@ -31,6 +37,11 @@ interface IAWSAccountWithIAMAttributes {
interface IAWSAccountWithSupportLevel {
SupportLevel?: SupportLevel;
}

interface IAWSAccountWithOptinRegions {
OptInRegions?: OptInRegions;
}

interface IObjectWithParentId {
ParentId: string;
}
Expand All @@ -57,7 +68,7 @@ interface IPolicyTargets {
}

export type AWSPolicy = Organizations.Policy & IPolicyTargets & IAWSObject;
export type AWSAccount = Organizations.Account & IAWSAccountWithTags & IAWSAccountWithSupportLevel & IAWSAccountWithIAMAttributes & IObjectWithParentId & IObjectWithPolicies & IAWSObject & IAWSObjectWithPartition;
export type AWSAccount = Organizations.Account & IAWSAccountWithTags & IAWSAccountWithSupportLevel & IAWSAccountWithOptinRegions & IAWSAccountWithIAMAttributes & IObjectWithParentId & IObjectWithPolicies & IAWSObject & IAWSObjectWithPartition;
export type AWSOrganizationalUnit = Organizations.OrganizationalUnit & IObjectWithParentId & IObjectWithPolicies & IObjectWithAccounts & IAWSObject & IObjectWitOrganizationalUnits & IAWSObjectWithPartition;
export type AWSRoot = Organizations.Root & IObjectWithPolicies & IObjectWitOrganizationalUnits;

Expand Down Expand Up @@ -247,13 +258,15 @@ export class AwsOrganizationReader {
let alias: string;
let passwordPolicy: IAM.PasswordPolicy;
let supportLevel = 'basic';
let optInRegions: OptInRegions;

try {
[tags, alias, passwordPolicy, supportLevel] = await Promise.all([
[tags, alias, passwordPolicy, supportLevel, optInRegions] = await Promise.all([
AwsOrganizationReader.getTagsForAccount(that, acc.Id),
AwsOrganizationReader.getIamAliasForAccount(that, acc.Id),
AwsOrganizationReader.getIamPasswordPolicyForAccount(that, acc.Id),
AwsOrganizationReader.getSupportLevelForAccount(that, acc.Id),
AwsOrganizationReader.getOptInRegionsForAccount(that, acc.Id),
]);

} catch (err) {
Expand All @@ -276,6 +289,7 @@ export class AwsOrganizationReader {
Alias: alias,
PasswordPolicy: passwordPolicy,
SupportLevel: supportLevel,
OptInRegions: optInRegions,
};

const parentOU = organizationalUnits.find(x => x.Id === req.ParentId);
Expand All @@ -297,6 +311,22 @@ export class AwsOrganizationReader {
}
}

private static async getOptInRegionsForAccount(that: AwsOrganizationReader, accountId: string): Promise<OptInRegions> {
try {
await that.organization.getValue();
const targetRoleConfig = await GetOrganizationAccessRoleInTargetAccount(that.crossAccountConfig, accountId);
const accountClient: Account.AccountClient = AwsUtil.GetAccountService(accountId, targetRoleConfig.role, targetRoleConfig.viaRole, that.isPartition);

const command = await accountClient.send( new Account.ListRegionsCommand({RegionOptStatusContains: ["ENABLED"],}));
return command.Regions;
} catch (err) {
if (err instanceof Account.AccountServiceException) {
return undefined;
}
throw err;
}
}

private static async getSupportLevelForAccount(that: AwsOrganizationReader, accountId: string): Promise<SupportLevel> {
try {
await that.organization.getValue();
Expand Down
1 change: 1 addition & 0 deletions src/aws-provider/aws-organization-writer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as Organizations from '@aws-sdk/client-organizations';
import * as IAM from '@aws-sdk/client-iam';
import * as Account from "@aws-sdk/client-account";
import { CreateCaseCommand } from '@aws-sdk/client-support';
import { STSServiceException } from '@aws-sdk/client-sts';
import { AwsUtil, passwordPolicyEquals } from '../util/aws-util';
Expand Down
12 changes: 11 additions & 1 deletion src/parser/model/account-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface IAccountProperties {
SupportLevel?: string;
OrganizationAccessRoleName?: string;
BuildAccessRoleName?: string;
OptInRegions?: any;
}

export class AccountResource extends Resource {
Expand All @@ -36,6 +37,7 @@ export class AccountResource extends Resource {
public supportLevel?: string;
public organizationAccessRoleName?: string;
public buildAccessRoleName?: string;
public optInRegions?:any;
private props?: IAccountProperties;

constructor(root: TemplateRoot, id: string, resource: IResource) {
Expand All @@ -60,13 +62,21 @@ export class AccountResource extends Resource {
this.supportLevel = this.props.SupportLevel;
this.organizationAccessRoleName = this.props.OrganizationAccessRoleName;
this.buildAccessRoleName = this.props.BuildAccessRoleName;
this.optInRegions = this.props.OptInRegions;

if (this.supportLevel !== undefined) {
if (!['basic', 'developer', 'business', 'enterprise'].includes(this.supportLevel)) {
throw new OrgFormationError(`Unexpected value for SupportLevel on account ${id}. Found: ${this.supportLevel}, Exported one of 'basic', 'developer', 'business', 'enterprise'.`);
}
}

// add this later so existing regions do not get "enabled"
// if (this.optInRegions !== undefined) {
// if (!['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'ap-south-1', ].includes(this.optInRegions)) {
// throw new OrgFormationError(`Unexpected value for SupportLevel on account ${id}. Found: ${this.optInRegions}, Exported one of 'basic', 'developer', 'business', 'enterprise'.`);
// }
// }

if (typeof this.accountId === 'number') {
this.accountId = '' + this.accountId;
}
Expand All @@ -79,7 +89,7 @@ export class AccountResource extends Resource {
this.organizationAccessRoleName = this.props.OrganizationAccessRoleName;

super.throwForUnknownAttributes(resource, id, 'Type', 'Properties');
super.throwForUnknownAttributes(this.props, id, 'RootEmail', 'AccountName', 'AccountId', 'Alias', 'PartitionAlias', 'PartitionAccountId', 'ServiceControlPolicies', 'Tags', 'PasswordPolicy', 'SupportLevel', 'OrganizationAccessRoleName', 'BuildAccessRoleName');
super.throwForUnknownAttributes(this.props, id, 'RootEmail', 'AccountName', 'AccountId', 'Alias', 'PartitionAlias', 'PartitionAccountId', 'ServiceControlPolicies', 'Tags', 'PasswordPolicy', 'SupportLevel', 'OrganizationAccessRoleName', 'BuildAccessRoleName', 'OptInRegions');
}

public calculateHash(): string {
Expand Down
24 changes: 24 additions & 0 deletions src/util/aws-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as ini from 'ini';
import { IAMClient, IAMClientConfig } from '@aws-sdk/client-iam';
import { v4 as uuid } from 'uuid';
import { SupportClient, SupportClientConfig } from '@aws-sdk/client-support';
import { AccountClient, AccountClientConfig } from "@aws-sdk/client-account";
import { AwsCredentialIdentity, AwsCredentialIdentityProvider, Provider } from '@smithy/types';
import { fromEnv, fromTemporaryCredentials } from '@aws-sdk/credential-providers';
import { DescribeOrganizationCommand, DescribeOrganizationCommandOutput, OrganizationsClient, OrganizationsClientConfig } from '@aws-sdk/client-organizations';
Expand Down Expand Up @@ -399,6 +400,28 @@ export class AwsUtil {
return this.IamServiceCache[cacheKey];
}

public static GetAccountService(accountId: string, roleInTargetAccount?: string, viaRoleArn?: string, isPartition?: boolean): AccountClient {
AwsUtil.throwIfNowInitiazized();
const { cacheKey, provider } = AwsUtil.GetCredentialProviderWithRoleAssumptions({
accountId,
roleInTargetAccount,
viaRoleArn,
isPartition,
});
const config: AccountClientConfig = {
region: (isPartition) ? this.partitionRegion : AwsUtil.GetDefaultRegion(),
credentials: provider,
defaultsMode: 'standard',
retryMode: 'standard',
maxAttempts: 6,
};
if (this.AccountServiceCache[cacheKey]) {
return this.AccountServiceCache[cacheKey];
}
this.AccountServiceCache[cacheKey] = new AccountClient(config);
return this.AccountServiceCache[cacheKey];
}

/**
* Returns an authenticated CloudFormationClient in the provided accountId and region assuming the role provided.
*
Expand Down Expand Up @@ -630,6 +653,7 @@ export class AwsUtil {
private static largeTemplateBucketName: string | undefined;
private static partitionCredentials: AwsCredentialIdentity | undefined;
private static buildProcessAccountId: string;
private static AccountServiceCache: Record<string, AccountClient> = {};
private static IamServiceCache: Record<string, IAMClient> = {};
private static SupportServiceCache: Record<string, SupportClient> = {};
private static OrganizationsServiceCache: Record<string, OrganizationsClient> = {};
Expand Down
10 changes: 10 additions & 0 deletions src/writer/default-template-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,16 @@ export class DefaultTemplateWriter {
}
}

if (account.OptInRegions) {
const regions = account.OptInRegions;
if (regions.length > 0) {
const regionList = regions
.filter(region => region.RegionName)
.map(region => region.RegionName);
lines.push(new ListLine('OptInRegions', regionList, 6));
}
}

lines.push(new ListLine('ServiceControlPolicies', policiesList, 6));

lines.push(new EmptyLine());
Expand Down

0 comments on commit 55d449d

Please sign in to comment.