Skip to content

Commit 2007c88

Browse files
charliejllewellynCharlie Llewellyn
andauthored
feat(core) Enable account level SCP mgmt (#691)
* Added scps on account type * inital commit for account SCPs * added example account scp configuration * made mandatory * attempting to correct type * added example account scp configuration * correct formatting * added code to restore policy if policy is edited by non accelerator role * removed code attempting to implement duplicate functionality Co-authored-by: Charlie Llewellyn <cjl@amazon.co.uk>
1 parent dfe0243 commit 2007c88

File tree

4 files changed

+91
-0
lines changed

4 files changed

+91
-0
lines changed

reference-artifacts/SAMPLE_CONFIGS/sample_snippets.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,17 @@
705705

706706
---
707707

708+
- Add SCP on a per account basis - add this to either workload or mandatory accounts sections
709+
710+
```
711+
"scps": [
712+
"SCP 1",
713+
"SCP 2"
714+
]
715+
```
716+
717+
---
718+
708719
- Future description
709720

710721
```

src/core/runtime/src/add-scp-step.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ export const handler = async (input: AddScpInput) => {
103103
acceleratorPrefix,
104104
});
105105

106+
await scps.attachOrDetachPoliciesToAccounts({
107+
existingPolicies,
108+
configurationAccounts: accounts,
109+
accountConfigs: config.getAccountConfigs(),
110+
acceleratorPrefix,
111+
});
112+
106113
return {
107114
status: 'SUCCESS',
108115
};

src/lib/common-config/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ export const MandatoryAccountConfigType = t.interface({
592592
'populate-all-elbs-in-param-store': fromNullable(t.boolean, false),
593593
'ssm-automation': fromNullable(t.array(SsmShareAutomation), []),
594594
'aws-config': fromNullable(t.array(AwsConfigAccountConfig), []),
595+
scps: optional(t.array(t.string)),
595596
});
596597

597598
export type MandatoryAccountConfig = t.TypeOf<typeof MandatoryAccountConfigType>;

src/lib/common/src/scp/index.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { stringType } from 'aws-sdk/clients/iam';
55
import { PolicySummary } from 'aws-sdk/clients/organizations';
66
import { OrganizationalUnit } from '@aws-accelerator/common-outputs/src/organizations';
77
import { additionalReplacements, replaceDefaults } from './../util/common';
8+
import { AccountConfig } from '@aws-accelerator/common-config/src';
9+
import { Account } from '@aws-accelerator/common-outputs/src/accounts';
810

911
export const FULL_AWS_ACCESS_POLICY_NAME = 'FullAWSAccess';
1012

@@ -266,6 +268,76 @@ export class ServiceControlPolicy {
266268
}
267269
}
268270

271+
/**
272+
* Attach new or detach removed policies based on the account configuration.
273+
*/
274+
async attachOrDetachPoliciesToAccounts(props: {
275+
existingPolicies: PolicySummary[];
276+
configurationAccounts: Account[];
277+
accountConfigs: [string, AccountConfig][];
278+
acceleratorPrefix: string;
279+
}) {
280+
const { existingPolicies, configurationAccounts, accountConfigs, acceleratorPrefix } = props;
281+
282+
for (const [accountKey, accountConfig] of accountConfigs) {
283+
const Account = configurationAccounts.find(Account => Account.key === accountKey);
284+
/**
285+
* Check if scps key is set on account. If not, ignore as SCPs are being managed in the outside the installer.
286+
*/
287+
if (accountConfig.scps == null) {
288+
continue;
289+
}
290+
291+
// Attach Accelerator SCPs to Accounts
292+
if (!Account) {
293+
console.warn(`Cannot find Account configuration with key "${accountKey}"`);
294+
continue;
295+
}
296+
297+
const accountPolicyNames = accountConfig.scps.map(policyName =>
298+
ServiceControlPolicy.policyNameToAcceleratorPolicyName({ acceleratorPrefix, policyName }),
299+
);
300+
301+
if (accountPolicyNames.length > 4) {
302+
console.warn(`Maximum allowed SCP per Account is 5. Limit exceeded for Account ${accountKey}`);
303+
continue;
304+
}
305+
306+
// Find targets for this policy
307+
const policyTargets = await this.org.listPoliciesForTarget({
308+
Filter: 'SERVICE_CONTROL_POLICY',
309+
TargetId: Account.id,
310+
});
311+
312+
// Detach removed policies
313+
for (const policyTarget of policyTargets) {
314+
const policyTargetName = policyTarget.Name!;
315+
if (!accountPolicyNames.includes(policyTargetName) && policyTargetName !== FULL_AWS_ACCESS_POLICY_NAME) {
316+
console.log(`Detaching ${policyTargetName} from Account ${accountKey}`);
317+
await this.org.detachPolicy(policyTarget.Id!, Account.id);
318+
}
319+
}
320+
321+
// Attach new policies
322+
for (const accountPolicyName of accountPolicyNames) {
323+
const policy = existingPolicies.find(p => p.Name === accountPolicyName);
324+
if (!policy) {
325+
console.warn(`Cannot find policy with name "${accountPolicyName}"`);
326+
continue;
327+
}
328+
329+
const policyTarget = policyTargets.find(x => x.Name === accountPolicyName);
330+
if (policyTarget) {
331+
console.log(`Skipping attachment of ${accountPolicyName} to already attached Account ${accountKey}`);
332+
continue;
333+
}
334+
335+
console.log(`Attaching ${accountPolicyName} to Account ${accountKey}`);
336+
await this.org.attachPolicy(policy.Id!, Account.id);
337+
}
338+
}
339+
}
340+
269341
static createQuarantineScpContent(props: { acceleratorPrefix: string; organizationAdminRole: string }) {
270342
return JSON.stringify({
271343
Version: '2012-10-17',

0 commit comments

Comments
 (0)