Skip to content

Instantly share code, notes, and snippets.

@anaarezo
Last active June 28, 2024 10:00
Show Gist options
  • Save anaarezo/c2bc2f28cbfb7284b03af523321780bc to your computer and use it in GitHub Desktop.
Save anaarezo/c2bc2f28cbfb7284b03af523321780bc to your computer and use it in GitHub Desktop.
WAF CDK examples with WAF Stack.
// https://spoofing.medium.com/deploying-a-cloudfront-waf-with-typescript-and-aws-cdk-e35df6d7d00c
import * as cdk from "aws-cdk-lib";
import * as wafv2 from "aws-cdk-lib/aws-wafv2";
import { aws_opensearchservice as opensearchservice } from "aws-cdk-lib";
import { aws_kinesisfirehose as kinesisfirehose } from "aws-cdk-lib";
import { aws_s3 } from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager";
import * as logs from "aws-cdk-lib/aws-logs";
import { RetentionDays } from "aws-cdk-lib/aws-logs";
import * as iam from "aws-cdk-lib/aws-iam";
import { ApplicationStack, ApplicationStackProps } from "./application";
export function createCloudfrontWAF(
stack: ApplicationStack,
props: ApplicationStackProps
) {
///////////////////////////////////////////////////////////////////////////////////////////
//Creating IP Sets to Block/Allow IPs from WAF
///////////////////////////////////////////////////////////////////////////////////////////
const demoIPSet = new wafv2.CfnIPSet(stack, "DemoIPSet", {
addresses: [
"10.30.0.0/16", //VPC CIDR
"16.208.45.100/32", //Elastic IP
],
ipAddressVersion: "IPV4",
scope: "CLOUDFRONT",
});
///////////////////////////////////////////////////////////////////////////////////////////
//Creating Regex Patterns for WAF
///////////////////////////////////////////////////////////////////////////////////////////
// prettier-ignore
const regexPatternSet = new wafv2.CfnRegexPatternSet(
stack,
"DemoRegexPatternSet",
{
regularExpressionList: ["^\\/api\\/v1\\/demo"], //The sequence "\\" inserts a "\" in a string
scope: "CLOUDFRONT",
}
);
///////////////////////////////////////////////////////////////////////////////////////////
//Creating Rules for WAF
///////////////////////////////////////////////////////////////////////////////////////////
interface WafRule {
Rule: wafv2.CfnWebACL.RuleProperty;
}
const awsManagedRules: WafRule[] = [
{
Rule: {
name: "AllowInternalTraffic",
priority: 0,
statement: {
ipSetReferenceStatement: {
arn: demoIPSet.attrArn,
},
},
action: {
allow: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "AllowInternalTraffic",
},
},
},
{
Rule: {
name: "IPRateLimitingRule",
priority: 1,
statement: {
rateBasedStatement: {
limit: 600,
aggregateKeyType: "IP",
},
},
action: {
block: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "IPRateLimitingRule",
},
},
},
{
Rule: {
name: "AWS-AWSManagedRulesCommonRuleSet",
priority: 2,
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesCommonRuleSet",
excludedRules: [
{
name: "CrossSiteScripting_BODY",
},
{
name: "EC2MetaDataSSRF_BODY",
},
{
name: "GenericLFI_BODY",
},
{
name: "GenericRFI_BODY",
},
{
name: "SizeRestrictions_BODY",
},
],
},
},
overrideAction: {
none: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesCommonRuleSet",
},
},
},
{
Rule: {
name: "AWS-AWSManagedRulesBotControlRuleSet",
priority: 3,
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesBotControlRuleSet",
excludedRules: [
{
name: "CategoryAdvertising",
},
{
name: "CategoryContentFetcher",
},
{
name: "CategoryHttpLibrary",
},
{
name: "CategoryLinkChecker",
},
{
name: "CategoryMiscellaneous",
},
{
name: "CategoryMonitoring",
},
{
name: "CategorySeo",
},
{
name: "CategorySocialMedia",
},
{
name: "SignalAutomatedBrowser",
},
{
name: "SignalKnownBotDataCenter",
},
{
name: "SignalNonBrowserUserAgent",
},
],
},
},
overrideAction: {
none: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesBotControlRuleSet",
},
},
},
{
Rule: {
name: "AWS-AWSManagedRulesWordPressRuleSet",
priority: 4,
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesWordPressRuleSet",
},
},
overrideAction: {
none: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesWordPressRuleSet",
},
},
},
{
Rule: {
name: "AWS-AWSManagedRulesKnownBadInputsRuleSet",
priority: 5,
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesKnownBadInputsRuleSet",
},
},
overrideAction: {
none: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesKnownBadInputsRuleSet",
},
},
},
{
Rule: {
name: "AWS-AWSManagedRulesUnixRuleSet",
priority: 6,
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesUnixRuleSet",
excludedRules: [
{
name: "UNIXShellCommandsVariables_BODY",
},
],
},
},
overrideAction: {
none: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesUnixRuleSet",
},
},
},
{
Rule: {
name: "AWS-AWSManagedRulesSQLiRuleSet",
priority: 7,
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesSQLiRuleSet",
excludedRules: [
{
name: "SQLi_BODY",
},
],
},
},
overrideAction: {
none: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesSQLiRuleSet",
},
},
},
];
///////////////////////////////////////////////////////////////////////////////////////////
//Creating WebACL for WAF
///////////////////////////////////////////////////////////////////////////////////////////
const demoWebACL = new wafv2.CfnWebACL(stack, "WebACL", {
defaultAction: { allow: {} },
scope: "CLOUDFRONT",
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "WAFMetric",
sampledRequestsEnabled: true,
},
rules: awsManagedRules.map((wafRule) => wafRule.Rule),
});
///////////////////////////////////////////////////////////////////////////////////////////
//Creating OpenSearch for WAF
///////////////////////////////////////////////////////////////////////////////////////////
const serviceLinkedRole = new cdk.CfnResource(stack, "RoleOpenSearch", {
type: "AWS::IAM::ServiceLinkedRole",
properties: {
AWSServiceName: "es.amazonaws.com",
},
});
const openSG = new ec2.SecurityGroup(stack, "SecurityGroupOpenSearch", {
vpc: props.vpc,
allowAllOutbound: true,
});
openSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443));
const openSearchSecret = new secretsmanager.Secret(
stack,
"OpenSearchSecret",
{
generateSecretString: {
secretStringTemplate: JSON.stringify({
username: "master_user",
}),
generateStringKey: "password",
},
}
);
const esDomainName = "waf-logs-es-iac";
const elasticDomain = new opensearchservice.CfnDomain(stack, "OpenSearch", {
accessPolicies: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: {
AWS: "*",
},
Action: "es:*",
Resource: `arn:aws:es:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:domain/${esDomainName}/*`,
},
],
},
domainName: esDomainName,
engineVersion: "OpenSearch_1.3",
advancedSecurityOptions: {
enabled: true,
internalUserDatabaseEnabled: true,
masterUserOptions: {
masterUserName: openSearchSecret
.secretValueFromJson("username")
.unsafeUnwrap(),
masterUserPassword: openSearchSecret
.secretValueFromJson("password")
.unsafeUnwrap(),
},
},
domainEndpointOptions: {
enforceHttps: true,
},
clusterConfig: {
dedicatedMasterCount: 3,
dedicatedMasterEnabled: true,
dedicatedMasterType: "t3.small.search",
instanceCount: 2,
instanceType: "t3.small.search",
zoneAwarenessConfig: {
availabilityZoneCount: 2,
},
zoneAwarenessEnabled: true,
},
encryptionAtRestOptions: {
enabled: true,
},
nodeToNodeEncryptionOptions: {
enabled: true,
},
ebsOptions: {
ebsEnabled: true,
volumeSize: 10,
volumeType: "gp3",
},
vpcOptions: {
securityGroupIds: [openSG.securityGroupId],
subnetIds: [
props.vpc.selectSubnets({ subnetGroupName: "Private" }).subnetIds[0],
props.vpc.selectSubnets({ subnetGroupName: "Private" }).subnetIds[1],
],
},
});
elasticDomain.node.addDependency(serviceLinkedRole);
// ///////////////////////////////////////////////////////////////////////////////////////////
// //Creating Data Firehose Delivery Stream
// ///////////////////////////////////////////////////////////////////////////////////////////
const demoWAFBucket = new aws_s3.Bucket(stack, "DemoAllLogs", {
removalPolicy: cdk.RemovalPolicy.DESTROY,
encryption: aws_s3.BucketEncryption.S3_MANAGED,
autoDeleteObjects: true,
});
const logGroup = new logs.LogGroup(stack, "KinesisLogGroup", {
retention: RetentionDays.ONE_MONTH,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const logStream = new logs.LogStream(stack, "KinesisLogStream", {
logGroup: logGroup,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const s3logGroup = new logs.LogGroup(stack, "S3WAFLogGroup", {
retention: RetentionDays.ONE_MONTH,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const s3logStream = new logs.LogStream(stack, "S3WAFLogStream", {
logGroup: logGroup,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const firehoseRole = new iam.Role(stack, "WAFFirehoseRole", {
assumedBy: new iam.ServicePrincipal("firehose.amazonaws.com"),
});
const firehouseManagedPolicy = new iam.ManagedPolicy(
stack,
"FirehouseManagedPolicy",
{
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["es:*"],
resources: [
`arn:aws:es:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:domain/${esDomainName}/*`,
`arn:aws:es:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:domain/${esDomainName}`,
],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"ec2:DescribeVpcs",
"ec2:DescribeVpcAttribute",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeNetworkInterfaces",
"ec2:CreateNetworkInterface",
"ec2:CreateNetworkInterfacePermission",
"ec2:DeleteNetworkInterface",
],
resources: ["*"],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["logs:PutLogEvents"],
resources: [logGroup.logGroupArn, s3logGroup.logGroupArn],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["lambda:InvokeFunction", "lambda:GetFunctionConfiguration"],
resources: [
"arn:aws:lambda:us-east-1:884515407868:function:%FIREHOSE_POLICY_TEMPLATE_PLACEHOLDER%",
],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["kms:GenerateDataKey", "kms:Decrypt"],
resources: [
"arn:aws:kms:us-east-1:884515407868:key/%FIREHOSE_POLICY_TEMPLATE_PLACEHOLDER%",
],
conditions: {
StringEquals: {
"kms:ViaService": "s3.us-east-1.amazonaws.com",
},
StringLike: {
"kms:EncryptionContext:aws:s3:arn": [
"arn:aws:s3:::%FIREHOSE_POLICY_TEMPLATE_PLACEHOLDER%/*",
"arn:aws:s3:::%FIREHOSE_POLICY_TEMPLATE_PLACEHOLDER%",
],
},
},
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"kinesis:DescribeStream",
"kinesis:GetShardIterator",
"kinesis:GetRecords",
"kinesis:ListShards",
],
resources: ["*"],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["kms:Decrypt"],
resources: [
"arn:aws:kms:us-east-1:884515407868:key/%FIREHOSE_POLICY_TEMPLATE_PLACEHOLDER%",
],
conditions: {
StringEquals: {
"kms:ViaService": "kinesis.us-east-1.amazonaws.com",
},
StringLike: {
"kms:EncryptionContext:aws:kinesis:arn":
"arn:aws:kinesis:us-east-1:884515407868:stream/%FIREHOSE_POLICY_TEMPLATE_PLACEHOLDER%",
},
},
}),
],
}
);
firehoseRole.addManagedPolicy(firehouseManagedPolicy);
demoWAFBucket.grantReadWrite(firehoseRole);
const wafDeliveryStream = new kinesisfirehose.CfnDeliveryStream(
stack,
"WafIaCDeliveryStream",
{
amazonopensearchserviceDestinationConfiguration: {
domainArn: elasticDomain.attrArn,
indexName: "aws-waf-iac-logs",
indexRotationPeriod: "OneWeek",
retryOptions: {
durationInSeconds: 300,
},
roleArn: firehoseRole.roleArn,
s3BackupMode: "AllDocuments",
s3Configuration: {
bucketArn: demoWAFBucket.bucketArn,
roleArn: firehoseRole.roleArn,
prefix: "waf-logs-iac",
bufferingHints: {
intervalInSeconds: 60,
sizeInMBs: 1,
},
cloudWatchLoggingOptions: {
enabled: true,
logGroupName: s3logGroup.logGroupName,
logStreamName: s3logStream.logStreamName,
},
compressionFormat: "UNCOMPRESSED",
},
vpcConfiguration: {
roleArn: firehoseRole.roleArn,
securityGroupIds: [openSG.securityGroupId],
subnetIds: props.vpc.selectSubnets({
subnetGroupName: "Private",
}).subnetIds,
},
bufferingHints: {
intervalInSeconds: 60,
sizeInMBs: 1,
},
cloudWatchLoggingOptions: {
enabled: true,
logGroupName: logGroup.logGroupName,
logStreamName: logStream.logStreamName,
},
},
deliveryStreamType: "DirectPut",
deliveryStreamName: "aws-waf-logs-iac",
}
);
/////////////////////////////////////////////////////////////////////////////////////////
//Creating WAF ACL Logging
/////////////////////////////////////////////////////////////////////////////////////////
const cfnLoggingConfiguration = new wafv2.CfnLoggingConfiguration(
stack,
"AclLoggingConfiguration",
{
logDestinationConfigs: [wafDeliveryStream.attrArn],
resourceArn: demoWebACL.attrArn,
}
);
return demoWebACL;
}
view rawcreate-cloudfrontWAF.ts hosted with ❤ by GitHub
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment