Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add policy to allow writing to Anghammarad #626

Merged
merged 1 commit into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions docs/005-default-parameter-store-locations.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
A number of constructs from the library define parameters to get configuration from Parameter Store. Each of these parameters configures a default path.
The below table lists those paths, the parameter that sets them, the expected value type and which construct the parameter is defined within.

| Path | Parameter | Type | Construct |
| ------------------------------------- | ---------------------- | ---------------------------- | -------------------------- |
| /account/vpc/primary/id | VpcId | AWS::EC2::VPC::Id | GuVpc.fromIdParameter |
| /account/vpc/primary/subnets/public | PublicSubnets | List\<AWS::EC2::Subnet::Id\> | GuVpc.subnetsFromParameter |
| /account/vpc/primary/subnets/private | PrivateSubnets | List\<AWS::EC2::Subnet::Id\> | GuVpc.subnetsFromParameter |
| /account/services/artifact.bucket | DistributionBucketName | String | GuGetDistributablePolicy |
| /account/services/logging.stream.name | LoggingStreamName | String | GuLogShippingPolicy |
| Path | Parameter | Type | Construct |
| --------------------------------------- | ---------------------- | ---------------------------- | -------------------------- |
| /account/vpc/primary/id | VpcId | AWS::EC2::VPC::Id | GuVpc.fromIdParameter |
| /account/vpc/primary/subnets/public | PublicSubnets | List\<AWS::EC2::Subnet::Id\> | GuVpc.subnetsFromParameter |
| /account/vpc/primary/subnets/private | PrivateSubnets | List\<AWS::EC2::Subnet::Id\> | GuVpc.subnetsFromParameter |
| /account/services/artifact.bucket | DistributionBucketName | String | GuGetDistributablePolicy |
| /account/services/logging.stream.name | LoggingStreamName | String | GuLogShippingPolicy |
| /account/services/anghammarad.topic.arn | AnghammaradSnsArn | String | AnghammaradSenderPolicy |


## Pattern-specific default Parameter Store locations
Expand Down
35 changes: 35 additions & 0 deletions src/constructs/core/parameters/anghammarad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { isSingletonPresentInStack } from "../../../utils/test";
import type { GuStack } from "../stack";
import { GuStringParameter } from "./base";

/**
* Creates a CloudFormation parameter to a SSM Parameter Store item that holds the ARN of the Anghammarad SNS topic.
* This parameter is implemented as a singleton, meaning only one can ever be added to a stack and will be reused if necessary.
*
* @see https://github.com/guardian/anghammarad
*/
export class AnghammaradTopicParameter extends GuStringParameter {
private static instance: AnghammaradTopicParameter | undefined;

private constructor(scope: GuStack) {
super(scope, "AnghammaradSnsArn", {
fromSSM: true,
default: "/account/services/anghammarad.topic.arn",
description: "SSM parameter containing the ARN of the Anghammarad SNS topic",
});
}

/**
* Returns a pre-existing parameter in the stack.
* If no parameter exists, creates a new parameter.
*
* @param stack the stack to operate on
*/
public static getInstance(stack: GuStack): AnghammaradTopicParameter {
if (!this.instance || !isSingletonPresentInStack(stack, this.instance)) {
this.instance = new AnghammaradTopicParameter(stack);
}

return this.instance;
}
}
60 changes: 60 additions & 0 deletions src/constructs/iam/policies/anghammarad.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import "@aws-cdk/assert/jest";
import { SynthUtils } from "@aws-cdk/assert/lib/synth-utils";
import type { SynthedStack } from "../../../utils/test";
import { attachPolicyToTestRole, simpleGuStackForTesting } from "../../../utils/test";
import type { GuStack } from "../../core";
import { AnghammaradTopicParameter } from "../../core/parameters/anghammarad";
import { AnghammaradSenderPolicy } from "./anghammarad";

describe("AnghammaradSenderPolicy", () => {
const getParams = (stack: GuStack) => {
const json = SynthUtils.toCloudFormation(stack) as SynthedStack;
return Object.keys(json.Parameters);
};

it("should add a parameter to the stack if it is not already defined", () => {
const stack = simpleGuStackForTesting();

// an empty stack should only have `Stage` which GuStack adds
expect(getParams(stack)).toEqual(["Stage"]);

// add the policy
attachPolicyToTestRole(stack, AnghammaradSenderPolicy.getInstance(stack));
expect(getParams(stack)).toEqual(["Stage", "AnghammaradSnsArn"]);
});

it("should not add a parameter to the stack if it already exists", () => {
const stack = simpleGuStackForTesting();

// an empty stack should only have `Stage` which GuStack adds
expect(getParams(stack)).toEqual(["Stage"]);

// explicitly add an AnghammaradTopicParameter
AnghammaradTopicParameter.getInstance(stack);
expect(getParams(stack)).toEqual(["Stage", "AnghammaradSnsArn"]);

// add the policy
attachPolicyToTestRole(stack, AnghammaradSenderPolicy.getInstance(stack));
expect(getParams(stack)).toEqual(["Stage", "AnghammaradSnsArn"]);
});

it("should define a policy that would allow writing to SNS", () => {
const stack = simpleGuStackForTesting();
attachPolicyToTestRole(stack, AnghammaradSenderPolicy.getInstance(stack));

expect(stack).toHaveResource("AWS::IAM::Policy", {
PolicyDocument: {
Version: "2012-10-17",
Statement: [
{
Action: "sns:Publish",
Effect: "Allow",
Resource: {
Ref: "AnghammaradSnsArn",
},
},
],
},
});
});
});
32 changes: 32 additions & 0 deletions src/constructs/iam/policies/anghammarad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { isSingletonPresentInStack } from "../../../utils/test";
import type { GuStack } from "../../core";
import { AnghammaradTopicParameter } from "../../core/parameters/anghammarad";
import { GuAllowPolicy } from "./base-policy";

/**
* Creates an `AWS::IAM::Policy` to grant `sns:Publish` permission to the Anghammarad topic.
* An `AnghammaradSnsArn` parameter will be automatically added to the stack when needed.
*
* @see AnghammaradTopicParameter
* @see https://github.com/guardian/anghammarad
*/
export class AnghammaradSenderPolicy extends GuAllowPolicy {
private static instance: AnghammaradSenderPolicy | undefined;

private constructor(scope: GuStack) {
const anghammaradTopicParameter = AnghammaradTopicParameter.getInstance(scope);

super(scope, "GuSESSenderPolicy", {
actions: ["sns:Publish"],
resources: [anghammaradTopicParameter.valueAsString],
});
}

public static getInstance(stack: GuStack): AnghammaradSenderPolicy {
if (!this.instance || !isSingletonPresentInStack(stack, this.instance)) {
this.instance = new AnghammaradSenderPolicy(stack);
}

return this.instance;
}
}