Skip to content

Commit

Permalink
Merge pull request #2217 from vrk-kpa/AV-2221_implement_new_waf_confi…
Browse files Browse the repository at this point in the history
…guration_options

AV-2221: Implement new waf configuration options
  • Loading branch information
Zharktas authored Apr 30, 2024
2 parents 62c8173 + 4ed6184 commit 2148e90
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 106 deletions.
39 changes: 39 additions & 0 deletions cdk/bin/opendata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {DomainStack} from "../lib/domain-stack";
import {CiTestStack} from "../lib/ci-test-stack";
import {SubDomainStack} from "../lib/sub-domain-stack";
import {ShieldStack} from "../lib/shield-stack";
import {CloudfrontParameterStack} from "../lib/cloudfront-parameter-stack";
import {undefined} from "zod";

// load .env file, shared with docker setup
// mainly for ECR repo and image tag information
Expand Down Expand Up @@ -164,6 +166,15 @@ const bypassCdnStackBeta = new BypassCdnStack(app, 'BypassCdnStack-beta', {
loadbalancer: loadBalancerStackBeta.loadBalancer
})

const cloudfrontParameterStackBeta = new CloudfrontParameterStack(app, 'CloudfrontParameterStack-beta', {
env: {
account: betaProps.account,
region: 'us-east-1',
},
environment: betaProps.environment,
})


const shieldStackBeta = new ShieldStack(app, 'ShieldStack-beta', {
env: {
account: betaProps.account,
Expand All @@ -174,6 +185,16 @@ const shieldStackBeta = new ShieldStack(app, 'ShieldStack-beta', {
highPriorityRequestSamplingEnabled: false,
rateLimitRequestSamplingEnabled: false,
requestSampleAllTrafficEnabled: false,
cloudfrontDistributionArn: cloudfrontParameterStackBeta.cloudFrontDistributionArn,
bannedIpListParameterName: cloudfrontParameterStackBeta.bannedIpListParameterName,
whitelistedIpListParameterName: cloudfrontParameterStackBeta.whitelistedIpListParameterName,
highPriorityCountryCodeListParameterName: cloudfrontParameterStackBeta.highPriorityCountryCodeListParameterName,
highPriorityRateLimit: cloudfrontParameterStackBeta.highPriorityRateLimit,
rateLimit: cloudfrontParameterStackBeta.rateLimit,
managedRulesParameterName: cloudfrontParameterStackBeta.managedRulesParameterName,
snsTopicArn: cloudfrontParameterStackBeta.snsTopicArn,
wafAutomationArn: cloudfrontParameterStackBeta.wafAutomationArn,
evaluationPeriod: cloudfrontParameterStackBeta.evaluationPeriod
})

const cacheStackBeta = new CacheStack(app, 'CacheStack-beta', {
Expand Down Expand Up @@ -465,6 +486,14 @@ const bypassCdnStackProd = new BypassCdnStack(app, 'BypassCdnStack-prod', {
loadbalancer: loadBalancerStackProd.loadBalancer
})

const cloudfrontParameterStackProd = new CloudfrontParameterStack(app, 'CloudfrontParameterStack-prod', {
env: {
account: prodProps.account,
region: 'us-east-1'
},
environment: prodProps.environment,
})

const shieldStackProd = new ShieldStack(app, 'ShieldStack-prod', {
env: {
account: prodProps.account,
Expand All @@ -475,6 +504,16 @@ const shieldStackProd = new ShieldStack(app, 'ShieldStack-prod', {
highPriorityRequestSamplingEnabled: false,
rateLimitRequestSamplingEnabled: false,
requestSampleAllTrafficEnabled: false,
cloudfrontDistributionArn: cloudfrontParameterStackProd.cloudFrontDistributionArn,
bannedIpListParameterName: cloudfrontParameterStackProd.bannedIpListParameterName,
whitelistedIpListParameterName: cloudfrontParameterStackProd.whitelistedIpListParameterName,
highPriorityCountryCodeListParameterName: cloudfrontParameterStackProd.highPriorityCountryCodeListParameterName,
highPriorityRateLimit: cloudfrontParameterStackProd.highPriorityRateLimit,
rateLimit: cloudfrontParameterStackProd.rateLimit,
managedRulesParameterName: cloudfrontParameterStackProd.managedRulesParameterName,
snsTopicArn: cloudfrontParameterStackProd.snsTopicArn,
wafAutomationArn: cloudfrontParameterStackProd.wafAutomationArn,
evaluationPeriod: cloudfrontParameterStackProd.evaluationPeriod
})

const cacheStackProd = new CacheStack(app, 'CacheStack-prod', {
Expand Down
85 changes: 85 additions & 0 deletions cdk/lib/cloudfront-parameter-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {aws_ssm, CfnParameter, Stack} from "aws-cdk-lib";
import {Construct} from "constructs";

import {EnvStackProps} from "./env-stack-props";

export class CloudfrontParameterStack extends Stack {
readonly cloudFrontDistributionArn: aws_ssm.IStringParameter;
readonly bannedIpListParameterName: string;
readonly whitelistedIpListParameterName: string;
readonly highPriorityCountryCodeListParameterName: string;
readonly highPriorityRateLimit: aws_ssm.IStringParameter;
readonly rateLimit: aws_ssm.IStringParameter;
readonly managedRulesParameterName: string;
readonly wafAutomationArn: aws_ssm.IStringParameter;
readonly snsTopicArn: aws_ssm.IStringParameter;
readonly evaluationPeriod: aws_ssm.IStringParameter;

constructor(scope: Construct, id: string, props: EnvStackProps ) {
super(scope, id, props);

this.cloudFrontDistributionArn = new aws_ssm.StringParameter(this, 'cloudfrontDistributionArn', {
stringValue: 'some placeholder',
description: 'Arn of cloudfront distribution',
parameterName: `/${props.environment}/waf/cloudfrontDistributionArn`,
})

this.bannedIpListParameterName = `/${props.environment}/waf/banned_ips`
new aws_ssm.StringListParameter(this, 'bannedIplist', {
stringListValue: ["127.0.0.1"],
description: 'List of banned IP addresses',
parameterName: this.bannedIpListParameterName
})

this.whitelistedIpListParameterName = `/${props.environment}/waf/whitelisted_ips`
new aws_ssm.StringListParameter(this, 'whitelistedIplist', {
stringListValue: ["127.0.0.1"],
description: 'List of whitelisted IP addresses',
parameterName: this.whitelistedIpListParameterName
})

this.highPriorityCountryCodeListParameterName = `/${props.environment}/waf/high_priority_country_codes`
new aws_ssm.StringListParameter(this, 'highPriorityCountryCodeList', {
stringListValue: ["Some bogus country code"],
description: 'Country codes deemed high priority',
parameterName: this.highPriorityCountryCodeListParameterName
})

this.highPriorityRateLimit = new aws_ssm.StringParameter(this, 'highPriorityRateLimit', {
stringValue: '0',
description: 'Rate limit for high priority country codes',
parameterName: `/${props.environment}/waf/high_priority_rate_limit`
})

this.rateLimit = new aws_ssm.StringParameter(this, 'rateLimit', {
stringValue: '0',
description: 'Rate limit for others',
parameterName: `/${props.environment}/waf/rate_limit`
})

this.managedRulesParameterName = `/${props.environment}/waf/managed_rules`
new aws_ssm.StringParameter(this, 'managedRules', {
stringValue: 'some placeholder',
description: 'JSON value for managed rules',
parameterName: this.managedRulesParameterName
})

this.wafAutomationArn = new aws_ssm.StringParameter(this, 'wafAutomationArn', {
stringValue: 'some placeholder',
description: 'Arn of waf automation lambda',
parameterName: `/${props.environment}/waf/waf_automation_arn`,
})

this.snsTopicArn = new aws_ssm.StringParameter(this, 'snsTopicArn', {
stringValue: 'some placeholder',
description: 'Arn of sns topic',
parameterName: `/${props.environment}/waf/sns_topic_arn`,
})

this.evaluationPeriod = new aws_ssm.StringParameter(this, 'evaluationPeriod', {
stringValue: '0',
description: 'Evaluation period for rate limits',
parameterName: `/${props.environment}/waf/evaluation_period`
})
}
}
13 changes: 12 additions & 1 deletion cdk/lib/shield-stack-props.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import {EnvStackProps} from "./env-stack-props";
import {aws_ssm} from "aws-cdk-lib";

export interface ShieldStackProps extends EnvStackProps{
bannedIpsRequestSamplingEnabled: boolean,
requestSampleAllTrafficEnabled: boolean,
highPriorityRequestSamplingEnabled: boolean,
rateLimitRequestSamplingEnabled: boolean
rateLimitRequestSamplingEnabled: boolean,
cloudfrontDistributionArn: aws_ssm.IStringParameter,
bannedIpListParameterName: string,
whitelistedIpListParameterName: string,
highPriorityCountryCodeListParameterName: string,
highPriorityRateLimit: aws_ssm.IStringParameter,
rateLimit: aws_ssm.IStringParameter,
managedRulesParameterName: string,
wafAutomationArn: aws_ssm.IStringParameter,
snsTopicArn: aws_ssm.IStringParameter,
evaluationPeriod: aws_ssm.IStringParameter
}
123 changes: 81 additions & 42 deletions cdk/lib/shield-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,21 @@ import {Construct} from "constructs";

import {ShieldStackProps} from "./shield-stack-props";

import { z } from "zod";

export class ShieldStack extends Stack {
constructor(scope: Construct, id: string, props: ShieldStackProps) {
super(scope, id, props);

const cloudfrontDistributionArn = aws_ssm.StringParameter.fromStringParameterName(this,'cloudfrontDistributionArn',
`/${props.environment}/waf/cloudfrontDistributionArn`)

const cfnProtection = new aws_shield.CfnProtection(this, 'ShieldProtection', {
name: 'Cloudfront distribution',
resourceArn: cloudfrontDistributionArn.stringValue
resourceArn: props.cloudfrontDistributionArn.stringValue
})


const banned_ips = new CfnParameter(this, 'bannedIpsList', {
type: 'AWS::SSM::Parameter::Value<List<String>>',
default: `/${props.environment}/waf/banned_ips`
default: props.bannedIpListParameterName
})

const cfnBannedIPSet = new aws_wafv2.CfnIPSet(this, 'BannedIPSet', {
Expand All @@ -38,7 +36,7 @@ export class ShieldStack extends Stack {

const whitelisted_ips = new CfnParameter(this, 'whitelistedIpsList', {
type: 'AWS::SSM::Parameter::Value<List<String>>',
default: `/${props.environment}/waf/whitelisted_ips`
default: props.whitelistedIpListParameterName
})

const cfnWhiteListedIpSet = new aws_wafv2.CfnIPSet(this, 'WhitelistedIPSet', {
Expand All @@ -51,21 +49,10 @@ export class ShieldStack extends Stack {

const highPriorityCountryCodesParameter = new CfnParameter(this, 'highPriorityCountryCodesParameter', {
type: 'AWS::SSM::Parameter::Value<List<String>>',
default: `/${props.environment}/waf/high_priority_country_codes`
default: props.highPriorityCountryCodeListParameterName
});


const highPriorityRateLimit = aws_ssm.StringParameter.fromStringParameterName(this, 'highPriorityRateLimit',
`/${props.environment}/waf/high_priority_rate_limit`);

const rateLimit = aws_ssm.StringParameter.fromStringParameterName(this, 'rateLimit',
`/${props.environment}/waf/rate_limit`);


const managedRulesParameter = aws_ssm.StringParameter.valueFromLookup(this, `/${props.environment}/waf/managed_rules`)

const managedRules = managedRulesParameter.startsWith("dummy-value") ? "dummy" : JSON.parse(managedRulesParameter)

let rules = [
{
name: "block-banned_ips",
Expand Down Expand Up @@ -134,8 +121,9 @@ export class ShieldStack extends Stack {
},
statement: {
rateBasedStatement: {
limit: Token.asNumber(highPriorityRateLimit.stringValue),
limit: Token.asNumber(props.highPriorityRateLimit.stringValue),
aggregateKeyType: "IP",
evaluationWindowSec: Token.asNumber(props.evaluationPeriod.stringValue),
scopeDownStatement: {
geoMatchStatement: {
countryCodes: highPriorityCountryCodesParameter.valueAsList
Expand All @@ -158,8 +146,9 @@ export class ShieldStack extends Stack {
},
statement: {
rateBasedStatement: {
limit: Token.asNumber(rateLimit.stringValue),
limit: Token.asNumber(props.rateLimit.stringValue),
aggregateKeyType: "IP",
evaluationWindowSec: Token.asNumber(props.evaluationPeriod.stringValue),
scopeDownStatement: {
notStatement: {
statement: {
Expand All @@ -179,32 +168,89 @@ export class ShieldStack extends Stack {
},
]

type RuleGroup = {
groupName: string,
vendorName: string,
excludedRules: string[],
enableRequestSampling: boolean
}
const RuleGroupSchema = z.array(
z.object(
{
groupName: z.string(),
vendorName: z.string(),
ruleActionOverrideCounts: z.array(z.string()).default([]),
ruleActionOverrideAllows: z.array(z.string()).default([]),
ruleActionOverrideBlocks: z.array(z.string()).default([]),
ruleActionOverrideCaptchas: z.array(z.string()).default([]),
ruleActionOverrideChallenges: z.array(z.string()).default([]),
enableRequestSampling: z.boolean()
}
).strict()
)


const managedRulesParameter = aws_ssm.StringParameter.valueFromLookup(this, props.managedRulesParameterName)

const managedRules = managedRulesParameter.startsWith("dummy-value") ? "dummy" : JSON.parse(managedRulesParameter)


if ( managedRules !== "dummy"){
let ruleList: any[] = []

managedRules.forEach((rule: RuleGroup, index: number) => {
const validatedRules = RuleGroupSchema.parse(managedRules)
validatedRules.forEach((rule, index: number) => {

let ruleActionOverrides = []

for (let excludedRule of rule.excludedRules) {
let excludedRuleObj = {
for (let overrideCountRule of rule.ruleActionOverrideCounts) {
let overrideCountRuleObj = {
actionToUse: {
count: {}
},
name: excludedRule
name: overrideCountRule
}

ruleActionOverrides.push(excludedRuleObj)
ruleActionOverrides.push(overrideCountRuleObj)
}

for ( let overrideAllowRule of rule.ruleActionOverrideAllows) {
let overrideAllowRuleObj = {
actionToUse: {
allow: {}
},
name: overrideAllowRule
}

ruleActionOverrides.push(overrideAllowRuleObj)
}

for ( let overrideBlockRule of rule.ruleActionOverrideBlocks) {
let overrideBlockRuleObj = {
actionToUse: {
block: {}
},
name: overrideBlockRule
}

ruleActionOverrides.push(overrideBlockRuleObj)
}
for ( let overrideCaptchaRule of rule.ruleActionOverrideCaptchas) {
let overrideCaptchaRuleObj = {
actionToUse: {
captcha: {}
},
name: overrideCaptchaRule
}

ruleActionOverrides.push(overrideCaptchaRuleObj)
}
for ( let overrideChallengeRule of rule.ruleActionOverrideChallenges) {
let overrideChallengeRuleObj = {
actionToUse: {
challenge: {}
},
name: overrideChallengeRule
}

ruleActionOverrides.push(overrideChallengeRuleObj)
}



let managedRuleGroup: aws_wafv2.CfnWebACL.RuleProperty = {
name: "managed-rule-group-" + rule.groupName,
priority: 5 + index,
Expand Down Expand Up @@ -247,16 +293,9 @@ export class ShieldStack extends Stack {
rules: rules
})

const WafAutomationArn = aws_ssm.StringParameter.fromStringParameterName(this, 'WafAutomationArn',
`/${props.environment}/waf/waf_automation_arn`);

const WafAutomationLambdaFunction = aws_lambda.Function.fromFunctionArn(this, "WafAutomation", WafAutomationArn.stringValue)

const SNSTopicArn = aws_ssm.StringParameter.fromStringParameterName(this, 'SNSTopicArn',
`/${props.environment}/waf/sns_topic_arn`);


const topic = aws_sns.Topic.fromTopicArn(this, "SNSTopic", SNSTopicArn.stringValue)
const WafAutomationLambdaFunction = aws_lambda.Function.fromFunctionArn(this, "WafAutomation", props.wafAutomationArn.stringValue)

const topic = aws_sns.Topic.fromTopicArn(this, "SNSTopic", props.snsTopicArn.stringValue)

topic.addSubscription(new aws_sns_subscriptions.LambdaSubscription(WafAutomationLambdaFunction))

Expand Down
Loading

0 comments on commit 2148e90

Please sign in to comment.