CDKでWAFv2を定義して、IPアドレス制限を実現する。
CDKを使ってWAFv2の特定のIPアドレスのみアクセスを許可する設定を作るのに、けっっこう時間かけたのでその備忘録としてこの記事を書きました。今回、アクセス制限するリソースはCloudFrontですが、IPアドレス制限する設定は他のリソースでも共通ですので参考にしてください。
前提
- typescript で定義しています。
-
aws configure
を実行していて、awsのユーザー情報をローカル内に存在している - aws cdk をインストール済み
-
cdk init
、cdk bootstrap
は実行済み
WAFとCloudFrontの作成
先に完成したコードをお見せします。この後に詳しい説明を加えていきます。
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { CdkProjectStack } from "../lib/project-stack";
const app = new cdk.App();
// WAF + Cloudfront関連のスタック
const cdkProjectStack = new CdkProjectStack(
app,
"GlobalAccessStack",
{
env: {
region: "us-east-1", // CloudFrontのWAFはus-east-1でしか作成できないため、リージョンを指定
},
}
);
import * as cdk from "aws-cdk-lib";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as wafv2 from "aws-cdk-lib/aws-wafv2";
export class CdkProjectStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// wafのIPアドレスのホワイトリスト
const ipWhiteList = ["XXX.XX.XX.XXX/32"];
// IP setsの定義
const iPSet = new wafv2.CfnIPSet(this, "SampleWhiteListIPSet", {
name: "sample-white-list-ipset",
addresses: ipWhiteList,
ipAddressVersion: "IPV4",
scope: "CLOUDFRONT",
});
// cloudfrontのアクセスをIPアドレス制限するwafの定義
const webACL = new wafv2.CfnWebACL(this, "SampleWebACL", {
name: "sample-web-acl",
defaultAction: {
block: {}, // デフォルトでブロックする
},
scope: "CLOUDFRONT",
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "sample-webacl-rule-metric",
sampledRequestsEnabled: true,
},
rules: [
{
priority: 0,
name: "sample-webacl-rule",
action: { allow: {} }, // IPアドレスがマッチした場合は許可する
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "sample-webacl-rule-metric",
},
statement: {
ipSetReferenceStatement: {
arn: iPSet.attrArn,
},
},
},
],
});
// cloudfrontの定義
const distribution = new cloudfront.Distribution(
this,
"SampleDistribution",
{
webAclId: webACL.attrArn, // webAclの設定を反映
defaultBehavior: {
origin: new origins.HttpOrigin(
cdk.Fn.parseDomainName("https://xxxx")
),
cachePolicy: new cloudfront.CachePolicy(
this,
"SampleDistributionCachePolicy",
{
headerBehavior:
cloudfront.CacheHeaderBehavior.allowList("Authorization"),
}
),
responseHeadersPolicy:
cloudfront.ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.HTTPS_ONLY,
},
}
);
}
}
CloudFront + WAF の定義
CloudFrontのアクセスを特定のIPアドレス制限をするのにWAFと連携します。
IP sets
IPアドレスを制限するにはWAFのIPsetsを作成する必要があります。下記はWAFのIPsetsを定義しています。
// wafのipアドレスのホワイトリスト
const ipWhiteList = ["XXX.XX.XX.XXX/32"];
// cloudfrontのアクセスをipアドレスを制限するwafの定義
const iPSet = new wafv2.CfnIPSet(this, "SampleWhiteListIPSet", {
name: "sample-white-list-ipset",
addresses: ipWhiteList,
ipAddressVersion: "IPV4",
scope: "CLOUDFRONT",
});
IPsetsはCfnIPSet
クラスで作成しています。
const ipWhiteList = ["XXX.XX.XX.XXX"];
で定義している通り、IPsetsのIPアドレスの設定は配列で定義します。
ipAddressVersion: "IPV4",
でIPアドレスのバージョンを指定しています。今回はIPV4で指定しています。
scope: "CLOUDFRONT",
で、cloudFrontディストリビューション用か地域アプリケーション用かを指定しています。今回はcloudFrontのIP制限なので"CLOUDFRONT"
を指定。
("CLOUDFRONT"
以外にたとえば、Cognito用のIPsetを作成する場合は"REGIONAL"
を指定する。)
WAFの定義
IPsetsが作成できたので、IPsetsを設定したWAFを定義します。
// cloudfrontのアクセスをIPアドレス制限するwafの定義
const webACL = new wafv2.CfnWebACL(this, "SampleWebACL", {
name: "sample-web-acl",
defaultAction: {
block: {}, // デフォルトでブロックする
},
scope: "CLOUDFRONT",
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "sample-webacl-rule-metric",
sampledRequestsEnabled: true,
},
rules: [
{
priority: 0,
name: "sample-webacl-rule",
action: { allow: {} }, // IPアドレスがマッチした場合は許可する
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "sample-webacl-rule-metric",
},
statement: {
ipSetReferenceStatement: {
arn: iPSet.attrArn,
},
},
},
],
});
WAFはCfnWebACL
クラスで作成しています。
重要なプロパティはdefaultAction
です。ここをblock
にすることでIPsetsで指定しているIPアドレスのみをアクセスできるようになります。
scope: "CLOUDFRONT",
はIPsetsの時と同様で、cloudFrontを利用するので"CLOUDFRONT"を指定。
visibilityConfig
は CloudWatch メトリクスやWeb リクエストのサンプル収集の定義をしています。今回は有効にしています。
IPsetsはrules:
プロパティで設定しています。各IPsetsの設定はオブジェクト単位で設定しており、優先度を指定することで、複数あるIPsetsの中でもどれを重視するかを設定できます。
priority: 0,
優先度を表したプロパティです。値が低いほど優先度は高くなります。
action: { allow: {} },
ここでIPsetsで指定しているIPアドレスを許可しています。逆にここでblock
にするとIPsetsで指定したIPアドレスを拒否します。
statement: {
ipSetReferenceStatement: {
arn: iPSet.attrArn,
},
},
↑ここでIPsetsの指定をしています。
cloudFrontの定義
WAFの定義ができたのであとはcloudFrontを定義するだけです。
// cloudfrontの定義
const distribution = new cloudfront.Distribution(
this,
"SampleDistribution",
{
webAclId: webACL.attrArn, // webAclの設定を反映
defaultBehavior: {
origin: new origins.HttpOrigin(
cdk.Fn.parseDomainName("https://xxxx")
),
cachePolicy: new cloudfront.CachePolicy(
this,
"SampleDistributionCachePolicy",
{
headerBehavior:
cloudfront.CacheHeaderBehavior.allowList("Authorization"),
}
),
responseHeadersPolicy:
cloudfront.ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.HTTPS_ONLY,
},
}
);
webAclId: webACL.attrArn,
で定義したWAFの設定と連携しています。これで、cloudFrontとWAFの連携はできたのであとはcloudFrontの設定になります。
defaultBehavior:
でデフォルトビヘイビアを設定しています。追加でビヘイビアを設定するときはadditionalBehaviors:
プロパティを使って定義します。
origin:
でアクセスするURLを指定します。
cachePolicy:
でキャッシュポリシーを作成、設定しています。今回は下記の通りに設定することで、Amplifyとかで設定するBASIC認証にも対応できるようにしています。
headerBehavior: cloudfront.CacheHeaderBehavior.allowList("Authorization"),
responseHeadersPolicy: cloudfront.ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS,
はレスポンスヘッダーポリシーの設定を指定います。今回はSimpleCORS
の設定に指定います。
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.HTTPS_ONLY,
はビューワープロトコルポリシーの設定を定義しています。
上記でcloudFront + WAFの定義ができました。あとはcdk deploy
することでAWS上に作成することが出来ます。
しかし、落とし穴が一つあります。
cloudFrontにWAFをアタッチする場合、現在(2023/08/31)だとリージョンがus-east-1
でしか作成できないため、リージョンを指定する必要があります。(デフォルトでus-east-1
の場合は不要)
そのため、lib
配下(cdk init初期時)のstackを管理しているファイルでcloudFront + WAFを定義しているstackには下記のようにリージョンを指定する必要があります。
// WAF + Cloudfront関連のスタック
const cdkProjectStack = new CdkProjectStack(
app,
"GlobalAccessStack",
{
env: {
region: "us-east-1", // CloudFrontのWAFはus-east-1でしか作成できないため、リージョンを指定
},
}
);
こちらで作成可能になります。今回紹介したコードでは直接デフォルトアクションを入力していますが、cdk.json
ファイルを活用して環境ごとの設定もすることができます。
注意点
本番環境をリリースするのに設定しているIPsetsをデタッチするのにたとえば、コメントアウトなどでruleから削除します。その状態でcdk deploy
を実行すると手動で設定したIPsetsなどの設定もデタッチされます。リリース前にヒヤヒヤする羽目になるので注意してください。
Discussion