./knowledge-base/cdk/lib/waf-stack.ts

import * as cdk from "aws-cdk-lib";
import * as wafv2 from "aws-cdk-lib/aws-wafv2";
import { Construct } from "constructs";
import { ALLOWED_IPS } from "./allowed-ips";

/**
 * WAF WebACL スタック(CloudFront用はus-east-1に作成必須)
 * CloudFrontディストリビューションへのIPアドレス制限を提供する
 */
export class WafStack extends cdk.Stack {
  public readonly webAclArn: string;

  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 許可IPアドレスセット(NHK社内ネットワーク+固定IP+リモートアクセスIP)
    // IPリストは allowed-ips.ts で一元管理
    const ipSet = new wafv2.CfnIPSet(this, "AllowedIPSet", {
      name: "historical-research-allowed-ips",
      scope: "CLOUDFRONT",
      ipAddressVersion: "IPV4",
      addresses: ALLOWED_IPS,
    });

    // CloudFront用 WAF WebACL(デフォルト: ブロック、許可IPのみ通過)
    const webAcl = new wafv2.CfnWebACL(this, "CloudFrontWebACL", {
      name: "historical-research-cloudfront-waf",
      scope: "CLOUDFRONT",
      defaultAction: { block: {} },
      visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        metricName: "historical-research-cloudfront-waf",
        sampledRequestsEnabled: true,
      },
      rules: [
        {
          name: "AllowSpecificIPs",
          priority: 1,
          action: { allow: {} },
          statement: {
            ipSetReferenceStatement: {
              arn: ipSet.attrArn,
            },
          },
          visibilityConfig: {
            cloudWatchMetricsEnabled: true,
            metricName: "AllowSpecificIPs",
            sampledRequestsEnabled: true,
          },
        },
      ],
    });

    this.webAclArn = webAcl.attrArn;

    new cdk.CfnOutput(this, "WebAclArn", {
      value: webAcl.attrArn,
      exportName: "HistoricalResearchWebAclArn",
      description: "WAF WebACL ARN for CloudFront IP restriction",
    });
  }
}