この記事では、IaCツールであるAWS CDKを使って、AWS WAFを設定する方法を解説します。
AWS WAFは比較的設定項目が多いため、CDKでコード化することで設定全体の見通しが良くなります。
設定する内容は、次の記事で解説したWAFの誤検知対策を含めたものになっています。CDKのコードは、Amazon CloudFrontと併用する前提で記載していますが、少し変えることでALBやAPI Gatewayなどでも利用可能です。
本記事作成時点ではAWS WAFはCDKのL1コンストラクトであり、抽象度が低いのでやや独特な書き方になっています。本記事の最後にログ等の設定も含めてAWS WAFを設定するコードを記載していますので、書き方が分からない場合の参考にもなります。
CDKのコンストラクトの定義について詳しく知りたい方はAWSの公式ドキュメントをご覧ください。
WAF用のCDKのコード
WAFのルールから除外するIPセットを定義する
最初に、WAFのルールから除外するIPセットを定義します。これは、マネージドルールAWSManagedRulesCommonRuleSetが管理者がWordPressで投稿した際にブロックしてしまうのに対処するためです。
const ipv4Set = new wafv2.CfnIPSet(this, "IPv4Set", {
scope: "CLOUDFRONT",
ipAddressVersion: "IPV4",
addresses: [this.node.tryGetContext("ipv4Address")],
});
const ipv6Set = new wafv2.CfnIPSet(this, "IPv6Set", {
scope: "CLOUDFRONT",
ipAddressVersion: "IPV6",
addresses: [this.node.tryGetContext("ipv6Address")],
});
IPv4とIPv6の両方のIPセットを定義しています。 addresses
には、自社のIPアドレスなど許可したいIPアドレスを入力します。この例では、実際の値はcdk.context.json
に記載しており、this.node.tryGetContext()で呼び出しています。
こうすることでCDKのコード内にマジックナンバーが含まなくて良く、コードの再利用性も高くなります。
WAF ACL設定
次に、WAFの本体であるACL全般の設定をしていきます。
// WAF Acl
const webAcl = new wafv2.CfnWebACL(this, "WafACL", {
defaultAction: {
allow: {},
},
scope: "CLOUDFRONT",
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "WebACLMetric",
sampledRequestsEnabled: true,
},
rules: [ ...
defaultAction
をallow: {}
にすることでWAFルールにマッチしないリクエストは許可されます。WAFでは、攻撃と思わしきリクエストだけブロックするのが一般的なため、通常はこのような設定になります。
その他は、AWS WAFのメトリクスをCloudWatchに連携するための設定です。
マネージドルールの設定
最後に、AWS WAFで有効にするルールを決めていきます。ここはやや記述が煩雑です。
rules: [
{
name: "AWSManagedRulesCommonRuleSet",
priority: 0,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
name: "AWSManagedRulesCommonRuleSet",
vendorName: "AWS",
scopeDownStatement: {
andStatement: {
statements: [
{
notStatement: {
statement: {
ipSetReferenceStatement: {
arn: ipv4Set.attrArn,
},
},
},
},
{
notStatement: {
statement: {
ipSetReferenceStatement: {
arn: ipv6Set.attrArn,
},
},
},
},
],
},
},
},
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesCommonRuleSet",
sampledRequestsEnabled: true,
},
},
overrideAction
では、特定のルールを無効(WAFでブロックしない)にする設定を行うことができます。マネージドルールは複数のルールのグループになっていますが、特定のルールだけを無効にしたいことがあります。overrideAction
にルール名とそのアクションの組み合わせを記述することでそのような設定が可能です。
statement
では、有効にするマネージドルール名を指定しています。この例だと、AWSManagedRulesCommonRuleSet
です。
statement
の下にあるscopeDownStatement
では、WAFの対象となるリクエストを指定しています。この例では、andStatement
で2つのnotStatement
を指定しているため、「IPv4アドレスのリストにない(NOT)」かつ(AND)「IPv6アドレスのリストにない(NOT)」 = 自社のIPアドレスではないという意味になっています。
最後に、visibilityConfig
でCloudWatchとの連携の設定をしています。
コード全体
コード全体はこちらです。AWSManagedRulesWordPressRuleSet
など他のマネージドルールも同様にrules
配列に追加しています。
一番最後のコードでは、this.webAcl = webAcl
としています。こうすることでwebAcl
オブジェクトを他のスタックから利用できるようにしています。このwebAcl
の設定はCloudFront側の設定で関連付けされるため、CloudFrontを定義しているスタックに引き渡します。
import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";
import * as wafv2 from "aws-cdk-lib/aws-wafv2";
import * as s3 from "aws-cdk-lib/aws-s3";
export class WAFStack extends cdk.Stack {
webAcl: wafv2.CfnWebACL;
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);
// IP Sets
const ipv4Set = new wafv2.CfnIPSet(this, "IPv4Set", {
scope: "CLOUDFRONT",
ipAddressVersion: "IPV4",
addresses: [this.node.tryGetContext("ipv4Address")],
});
const ipv6Set = new wafv2.CfnIPSet(this, "IPv6Set", {
scope: "CLOUDFRONT",
ipAddressVersion: "IPV6",
addresses: [this.node.tryGetContext("ipv6Address")],
});
// WAF Acl
const webAcl = new wafv2.CfnWebACL(this, "WafACL", {
defaultAction: {
allow: {},
},
scope: "CLOUDFRONT",
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "WebACLMetric",
sampledRequestsEnabled: true,
},
rules: [
{
name: "AWSManagedRulesCommonRuleSet",
priority: 0,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
name: "AWSManagedRulesCommonRuleSet",
vendorName: "AWS",
scopeDownStatement: {
andStatement: {
statements: [
{
notStatement: {
statement: {
ipSetReferenceStatement: {
arn: ipv4Set.attrArn,
},
},
},
},
{
notStatement: {
statement: {
ipSetReferenceStatement: {
arn: ipv6Set.attrArn,
},
},
},
},
],
},
},
},
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesCommonRuleSet",
sampledRequestsEnabled: true,
},
},
{
name: "AWSManagedRulesWordPressRuleSet",
priority: 1,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
name: "AWSManagedRulesWordPressRuleSet",
vendorName: "AWS",
},
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesWordPressRuleSet",
sampledRequestsEnabled: true,
},
},
{
name: "AWSManagedRulesPHPRuleSet",
priority: 2,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
name: "AWSManagedRulesPHPRuleSet",
vendorName: "AWS",
},
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesPHPRuleSet",
sampledRequestsEnabled: true,
},
},
{
name: "AWSManagedRulesSQLiRuleSet",
priority: 3,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
name: "AWSManagedRulesSQLiRuleSet",
vendorName: "AWS",
},
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "AWS-AWSManagedRulesSQLiRuleSet",
sampledRequestsEnabled: true,
},
},
],
});
// S3 Bucket for logging
const bucket = new s3.Bucket(this, "s3-waf-logging", {
bucketName: "aws-waf-logs-cf-XXXXXXXXXXX",
});
new wafv2.CfnLoggingConfiguration(this, "WafLogging", {
logDestinationConfigs: [bucket.bucketArn],
resourceArn: webAcl.attrArn,
});
this.webAcl = webAcl;
}
}
まとめ
この記事では、AWS CDNを使用してTypescriptでAWS WAFの設定を行いました。AWS WAFは設定範囲が広いため、マネージメントコンソールで設定するよりも、CDKでコード化することで管理がしやすくなります。
WAFのコードはCDKのL2コンストラクトになっていることもあり、書き味が他のサービスと違うため、最初は戸惑うかもしれません。本記事で記載したコードが参考になれば幸いです。