Skip to content

Commit

Permalink
chore(cdk greenfield support): Changes to support greenfield account …
Browse files Browse the repository at this point in the history
…restrictions present in higher environments (#700)

* add support for iam path and permissions boundary, required in higher greenfield accounts

* update test... wow this is verbose

* Add type guards to prevent casting
  • Loading branch information
mdial89f authored Aug 2, 2024
1 parent 99d54b0 commit 22e9ca1
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 13 deletions.
19 changes: 17 additions & 2 deletions bin/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { ParentStack } from "../lib/stacks/parent";
import { DeploymentConfig } from "../lib/stacks/deployment-config";
import { validateEnvVariable } from "shared-utils";
import { getSecret, validateEnvVariable } from "shared-utils";
import {
IamPathAspect,
IamPermissionsBoundaryAspect,
} from "../lib/local-aspects";

async function main() {
try {
const app = new cdk.App();
const app = new cdk.App({
defaultStackSynthesizer: new cdk.DefaultStackSynthesizer(
JSON.parse(await getSecret("cdkSynthesizerConfig")),
),
});

validateEnvVariable("REGION_A");

Expand All @@ -26,6 +34,13 @@ async function main() {
region: process.env.CDK_DEFAULT_REGION,
},
});

cdk.Aspects.of(app).add(
new IamPermissionsBoundaryAspect(
deploymentConfig.config.iamPermissionsBoundary,
),
);
cdk.Aspects.of(app).add(new IamPathAspect(deploymentConfig.config.iamPath));
} catch (error) {
console.error("Error:", error);
process.exit(1);
Expand Down
Binary file modified bun.lockb
Binary file not shown.
26 changes: 26 additions & 0 deletions lib/local-aspects/iam-path/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { IAspect } from "aws-cdk-lib";
import { IConstruct } from "constructs";
import * as iam from "aws-cdk-lib/aws-iam";
import { isCfnRole, isCfnUser, isCfnGroup } from "shared-utils";

export class IamPathAspect implements IAspect {
private readonly iamPath: string;

constructor(iamPath: string) {
this.iamPath = iamPath;
}

public visit(node: IConstruct): void {
if (node instanceof iam.Role && isCfnRole(node.node.defaultChild)) {
node.node.defaultChild?.addPropertyOverride("Path", this.iamPath);
}

if (node instanceof iam.User && isCfnUser(node.node.defaultChild)) {
node.node.defaultChild.addPropertyOverride("Path", this.iamPath);
}

if (node instanceof iam.Group && isCfnGroup(node.node.defaultChild)) {
node.node.defaultChild.addPropertyOverride("Path", this.iamPath);
}
}
}
21 changes: 21 additions & 0 deletions lib/local-aspects/iam-permissions-boundary/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IAspect } from "aws-cdk-lib";
import { IConstruct } from "constructs";
import * as iam from "aws-cdk-lib/aws-iam";
import { isCfnRole } from "shared-utils";

export class IamPermissionsBoundaryAspect implements IAspect {
private readonly permissionsBoundaryArn: string;

constructor(permissionBoundaryArn: string) {
this.permissionsBoundaryArn = permissionBoundaryArn;
}

public visit(node: IConstruct): void {
if (node instanceof iam.Role && isCfnRole(node.node.defaultChild)) {
node.node.defaultChild.addPropertyOverride(
"PermissionsBoundary",
this.permissionsBoundaryArn,
);
}
}
}
2 changes: 2 additions & 0 deletions lib/local-aspects/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./iam-permissions-boundary";
export * from "./iam-path";
13 changes: 13 additions & 0 deletions lib/packages/shared-utils/cdk-type-guards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as iam from "aws-cdk-lib/aws-iam";

export function isCfnRole(child: any): child is iam.CfnRole {
return child instanceof iam.CfnRole;
}

export function isCfnUser(child: any): child is iam.CfnUser {
return child instanceof iam.CfnUser;
}

export function isCfnGroup(child: any): child is iam.CfnGroup {
return child instanceof iam.CfnGroup;
}
1 change: 1 addition & 0 deletions lib/packages/shared-utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./cdk-type-guards";
export * from "./feature-flags";
export * from "./package-actions/getAvailableActions";
export * from "./package-check";
Expand Down
6 changes: 6 additions & 0 deletions lib/stacks/deployment-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ describe("DeploymentConfig", () => {
vpcName: "vpcName",
emailFromIdentity: "[email protected]",
emailIdentityDomain: "cms.hhs.gov",
iamPath: "/my/path/",
iamPermissionsBoundary: "arn:aws:iam::1234578910:policy/foo/bar-policy",
});

const stageSecret = JSON.stringify({
Expand Down Expand Up @@ -101,6 +103,8 @@ describe("DeploymentConfig", () => {
terminationProtection: false,
emailFromIdentity: "[email protected]",
emailIdentityDomain: "cms.hhs.gov",
iamPath: "/my/path/",
iamPermissionsBoundary: "arn:aws:iam::1234578910:policy/foo/bar-policy",
};

expect(deploymentConfig.config).toEqual(expectedConfig);
Expand Down Expand Up @@ -167,6 +171,8 @@ describe("DeploymentConfig", () => {
terminationProtection: false,
emailFromIdentity: "[email protected]",
emailIdentityDomain: "cms.hhs.gov",
iamPath: "/my/path/",
iamPermissionsBoundary: "arn:aws:iam::1234578910:policy/foo/bar-policy",
};

expect(deploymentConfig.config).toEqual(expectedConfig);
Expand Down
4 changes: 4 additions & 0 deletions lib/stacks/deployment-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export type InjectedConfigProperties = {
emailIdentityDomain: string;
googleAnalyticsDisable: boolean;
googleAnalyticsGTag: string;
iamPath: string;
iamPermissionsBoundary: string;
idmAuthzApiEndpoint: string;
idmAuthzApiKeyArn: string;
idmClientId: string;
Expand Down Expand Up @@ -128,6 +130,8 @@ export class DeploymentConfig {
typeof config.emailAddressLookupSecretName === "string" && // pragma: allowlist secret
typeof config.googleAnalyticsDisable == "boolean" &&
typeof config.googleAnalyticsGTag === "string" &&
typeof config.iamPermissionsBoundary === "string" &&
typeof config.iamPath === "string" &&
typeof config.idmAuthzApiEndpoint === "string" &&
typeof config.idmAuthzApiKeyArn === "string" && // pragma: allowlist secret
typeof config.idmClientId === "string" &&
Expand Down
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,35 +31,35 @@
"license": "CC0-1.0",
"homepage": "https://github.com/Enterprise-CMCS/macpro-mako#readme",
"dependencies": {
"@aws-sdk/client-cloudformation": "^3.600.0",
"@types/aws-lambda": "^8.10.141",
"aws-cdk-lib": "^2.147.0",
"cdk-serverless-clamscan": "^2.6.213",
"@aws-sdk/client-cloudformation": "^3.622.0",
"@types/aws-lambda": "^8.10.142",
"aws-cdk-lib": "^2.150.0",
"cdk-serverless-clamscan": "^2.8.1",
"constructs": "^10.3.0",
"esbuild": "^0.21.5",
"source-map-support": "^0.5.21",
"tsx": "4.15.7"
},
"devDependencies": {
"@anatine/zod-mock": "^3.13.4",
"@aws-sdk/client-secrets-manager": "^3.599.0",
"@aws-sdk/client-secrets-manager": "^3.622.0",
"@faker-js/faker": "^8.4.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/jest": "^29.5.12",
"@types/node": "20.14.2",
"@vitest/coverage-c8": "^0.29.8",
"@vitest/coverage-istanbul": "^2.0.2",
"@vitest/coverage-v8": "^2.0.2",
"@vitest/ui": "^2.0.2",
"@vitest/coverage-istanbul": "^2.0.5",
"@vitest/coverage-v8": "^2.0.5",
"@vitest/ui": "^2.0.5",
"aws-cdk": "2.146.0",
"jest": "^29.7.0",
"semantic-release": "^21.0.1",
"ts-jest": "^29.1.4",
"semantic-release": "^21.1.2",
"ts-jest": "^29.2.4",
"ts-node": "^10.9.2",
"turbo": "^2.0.11",
"typescript": "5.4.5",
"vitest": "^2.0.2"
"vitest": "^2.0.5"
},
"release": {
"branches": [
Expand Down

0 comments on commit 22e9ca1

Please sign in to comment.