Skip to content

Commit

Permalink
feat: allow EC2 App pattern users to customise role permissions (#542)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobwinch authored May 11, 2021
1 parent b8734ee commit f1bafc0
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 4 deletions.
6 changes: 4 additions & 2 deletions src/constructs/iam/roles/instance-role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import {
} from "../policies";
import { GuRole } from "./roles";

interface GuInstanceRoleProps extends AppIdentity {
export interface GuInstanceRoleProps {
withoutLogShipping?: boolean;
additionalPolicies?: GuPolicy[];
}

export type GuInstanceRolePropsWithApp = GuInstanceRoleProps & AppIdentity;

/**
* Creates an IAM role with common policies that are needed by most Guardian applications.
*
Expand All @@ -31,7 +33,7 @@ interface GuInstanceRoleProps extends AppIdentity {
* If log shipping is not required, opt out by setting the `withoutLogShipping` prop to `true`.
*/
export class GuInstanceRole extends GuRole {
constructor(scope: GuStack, props: GuInstanceRoleProps) {
constructor(scope: GuStack, props: GuInstanceRolePropsWithApp) {
super(scope, AppIdentity.suffixText(props, "InstanceRole"), {
path: "/",
assumedBy: new ServicePrincipal("ec2.amazonaws.com"),
Expand Down
75 changes: 75 additions & 0 deletions src/patterns/ec2-app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { CfnLoadBalancer } from "@aws-cdk/aws-elasticloadbalancingv2";
import { Stage } from "../constants";
import { GuPrivateConfigBucketParameter } from "../constructs/core";
import { GuSecurityGroup } from "../constructs/ec2/security-groups";
import { GuDynamoDBWritePolicy } from "../constructs/iam";
import type { SynthedStack } from "../utils/test";
import { simpleGuStackForTesting } from "../utils/test";
import { AccessScope, GuApplicationPorts, GuEc2App, GuNodeApp, GuPlayApp } from "./ec2-app";
Expand Down Expand Up @@ -221,6 +222,80 @@ describe("the GuEC2App pattern", function () {
).toThrowError();
});

it("correctly wires up custom role configuration", function () {
const stack = simpleGuStackForTesting();
const app = "test-gu-ec2-app";
new GuEc2App(stack, {
applicationPort: GuApplicationPorts.Node,
access: { scope: AccessScope.PUBLIC },
app: app,
certificateProps: getCertificateProps(),
monitoringConfiguration: { noMonitoring: true },
userData: "",
roleConfiguration: {
withoutLogShipping: true,
additionalPolicies: [new GuDynamoDBWritePolicy(stack, "DynamoTable", { tableName: "my-dynamo-table" })],
},
});
expect(stack).not.toHaveResource("AWS::IAM::Policy", {
PolicyDocument: {
Version: "2012-10-17",
Statement: [
{
Action: ["kinesis:Describe*", "kinesis:Put*"],
Effect: "Allow",
Resource: {
"Fn::Join": [
"",
[
"arn:aws:kinesis:",
{
Ref: "AWS::Region",
},
":",
{
Ref: "AWS::AccountId",
},
":stream/",
{
Ref: "LoggingStreamName",
},
],
],
},
},
],
},
});
expect(stack).toHaveResource("AWS::IAM::Policy", {
PolicyDocument: {
Version: "2012-10-17",
Statement: [
{
Action: ["dynamodb:BatchWriteItem", "dynamodb:PutItem", "dynamodb:DeleteItem", "dynamodb:UpdateItem"],
Effect: "Allow",
Resource: {
"Fn::Join": [
"",
[
"arn:aws:dynamodb:",
{
Ref: "AWS::Region",
},
":",
{
Ref: "AWS::AccountId",
},
":table/my-dynamo-table",
],
],
},
},
],
},
});
});

it("sub-constructs can be accessed and modified after declaring the pattern", function () {
const stack = simpleGuStackForTesting();
const app = "test-gu-ec2-app";
Expand Down
22 changes: 20 additions & 2 deletions src/patterns/ec2-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Gu5xxPercentageAlarm } from "../constructs/cloudwatch";
import type { GuStack } from "../constructs/core";
import { AppIdentity } from "../constructs/core/identity";
import { GuSecurityGroup, GuVpc, SubnetType } from "../constructs/ec2";
import type { GuInstanceRoleProps } from "../constructs/iam";
import { GuGetPrivateConfigPolicy, GuInstanceRole } from "../constructs/iam";
import {
GuApplicationLoadBalancer,
Expand Down Expand Up @@ -47,11 +48,23 @@ interface RestrictedAccess extends Access {

export type AppAccess = PublicAccess | RestrictedAccess;

interface GuEc2AppProps extends AppIdentity {
/**
* To grant applications additional IAM permissions, use the `roleConfiguration` prop. For example,
* to allow your app to write to DynamoDB:
*
* ```typescript
* // other props
* roleConfiguration: {
* additionalPolicies: [new GuDynamoDBWritePolicy(stack, "DynamoTable", { tableName: "my-dynamo-table" })],
* }
* ```
*/
export interface GuEc2AppProps extends AppIdentity {
userData: GuUserDataProps | string;
access: AppAccess;
applicationPort: number;
certificateProps: GuCertificateProps;
roleConfiguration?: GuInstanceRoleProps;
monitoringConfiguration: NoMonitoring | Gu5xxPercentageMonitoringProps;
}

Expand Down Expand Up @@ -159,14 +172,19 @@ export class GuEc2App {
? [new GuGetPrivateConfigPolicy(scope, "GetPrivateConfigFromS3Policy", props.userData.configuration)]
: [];

const mergedRoleConfiguration: GuInstanceRoleProps = {
withoutLogShipping: props.roleConfiguration?.withoutLogShipping,
additionalPolicies: maybePrivateConfigPolicy.concat(props.roleConfiguration?.additionalPolicies ?? []),
};

const autoScalingGroup = new GuAutoScalingGroup(scope, "AutoScalingGroup", {
app,
vpc,
stageDependentProps: {
CODE: { minimumInstances: 1 },
PROD: { minimumInstances: 3 },
},
role: new GuInstanceRole(scope, { app: props.app, additionalPolicies: maybePrivateConfigPolicy }),
role: new GuInstanceRole(scope, { app: props.app, ...mergedRoleConfiguration }),
healthCheck: HealthCheck.elb({ grace: Duration.minutes(2) }), // should this be defaulted at pattern or construct level?
userData:
typeof props.userData !== "string"
Expand Down
1 change: 1 addition & 0 deletions src/patterns/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./scheduled-lambda";
export * from "./sns-lambda";
export * from "./kinesis-lambda";
export * from "./ec2-app";

0 comments on commit f1bafc0

Please sign in to comment.