Skip to content

Commit

Permalink
Merge branch 'main' into feat/braze-lists
Browse files Browse the repository at this point in the history
  • Loading branch information
bassrock authored Aug 29, 2024
2 parents 7ca1354 + 3ff2f95 commit 4bf252e
Show file tree
Hide file tree
Showing 10 changed files with 2,068 additions and 628 deletions.
88 changes: 87 additions & 1 deletion infrastructure/braze-content-proxy/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
dataAwsRegion,
dataAwsKmsAlias,
dataAwsSnsTopic,
wafv2WebAcl,
wafv2IpSet,
} from '@cdktf/provider-aws';
import { config } from './config';
import { PocketALBApplication } from '@pocket-tools/terraform-modules';
Expand Down Expand Up @@ -46,6 +48,85 @@ class BrazeContentProxy extends TerraformStack {
snsTopic: this.getCodeDeploySnsTopic(),
region,
caller,
wafAcl: this.createWafACL(),
});
}

/**
* Ensure that only internal IPs from the VPN or from Braze are allowed to access
* @returns Waf ACL
*/
private createWafACL() {
// Braze IPs
// We are on US-05
// https://www.braze.com/docs/user_guide/personalization_and_dynamic_content/connected_content/making_an_api_call/#connected-content-ip-allowlisting
const brazeIPList = [
'23.21.118.191/32',
'34.206.23.173/32',
'50.16.249.9/32',
'52.4.160.214/32',
'54.87.8.34/32',
'54.156.35.251/32',
'52.54.89.238/32',
'18.205.178.15/32',
];

const allowListIPs = new wafv2IpSet.Wafv2IpSet(this, 'AllowlistIPs', {
name: `${config.name}-${config.environment}-AllowList`,
ipAddressVersion: 'IPV4',
scope: 'CLOUDFRONT',
tags: config.tags,
addresses: brazeIPList,
});

const ipAllowListRule = <wafv2WebAcl.Wafv2WebAclRule>{
name: `${config.name}-${config.environment}-ipAllowList`,
priority: 1,
action: { allow: {} },
statement: {
ip_set_reference_statement: {
arn: allowListIPs.arn,
},
},
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: `${config.name}-${config.environment}-ipAllowList`,
sampledRequestsEnabled: true,
},
};

// When we are ready to block, make the default action block and remove this.
const blockAllIps = <wafv2WebAcl.Wafv2WebAclRule>{
name: `${config.name}-${config.environment}-ipBlockAll`,
priority: 2,
action: { count: {} }, //doing a count before we do a block.
statement: {
not_statement: {
statement: {
ip_set_reference_statement: {
arn: allowListIPs.arn,
},
},
},
},
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: `${config.name}-${config.environment}-ipBlockAll`,
sampledRequestsEnabled: true,
},
};

return new wafv2WebAcl.Wafv2WebAcl(this, `${config.name}-waf`, {
description: `Waf for ${config.name} ${config.environment} environment`,
name: `${config.name}-waf-${config.environment}`,
scope: 'CLOUDFRONT',
defaultAction: { allow: {} },
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: `${config.name}-waf-${config.environment}`,
sampledRequestsEnabled: true,
},
rule: [ipAllowListRule, blockAllIps],
});
}

Expand Down Expand Up @@ -74,14 +155,19 @@ class BrazeContentProxy extends TerraformStack {
caller: dataAwsCallerIdentity.DataAwsCallerIdentity;
secretsManagerKmsAlias: dataAwsKmsAlias.DataAwsKmsAlias;
snsTopic: dataAwsSnsTopic.DataAwsSnsTopic;
wafAcl: wafv2WebAcl.Wafv2WebAcl;
}): PocketALBApplication {
const { region, caller, secretsManagerKmsAlias, snsTopic } = dependencies;
const { region, caller, secretsManagerKmsAlias, snsTopic, wafAcl } =
dependencies;

return new PocketALBApplication(this, 'application', {
internal: false,
prefix: config.prefix,
alb6CharacterPrefix: config.shortName,
cdn: true,
wafConfig: {
aclArn: wafAcl.arn,
},
domain: config.domain,
containerConfigs: [
{
Expand Down
27 changes: 12 additions & 15 deletions infrastructure/client-api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
dataAwsKmsAlias,
dataAwsSnsTopic,
dataAwsSubnets,
wafv2IpSet,
wafv2WebAcl,
} from '@cdktf/provider-aws';
import { provider as localProvider } from '@cdktf/provider-local';
import { provider as nullProvider } from '@cdktf/provider-null';
Expand All @@ -22,11 +24,6 @@ import { App, S3Backend, TerraformStack } from 'cdktf';
import { Construct } from 'constructs';
import fs from 'fs';

import { Wafv2IpSet } from '@cdktf/provider-aws/lib/wafv2-ip-set';
import {
Wafv2WebAclRule,
Wafv2WebAcl,
} from '@cdktf/provider-aws/lib/wafv2-web-acl';
class ClientAPI extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name);
Expand Down Expand Up @@ -102,15 +99,15 @@ class ClientAPI extends TerraformStack {

const ipList = config.environment === 'Prod' ? ipListProd : ipListDev;

const allowListIPs = new Wafv2IpSet(this, 'AllowlistIPs', {
const allowListIPs = new wafv2IpSet.Wafv2IpSet(this, 'AllowlistIPs', {
name: `${config.name}-${config.environment}-AllowList`,
ipAddressVersion: 'IPV4',
scope: 'REGIONAL',
scope: 'CLOUDFRONT',
tags: config.tags,
addresses: ipList,
});

const ipAllowListRule = <Wafv2WebAclRule>{
const ipAllowListRule = <wafv2WebAcl.Wafv2WebAclRule>{
name: `${config.name}-${config.environment}-ipAllowList`,
priority: 1,
action: { allow: {} },
Expand All @@ -126,8 +123,8 @@ class ClientAPI extends TerraformStack {
},
};

const regionalRateLimitRule = <Wafv2WebAclRule>{
name: `${config.name}-${config.environment}-RegionalRateLimit`,
const cloudfrontRateLimitRule = <wafv2WebAcl.Wafv2WebAclRule>{
name: `${config.name}-${config.environment}-CloudfrontRateLimit`,
priority: 2,
action: { count: {} },
statement: {
Expand All @@ -147,22 +144,22 @@ class ClientAPI extends TerraformStack {
},
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: `${config.name}-${config.environment}-RegionalRateLimit`,
metricName: `${config.name}-${config.environment}-CloudfrontRateLimit`,
sampledRequestsEnabled: true,
},
};

return new Wafv2WebAcl(this, `${config.name}-waf`, {
return new wafv2WebAcl.Wafv2WebAcl(this, `${config.name}-waf`, {
description: `Waf for client-api-proxy ${config.environment} environment`,
name: `${config.name}-waf-${config.environment}`,
scope: 'REGIONAL',
scope: 'CLOUDFRONT',
defaultAction: { allow: {} },
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: `${config.name}-waf-${config.environment}`,
sampledRequestsEnabled: true,
},
rule: [ipAllowListRule, regionalRateLimitRule],
rule: [ipAllowListRule, cloudfrontRateLimitRule],
});
}

Expand Down Expand Up @@ -192,7 +189,7 @@ class ClientAPI extends TerraformStack {
secretsManagerKmsAlias: dataAwsKmsAlias.DataAwsKmsAlias;
snsTopic: dataAwsSnsTopic.DataAwsSnsTopic;
cache: string;
wafAcl: Wafv2WebAcl;
wafAcl: wafv2WebAcl.Wafv2WebAcl;
}): PocketALBApplication {
const { region, caller, secretsManagerKmsAlias, cache, snsTopic, wafAcl } =
dependencies;
Expand Down
32 changes: 26 additions & 6 deletions packages/terraform-modules/src/base/ApplicationLoadBalancer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
alb,
dataAwsElbServiceAccount,
dataAwsS3Bucket,
dataAwsEc2ManagedPrefixList,
} from '@cdktf/provider-aws';
import { TerraformMetaArguments, TerraformProvider } from 'cdktf';
import { Construct } from 'constructs';
Expand All @@ -16,6 +17,7 @@ export interface ApplicationLoadBalancerProps extends TerraformMetaArguments {
vpcId: string;
subnetIds: string[];
internal?: boolean;
useCloudfrontManagedPrefixList?: boolean;
/**
* Optional config to dump alb logs to a bucket.
*/
Expand Down Expand Up @@ -53,6 +55,28 @@ export class ApplicationLoadBalancer extends Construct {
) {
super(scope, name);

let ingress: securityGroup.SecurityGroupIngress = {
fromPort: 443,
toPort: 443,
protocol: 'TCP',
cidrBlocks: ['0.0.0.0/0'],
};

if (config.useCloudfrontManagedPrefixList) {
const prefixList =
new dataAwsEc2ManagedPrefixList.DataAwsEc2ManagedPrefixList(
this,
'alb_cloudfront_security',
{ name: 'com.amazonaws.global.cloudfront.origin-facing' },
);
ingress = {
fromPort: 443,
toPort: 443,
protocol: 'TCP',
prefixListIds: [prefixList.id],
};
}

this.securityGroup = new securityGroup.SecurityGroup(
this,
`alb_security_group`,
Expand All @@ -61,13 +85,9 @@ export class ApplicationLoadBalancer extends Construct {
description: 'External security group (Managed by Terraform)',
vpcId: config.vpcId,
ingress: [
ingress,
{
fromPort: 443,
toPort: 443,
protocol: 'TCP',
cidrBlocks: ['0.0.0.0/0'],
},
{
// Allow anything on Port 80 because its always a redirect to 443 which could have blocks for Cloudfront only
fromPort: 80,
toPort: 80,
protocol: 'TCP',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('PocketALBApplication', () => {
expect(synthed).toMatchSnapshot();
});

it('renders an external application', () => {
it('renders an external application with a cdn', () => {
const synthed = Testing.synthScope((stack) => {
BASE_CONFIG.internal = false;
BASE_CONFIG.cdn = true;
Expand All @@ -48,6 +48,38 @@ describe('PocketALBApplication', () => {
expect(synthed).toMatchSnapshot();
});

it('renders an external application with a waf', () => {
const synthed = Testing.synthScope((stack) => {
BASE_CONFIG.internal = false;
BASE_CONFIG.cdn = false;
BASE_CONFIG.wafConfig = { aclArn: 'some-arn' };

new PocketALBApplication(stack, 'testPocketApp', BASE_CONFIG);
});
expect(synthed).toMatchSnapshot();
});

it('renders an external application without a cdn', () => {
const synthed = Testing.synthScope((stack) => {
BASE_CONFIG.internal = false;
BASE_CONFIG.cdn = false;

new PocketALBApplication(stack, 'testPocketApp', BASE_CONFIG);
});
expect(synthed).toMatchSnapshot();
});

it('renders an external application with a CDN and a waf', () => {
const synthed = Testing.synthScope((stack) => {
BASE_CONFIG.internal = false;
BASE_CONFIG.cdn = true;
BASE_CONFIG.wafConfig = { aclArn: 'some-arn' };

new PocketALBApplication(stack, 'testPocketApp', BASE_CONFIG);
});
expect(synthed).toMatchSnapshot();
});

it('renders an internal application', () => {
const synthed = Testing.synthScope((stack) => {
BASE_CONFIG.internal = true;
Expand Down
12 changes: 10 additions & 2 deletions packages/terraform-modules/src/pocket/PocketALBApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,8 @@ export class PocketALBApplication extends Construct {
this.createCDN(albRecord);
}

if (config.wafConfig) {
// If we don't have a CDN add the WAF to the ALB
if (config.wafConfig && !config.cdn) {
this.createWAF(alb, config.wafConfig.aclArn);
}

Expand Down Expand Up @@ -419,6 +420,8 @@ export class PocketALBApplication extends Construct {
tags: this.config.tags,
accessLogs: this.config.accessLogs,
provider: this.config.provider,
useCloudfrontManagedPrefixList:
this.config.cdn && this.config.wafConfig !== null,
});

//When the app uses a CDN we set the ALB to be direct.app-domain
Expand Down Expand Up @@ -468,7 +471,9 @@ export class PocketALBApplication extends Construct {
* @param albRecord
* @private
*/
private createCDN(albRecord: route53Record.Route53Record): void {
private createCDN(
albRecord: route53Record.Route53Record,
): cloudfrontDistribution.CloudfrontDistribution {
//Create the certificate for the CDN
const cdnCertificate = new ApplicationCertificate(this, `cdn_certificate`, {
zoneId: this.baseDNS.zoneId,
Expand All @@ -487,6 +492,7 @@ export class PocketALBApplication extends Construct {
aliases: [this.config.domain],
priceClass: 'PriceClass_200',
tags: this.config.tags,
webAclId: this.config.wafConfig?.aclArn ?? undefined,
origin: [
{
domainName: albRecord.fqdn,
Expand Down Expand Up @@ -564,6 +570,8 @@ export class PocketALBApplication extends Construct {
setIdentifier: '2',
provider: this.config.provider,
});

return cdn;
}

/**
Expand Down
Loading

0 comments on commit 4bf252e

Please sign in to comment.