はじめに
CAPTCHA(Completely Automated Public Turing test to tell Computers and Humans Apart)は、アクセスしているユーザーが人間であることを判別するために使用される仕組みです。AWS WAFでは、このCAPTCHA機能をわずかな設定で導入でき、Webサイトを簡単に防御できます。今回はCDKでの作成方法を紹介します。
CAPTCHAとは?
CAPTCHAとは、人間とBot(自動アクセスプログラム)を区別する仕組みで、フォームスパムやBotによるアクセスを防止する目的で広く使われています。AWS WAFでは、CloudFrontやALBに適用する形でこの仕組みを導入でき、従来のようにJavaScriptライブラリやサーバーサイドロジックを用意する必要がありません。よく目にするものとして歪んだ文字を入力するものや写真の中からものを選択するものなどがあります。今回は以画像選択式 CAPTCHAを作成します。
AWS WAFでCAPTCHAを使うメリット
- 導入が簡単:コードの修正不要、コンソールやCDKなどからルール追加だけ
 - グローバルに使える:CloudFrontとの併用で世界中のトラフィックに対応
 - 高度なBot対策と併用可能:レート制限やIP制限と組み合わせて多層防御が可能
 - 再利用性:特定のURLや条件だけにCAPTCHAを適用可能
 
実装例
前提条件
URLに対してWAFを設定していくのでwebページを作成しておきます。
今回はCloud Front、S3を用いて以下のようなログイン画面とメニュー画面を用意しました。


ファイル構成
/ ├── bin/ │ └── cdk.ts ├── lib/ │ └── cdk-stack.ts └── cdk.json などの設定ファイル
CDKにWAFを追加
lib/cdk-stack.tsにAWS WAFを作成するコードを追加していきます。
    const captchaRule: wafv2.CfnWebACL.RuleProperty = {
      name: "CaptchaRule",
      priority: 0,
      action: { captcha: {} },
      visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        metricName: "CaptchaRule",
        sampledRequestsEnabled: true,
      },
      statement: {
        byteMatchStatement: {
          searchString: "/menu",
          fieldToMatch: { uriPath: {} },
          textTransformations: [
            {
              priority: 0,
              type: "NONE",
            },
          ],
          positionalConstraint: "EXACTLY",
        },
      },
    };
    const acl = new wafv2.CfnWebACL(this, "WebAcl", {
      name: 'YoshidaTestWebAcl',
      scope: "CLOUDFRONT",
      defaultAction: { allow: {} },
      visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        metricName: 'YoshidaTestWebAclMetric',
        sampledRequestsEnabled: true,
      },
      rules: [captchaRule],
    });
ポイント
- WAFのWebACL作成時は 
CfnWebACLを使う(L2 Construct は未対応) - CAPTCHAを特定のパスに限定するには 
byteMatchStatementでuriPathを使用 
今回は/menuに遷移する時にCAPTCHA認証をするようにしています。
DistributionにWAFを反映
CloudFrontとWAFを紐づけるには webAclId を Distribution に渡す必要があります。
DistributionのwebAclIdプロパティにWAFのARNを渡しています。
lib/cdk-stack.ts
    const distribution = new cloudfront.Distribution(this, 'CloudFrontDistribution', {
      webAclId: acl.attrArn, // ここに追加
      comment: "test distribution for waf capture",
      defaultRootObject: "index.html",
      errorResponses: [
        {
          ttl: Duration.seconds(300),
          httpStatus: 403,
          responseHttpStatus: 200,
          responsePagePath: "/index.html",
        },
        {
          ttl: Duration.seconds(300),
          httpStatus: 404,
          responseHttpStatus: 200,
          responsePagePath: "/index.html",
        },
      ],
      defaultBehavior: {
        allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
        cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD,
        cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
        viewerProtocolPolicy:
        cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        originRequestPolicy: cloudfront.OriginRequestPolicy.CORS_S3_ORIGIN,
        origin:
            cloudfront_origins.S3BucketOrigin.withOriginAccessControl(
                frontBucket
            ),
      },
    });
デプロイをし画面を確認

ログインボタンを押すと

このような画面になります。開始を押すと

このように画像が表示されました。今回はハットなのでハットを選択すると

成功という画面が表示された後、このようにメニューページに遷移することができます。
このようにAWS WAFを追加するだけで簡単にCAPTCHA認証を実装することができます。
コンソールからノーコードで設定することもできます。
注意点とハマりやすいポイント
- CloudFrontに適用するにはus-east-1リージョンで作成が必要
 - WAFのスコープが 
CLOUDFRONTの場合、リージョンはus-east-1限定 - /bin/cdk.tsに指定しています
 
new CdkStack(app, 'CdkStack', {
    env: { region: 'us-east-1' }
});
- SPA (Reactなど) でのURL遷移はWAFが検出しない場合がある
 - SPAで使用する場合の要注意点です!
 - クライアントサイドルーティングではCAPTCHAが発動しない
 - 対策:
window.location.hrefでフルリロードする - ステートレスな認証であることを理解しておく
 - CAPTCHAの通過はWAF Cookieにより一時的に記録される
 
費用感について
- CAPTCHA機能そのものはWAFの料金体系に含まれる
AWS WAF の料金 
おわりに
AWS WAFのCAPTCHA機能は、WebサイトへのBot対策を簡単に強化できる有力な手段です。CDKとの組み合わせにより、コードベースで柔軟に導入・管理することも可能です。