CloudFrontにWAFを当てる場合、WAFv2ではWAFをus-east-1リージョンに作成しないといけない。
CloudFrontが他のリージョンのリソースと一緒に定義されている場合、WAF適応に指定するARNをパラメータストア経由で渡したくなるが、そのままでは別リージョンのパラメータストアにはアクセスできない。
cdk-remote-stack
というモジュールで対応できるみたいだが、Lambda@Edgeを噛ませた場合にうまくいかなかったので以下を参考にCDKv2でクロスリージョンでのSSMパラメータストア読み書きを行うクラスを用意した。
$ cdk --version
2.1.0 (build f4f18b1)
lib/xorigin-ssm-parameter-accessor.ts
import { Construct } from 'constructs';
import { AwsCustomResource, AwsCustomResourcePolicy, AwsSdkCall, PhysicalResourceId } from 'aws-cdk-lib/custom-resources';
interface SSMParameterReaderProps {
action: "getParameter" | "putParameter" | "deleteParameter";
parameters: {
Name: string;
Value?: string;
Description?: string;
Type?: string;
Overwrite?: boolean;
}
region: string;
}
export class XOriginSSMParameterAccessor extends AwsCustomResource {
constructor(scope: Construct, name: string, props: SSMParameterReaderProps) {
const {action, parameters, region} = props;
const ssmAwsSdkCall: AwsSdkCall = {
service: "SSM",
action,
parameters,
region,
physicalResourceId: PhysicalResourceId.of(Date.now().toString()),
};
super(scope, name, {
onUpdate: ssmAwsSdkCall,
policy: AwsCustomResourcePolicy.fromSdkCalls({
resources: AwsCustomResourcePolicy.ANY_RESOURCE
})
});
}
public getParameterValue(): string {
return this.getResponseField("Parameter.Value").toString();
}
}
onUpdate
(リソースの更新時)で指定のAWS-SDKを実行する。ここでは他のリージョンのパラメータストアから読み出す処理。Lambdaが実行環境となるのでそのポリシーも指定するみたい。
lib/waf-stack.ts
import { Construct } from 'constructs';
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
import * as ssm from 'aws-cdk-lib/aws-ssm';
export class WafStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// == const ==
const wafName = "YOUR_WAF_NAME";
// == WAF ==
const myIpSet = new wafv2.CfnIPSet(this, "WhiteIPList", {
name: "WhiteIPList",
addresses: ["***.***.***.***/32"],
ipAddressVersion: "IPV4",
scope: "CLOUDFRONT",
});
const myWaf = new wafv2.CfnWebACL(this, wafName, {
defaultAction: { block: {} },
name: wafName,
rules: [
// aws managed rule
{
priority: 1,
overrideAction: { none: {} },
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: `${wafName}-AWSManagedRulesKnownBadInputsRuleSet`,
},
name: `${wafName}-AWSManagedRulesKnownBadInputsRuleSet`,
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesKnownBadInputsRuleSet"
}
}
},
// custom rule
{
priority: 2,
action: { allow: {} },
name: `${wafName}-CustomAllowIpSetRule`,
statement: {
ipSetReferenceStatement: {
arn: myIpSet.attrArn
},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: `${wafName}-CustomAllowIpSetRule`,
},
},
],
scope: "CLOUDFRONT",
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: `${wafName}`,
sampledRequestsEnabled: true
}
});
// == export ==
new ssm.StringParameter(this, 'SSMParamWafArn', {
parameterName: "/cdk-params/wafArn",
stringValue: myWaf.attrArn,
});
}
}
lib/cloudfront-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import { XOriginSSMParameterAccessor } from './xorigin-ssm-parameter-accessor';
export class CloudFrontStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// == import ==
const wafAttrArnReader = new XOriginSSMParameterAccessor(this, `SSMParameterReader`, {
action: "getParameter",
parameters: {
Name: "/cdk-params/wafArn",
},
region: 'us-east-1',
});
// == CloudFront ==
new cloudfront.Distribution(this,"Distribution", {
webAclId: wafAttrArnReader.getParameterValue(),
// 他略
});
}
}
bin/main.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CloudFrontStack } from '../lib/cloudfront-stack';
import { WafStack } from '../lib/waf-stack';
const app = new cdk.App();
new WafStack(app, 'WafStack', {
env: {
account: "************",
region: "us-east-1",
}
})
new CloudFrontStack(app, 'CloudFrontStack', {
env: {
account: "************",
region: "ap-northeast-1",
}
});