Skip to content

Commit

Permalink
Merge pull request #244 from guardian/aa-acm-construct
Browse files Browse the repository at this point in the history
feat: create a GuCertificateArnParameter construct
  • Loading branch information
akash1810 authored Feb 17, 2021
2 parents c395d80 + bea9c61 commit 1058f4f
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 9 deletions.
6 changes: 6 additions & 0 deletions src/constants/regex-pattern.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@ describe("the regex patterns", () => {
expect(regex.test("[email protected]")).toBeFalsy();
expect(regex.test("[email protected]")).toBeFalsy();
});

it("should successfully regex against ACM ARNs", () => {
const regex = new RegExp(RegexPattern.ACM_ARN);
expect(regex.test("arn:aws:acm:eu-west-1:000000000000:certificate/123abc-0000-0000-0000-123abc")).toBeTruthy();
expect(regex.test("arn:aws:acm:eu-west-1:000000000000:tls/123abc-0000-0000-0000-123abc")).toBeFalsy();
});
});
4 changes: 4 additions & 0 deletions src/constants/regex-pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ const s3ArnRegex = `arn:aws:s3:::${s3BucketRegex}*`;

const emailRegex = "^[a-zA-Z]+(\\.[a-zA-Z]+)*@theguardian.com$";

// TODO be more strict on region?
const acmRegex = "arn:aws:acm:[0-9a-z\\-]+:[0-9]{12}:certificate/[0-9a-z\\-]+";

export const RegexPattern = {
ARN: arnRegex,
S3ARN: s3ArnRegex,
GUARDIAN_EMAIL: emailRegex,
ACM_ARN: acmRegex,
};
10 changes: 10 additions & 0 deletions src/constructs/core/parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,13 @@ export class GuGuardianEmailSenderParameter extends GuStringParameter {
});
}
}

export class GuCertificateArnParameter extends GuStringParameter {
constructor(scope: GuStack, id: string = "TLSCertificate", props?: GuNoTypeParameterProps) {
super(scope, id, {
...props,
allowedPattern: RegexPattern.ACM_ARN,
constraintDescription: "Must be an ACM ARN resource",
});
}
}
32 changes: 26 additions & 6 deletions src/constructs/loadbalancing/elb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ApplicationProtocol, ListenerAction } from "@aws-cdk/aws-elasticloadbal
import { Stack } from "@aws-cdk/core";
import { simpleGuStackForTesting } from "../../../test/utils/simple-gu-stack";
import type { SynthedStack } from "../../../test/utils/synthed-stack";
import { RegexPattern } from "../../constants";
import {
GuApplicationListener,
GuApplicationLoadBalancer,
Expand Down Expand Up @@ -331,24 +332,43 @@ describe("The GuHttpsApplicationListener class", () => {

const json = SynthUtils.toCloudFormation(stack) as SynthedStack;

expect(json.Parameters.CertificateARN).toEqual({
expect(json.Parameters.TLSCertificate).toEqual({
Type: "String",
AllowedPattern: "arn:aws:[a-z0-9]*:[a-z0-9\\-]*:[0-9]{12}:.*",
ConstraintDescription: "Must be a valid ARN, eg: arn:partition:service:region:account-id:resource-id",
AllowedPattern: RegexPattern.ACM_ARN,
ConstraintDescription: "Must be an ACM ARN resource",
Description: "Certificate ARN for ApplicationListener",
});

expect(stack).toHaveResource("AWS::ElasticLoadBalancingV2::Listener", {
Certificates: [
{
CertificateArn: {
Ref: "CertificateARN",
Ref: "TLSCertificate",
},
},
],
});
});

test("passing in an invalid ACM ARN", () => {
const stack = simpleGuStackForTesting();

const loadBalancer = new GuApplicationLoadBalancer(stack, "ApplicationLoadBalancer", { vpc });
const targetGroup = new GuApplicationTargetGroup(stack, "GrafanaInternalTargetGroup", {
vpc: vpc,
protocol: ApplicationProtocol.HTTP,
});

expect(
() =>
new GuHttpsApplicationListener(stack, "ApplicationListener", {
loadBalancer,
targetGroup,
certificate: "test",
})
).toThrowError(new Error("test is not a valid ACM ARN"));
});

test("does not create certificate prop if a value passed in", () => {
const stack = simpleGuStackForTesting();

Expand All @@ -361,7 +381,7 @@ describe("The GuHttpsApplicationListener class", () => {
new GuHttpsApplicationListener(stack, "ApplicationListener", {
loadBalancer,
targetGroup,
certificate: "test",
certificate: "arn:aws:acm:eu-west-1:000000000000:certificate/123abc-0000-0000-0000-123abc",
});

const json = SynthUtils.toCloudFormation(stack) as SynthedStack;
Expand All @@ -371,7 +391,7 @@ describe("The GuHttpsApplicationListener class", () => {
expect(stack).toHaveResource("AWS::ElasticLoadBalancingV2::Listener", {
Certificates: [
{
CertificateArn: "test",
CertificateArn: "arn:aws:acm:eu-west-1:000000000000:certificate/123abc-0000-0000-0000-123abc",
},
],
});
Expand Down
15 changes: 12 additions & 3 deletions src/constructs/loadbalancing/elb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import {
Protocol,
} from "@aws-cdk/aws-elasticloadbalancingv2";
import { Duration } from "@aws-cdk/core";
import { RegexPattern } from "../../constants";
import type { GuStack } from "../core";
import { GuArnParameter } from "../core";
import { GuCertificateArnParameter } from "../core";

interface GuApplicationLoadBalancerProps extends ApplicationLoadBalancerProps {
overrideId?: boolean;
Expand Down Expand Up @@ -84,6 +85,13 @@ export interface GuHttpsApplicationListenerProps

export class GuHttpsApplicationListener extends ApplicationListener {
constructor(scope: GuStack, id: string, props: GuHttpsApplicationListenerProps) {
if (props.certificate) {
const isValid = new RegExp(RegexPattern.ACM_ARN).test(props.certificate);
if (!isValid) {
throw new Error(`${props.certificate} is not a valid ACM ARN`);
}
}

const mergedProps: GuApplicationListenerProps = {
port: 443,
protocol: ApplicationProtocol.HTTPS,
Expand All @@ -92,8 +100,9 @@ export class GuHttpsApplicationListener extends ApplicationListener {
{
certificateArn:
props.certificate ??
new GuArnParameter(scope, "CertificateARN", { description: "Certificate ARN for ApplicationListener" })
.valueAsString,
new GuCertificateArnParameter(scope, "TLSCertificate", {
description: `Certificate ARN for ${id}`,
}).valueAsString,
},
],
defaultAction: ListenerAction.forward([props.targetGroup]),
Expand Down

0 comments on commit 1058f4f

Please sign in to comment.