AWS WAF + API Gateway構成をカウントモードで構築し、ルールに該当するリクエストのログを取得してみた

2024.02.06

はじめに

WAFのルールを検討するにあたってはまず、ルールの該当件数や誤検知の有無などの検証が必要となる事があるかと思います。
AWS WAFではこのような場面のためにリクエストがルールに合致した場合のアクションをブロックからカウントに変更できます。その後カウントしたリクエストをログとして出力し検証に役立てることも可能です。

今回、API Gatewayに割り当てたAWS WAFをカウントモードで構築してルールに該当したリクエストのログをCloudWatch Logsで取得してみましたので、内容をご紹介します。構築にはCDKを使用しています。ルに該当するリクエストを送ってみます。

※ まずAWS WAFがどんなサービスか基本から知りたい方は以下ブログを参照してください。
AWS WAFを完全に理解する ~WAFの基礎からv2の変更点まで~

構成図

今回の検証で構築したリソースの構成は以下の通りです。

カウント対象のルール

IP setを用いて特定のIPアドレスからのリクエストをカウントするルールを設定しました。

CDKのコード

lib/constructs/api.ts

import {
  aws_lambda as lambda,
  aws_lambda_nodejs as lambdaNodeJs,
  aws_apigateway as apigateway,
  aws_wafv2 as wafv2,
  aws_logs as logs,
  CfnResource,
  StackProps,
  RemovalPolicy,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class ApiConstruct extends Construct {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id);

    const restApiFunc = new lambdaNodeJs.NodejsFunction(this, 'RestApiFunc', {
      runtime: lambda.Runtime.NODEJS_20_X,
      architecture: lambda.Architecture.ARM_64,
      entry:
        'src/lambda/handlers/rest-api-router.ts',
      tracing: lambda.Tracing.ACTIVE,
    });

    const restApi = new apigateway.LambdaRestApi(this, 'RestApi', {
      handler: restApiFunc,
      deployOptions: {
        stageName: 'v1',
        tracingEnabled: true,
      },
    });

    // IP setを作成
    const appDataIpV4Set = new wafv2.CfnIPSet(this, 'AppDataIpV4Set', {
      name: 'TestIpV4Set',
      ipAddressVersion: 'IPV4',
      scope: 'REGIONAL', // API Gatewayとの連携のためにREGIONALを指定
      addresses: ['<カウント対象のIPアドレス>'],
    });

    // WAFのルールを設定
    const webAcl = new wafv2.CfnWebACL(this, 'AppDataWebAcl', {
      defaultAction: { allow: {} },
      name: 'waf-api-gateway',
      scope: 'REGIONAL', // API Gatewayとの連携のためにREGIONALを指定
      visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        sampledRequestsEnabled: true,
        metricName: 'MetricsWebAcl',
      },
      rules: [
        {
          name: 'AppDataIpV4SetRule',
          priority: 1,
          statement: {
            ipSetReferenceStatement: {
              arn: appDataIpV4Set.attrArn,
            },
          },
          action: { count: {} },// カウントモードに設定
          visibilityConfig: {
            sampledRequestsEnabled: true,
            cloudWatchMetricsEnabled: true,
            metricName: 'AppDataIpV4SetRule',
          },
        },
      ],
    });

    // WAFをAPI Gatewayに関連付け
    const webAclAssociation = new wafv2.CfnWebACLAssociation(
      this,
      'webAclAssociation',
      {
        resourceArn: restApi.deploymentStage.stageArn,
        webAclArn: webAcl.attrArn,
      }
    );

    // WAFとAPI Gatewayのリソースの依存関係を設定
    webAclAssociation.addDependency(webAcl);
    webAclAssociation.addDependency(restApi.node.defaultChild as CfnResource);
  }
}

WAF関連の説明

WAFのIP setを作成

カウント対象のIPアドレスを含むIP setを作成します。
addressesプロパティにIPアドレスを指定します。
scopeにはAPI Gatewayとの連携のためにREGIONALを指定します。

const appDataIpV4Set = new wafv2.CfnIPSet(this, 'AppDataIpV4Set', {
  name: 'TestIpV4Set',
  ipAddressVersion: 'IPV4',
  scope: 'REGIONAL',
  addresses: ['<カウント対象のIPアドレス>'],
});

WAFのルールを設定

カウントモードでルールを設定します。
scopeにはAPI Gatewayとの連携のためにREGIONALを指定します。
actionプロパティにcountを指定します。
statementプロパティには、カウント対象のIP setを指定します。

const webAcl = new wafv2.CfnWebACL(this, 'AppDataWebAcl', {
  defaultAction: { allow: {} },
  name: 'waf-api-gateway',
  scope: 'REGIONAL',
  visibilityConfig: {
    cloudWatchMetricsEnabled: true,
    sampledRequestsEnabled: true,
    metricName: 'MetricsWebAcl',
  },
  rules: [
    {
      name: 'AppDataIpV4SetRule',
      priority: 1,
      statement: {
        ipSetReferenceStatement: {
          arn: appDataIpV4Set.attrArn,
        },
      },
      action: { count: {} },
      visibilityConfig: {
        sampledRequestsEnabled: true,
        cloudWatchMetricsEnabled: true,
        metricName: 'AppDataIpV4SetRule',
      },
    },
  ],
});

WAFをAPI Gatewayに関連付け

API GatewayとWAFを関連付けます。
resourceArnプロパティにはAPI GatewayのデプロイメントステージのARNを指定します。
webAclArnプロパティにはWAFのARNを指定します。

const webAclAssociation = new wafv2.CfnWebACLAssociation(
  this,
  'webAclAssociation',
  {
    resourceArn: restApi.deploymentStage.stageArn,
    webAclArn: webAcl.attrArn,
  }
);

デプロイ結果確認

デプロイ後、ルールに指定したIPアドレスがカウントモードで設定されたことを確認します。

リクエストを送ってみる

カウントモードで設定したルールに該当するIPアドレスからリクエストを送ってみます。

$ curl https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/hello | jq

{
  "message": "Hello from Lambda!"
}

カウントモードですので、リクエストは許可され正常なレスポンスが帰ってきます。

CloudWatch Logsへのログ出力の設定

WAFのルールに該当するリクエストのログをCloudWatch Logsに出力する設定を行います。
AWSのコンソールから作成したWAFを選択、Logging & metricsタブを選択し、EnableボタンををしてLoggingを有効にします。

ロギングの設定画面に移行しますので、設定を行っていきます。
今回はCloudWatch Logsにログを出力するため、Log destinationをCloudWatch Logsに設定します。
この画面からロググループの作成ができますので、未作成の場合はCreate newボタンを選択し、ロググループの作成画面に遷移します。
注意点として、ロググループ名には先頭にaws-waf-logs-を付与する必要があります。
ロググループを作成したら、ロギングの設定画面に戻りその他の設定を行います。

Redacted fieldsでは省略したい、内容を選択できます。今回はログ内容の全てを確認したいため、選択しません。

フィルターの作成

フィルターを作成することで、どのリクエストをログ出力するか制御できます。
設定しない場合全てのリクエストがログに出力されます。
今回必要なのはルールに該当してカウントされたリクエストのみなのでフィルターを作成します。

Filter requirement

リクエストが次に設定するFilter conditionsに一つでも合致した場合か、全てに合致した場合にFilterを適用するかの条件を選択します。
今回、Conditionは一つだけなので、Match all of the filter conditionsを選択します。

Filter conditions

Filter conditionsには、リクエストの内容に合わせて条件を設定します。
該当したルールのアクションがcountの場合としたいので、以下のように設定します。

  • Condition type
    • Rule action on request
  • Condition value
    • Count

Filter behavior

フィルターの条件にマッチしたリクエストのログを出力するか、出力しないかを設定します。
今回はマッチしたリクエストのログを出力するため、Keep in logs を選択します。

Default logging behavior

フィルターの条件にマッチしなかったリクエストのログを出力するか、出力しないかを設定します。
今回はマッチしなかったリクエストのログは出力しないので、Drop from logs を選択します。
以上の設定を行い、Saveボタンを選択します。

ログの確認

ログの設定を完了後にルールに指定したIPアドレスから何度かリクエストを送り、CloudWatch Logsにログが出力されているか確認します。
無事にログが出力されています。
ログには様々な情報が出力されており、該当したルールやリクエストの内容を確認できます。

ログ一部抜粋

以下の項目で該当したルールの情報が出力されています。

"nonTerminatingMatchingRules": [
    {
        "ruleId": "AppDataIpV4SetRule",
        "action": "COUNT",
        "ruleMatchDetails": []
    }
],

以下の項目でリクエストの情報が出力されています。

"httpRequest": {
  "clientIp": "<対象のIPアドレス>",
  "country": "JP",
  "headers": [
      {・・・・

さいごに

今回はWAFのルールをカウントモードで作成、該当するリクエストログをCloudWatch Logsに出力する設定を行いました。
WAFのルール検討としてルールに該当した場合のアクションをカウントモードにしてロギング設定を行うことで、CloudWatch Logsで該当したリクエストの内容を確認できます。
この他にも必要に応じてログをS3に出力する設定も可能です。
今回の内容がWAFのルール設定の参考になれば幸いです。