@stacksjs/cloud
Version:
The Stacks cloud/serverless integration & implementation.
149 lines (131 loc) • 4.28 kB
TypeScript
import type { Construct } from 'constructs';
import type { NestedCloudProps } from '../types';
export declare interface StorageStackProps extends NestedCloudProps {
zone: route53.IHostedZone
}
export declare class SecurityStack {
firewall: wafv2.CfnWebACL
kmsKey: kms.Key
certificate: acm.Certificate
originAccessIdentity: cloudfront.OriginAccessIdentity
constructor(scope: Construct, props: StorageStackProps) {
const firewallOptions = config.cloud.firewall
if (!firewallOptions)
throw new Error('No firewall options found in config')
const options = {
defaultAction: { allow: {} },
scope: 'CLOUDFRONT',
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'firewallMetric',
},
rules: this.getFirewallRules(scope),
}
this.firewall = new wafv2.CfnWebACL(scope, 'StacksWebFirewall', options)
Tags.of(this.firewall).add('Name', 'waf-cloudfront', { priority: 300 })
Tags.of(this.firewall).add('Purpose', 'CloudFront', { priority: 300 })
Tags.of(this.firewall).add('CreatedBy', 'CloudFormation', {
priority: 300,
})
this.kmsKey = new kms.Key(scope, 'EncryptionKey', {
alias: 'stacks-encryption-key',
description: 'KMS key for Stacks Cloud',
enableKeyRotation: true,
removalPolicy: RemovalPolicy.DESTROY,
pendingWindow: Duration.days(30),
})
this.certificate = new acm.Certificate(scope, 'Certificate', {
domainName: props.domain,
validation: acm.CertificateValidation.fromDns(props.zone),
subjectAlternativeNames: [`www.${props.domain}`, `api.${props.domain}`, `docs.${props.domain}`],
})
this.originAccessIdentity = new cloudfront.OriginAccessIdentity(scope, 'OAI')
}
getFirewallRules(scope: Construct): wafv2.CfnWebACL.RuleProperty[] {
const rules: wafv2.CfnWebACL.RuleProperty[] = []
const priorities = []
if (config.security.firewall?.countryCodes?.length) {
priorities.push(1)
rules.push({
name: 'CountryRule',
priority: priorities.length,
statement: {
geoMatchStatement: {
countryCodes: config.security.firewall.countryCodes as string[],
},
},
action: {
block: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'CountryRule',
},
})
}
if (config.security.firewall?.ipAddresses?.length) {
const ipSet = new wafv2.CfnIPSet(scope, 'IpSet', {
name: 'IpSet',
description: 'IP Set',
scope: 'CLOUDFRONT',
addresses: config.security.firewall.ipAddresses as string[],
ipAddressVersion: 'IPV4',
})
priorities.push(1)
rules.push({
name: 'IpAddressRule',
priority: priorities.length,
statement: {
ipSetReferenceStatement: {
arn: ipSet.attrArn,
},
},
action: {
block: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'IpAddressRule',
},
})
}
if (config.security.firewall?.httpHeaders?.length) {
config.security.firewall.httpHeaders.forEach((header: string | undefined, index: number) => {
priorities.push(1)
rules.push({
name: `HttpHeaderRule${index}`,
priority: priorities.length,
statement: {
byteMatchStatement: {
fieldToMatch: {
singleHeader: {
name: header,
},
},
positionalConstraint: 'EXACTLY',
searchString: 'true',
textTransformations: [
{
priority: 0,
type: 'NONE',
},
],
},
},
action: {
block: {},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: `HttpHeaderRule${index}`,
},
})
})
}
return rules
}
}