🌐

CDKを用いたAWS WAF CAPTCHA設定

2025/03/03に公開

導入

背景・目的

  • AWS WAF CAPTCHAを設定してユーザにCAPTCHAチャレンジを要求することは、ボットによるアクセスをブロックするのに有用です。
  • 本記事では、CloudFrontで配信されるフォーム画面へのAWS WAF CAPTCHA設定方法について、CDKサンプルコードとともに解説します。

対象読者

  • 本記事は、AWS 認定ソリューションアーキテクト - アソシエイトレベルの知識を持つ読者を想定しており、AWSサービスの基本的な解説は省略します。

環境概要

本記事で構築する環境の全体像を説明します。

CAPTCHA設定

AWS資源構築

CDKを用いて、Route53・ACM・WAF・CloudFront・S3関連資源を構築します。
今回はフォーム画面へのアクセス時のみCAPTCHAチャレンジを要求したいため、searchString/form.html を指定します。


    // ------------ FrontEnd ---------------
    // ---- WAF
    const cloudfrontWebAcl = new wafv2.CfnWebACL(this, "CloudfrontWebAcl", {
      defaultAction: { allow: {} },
      scope: "CLOUDFRONT",
      visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        sampledRequestsEnabled: true,
        metricName: "CloudfrontWebAcl",
      },
      rules: [
        {
          name: "CaptchaRule",
          priority: 0,
          statement: {
            byteMatchStatement: {
              searchString: "/form.html",
              fieldToMatch: {
                uriPath: {}
              },
              textTransformations: [
                {
                  priority: 0,
                  type: "NONE"
                }
              ],
              positionalConstraint: "EXACTLY"
            }
          },
          action: {
            captcha: {}
          },
          visibilityConfig: {
            sampledRequestsEnabled: true,
            cloudWatchMetricsEnabled: true,
            metricName: "CaptchaRule"
          }
        }
      ],
    });

    // ---- Route53
    const myHostedZone = route53.HostedZone.fromHostedZoneAttributes(this, "HostedZone", {
      hostedZoneId: props.hostedZoneId,
      zoneName: props.zoneName
    })

    // ---- S3
    const bucket = new s3.Bucket(this, 'Bucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      enforceSSL: true
    });

    // ---- CloudFront
    // Create Certificate
    const frontCertificate = new acm.Certificate(this, 'FrontCertificate', {
      domainName: props.frontHostname,
      validation: acm.CertificateValidation.fromDns(myHostedZone),
    });
    // Create Distribution
    const myDist = new cloudfront.Distribution(this, 'MyDist', {
      defaultBehavior: {
        origin: origins.S3BucketOrigin.withOriginAccessControl(bucket)
      },
      domainNames: [props.frontHostname],
      certificate: frontCertificate,
      webAclId: cloudfrontWebAcl.attrArn
    });
    // Create A Record
    new route53.ARecord(this, "FrontARecord", {
      recordName: props.frontHostname,
      zone: myHostedZone,
      target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(myDist))
    })

画面資源構築

今回は検証用に、トップ画面とフォーム画面向けに簡易的なHTMLを作成します。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Sample Site</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        text-align: center;
        margin: 50px;
      }
      .container {
        max-width: 400px;
        margin: auto;
        padding: 20px;
        border: 1px solid #ccc;
        border-radius: 5px;
        box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
      }
      input,
      button {
        width: 100%;
        padding: 10px;
        margin: 10px 0;
      }
    </style>
  </head>
  <body>
    <div id="home" class="container">
      <h1>トップ画面</h1>
      <p>Sample Siteへようこそ。</p>
      <button onclick="location.href='form.html'">フォームへ進む</button>
    </div>
  </body>
</html>
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>フォームページ - Sample Site</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        text-align: center;
        margin: 50px;
      }
      .container {
        max-width: 400px;
        margin: auto;
        padding: 20px;
        border: 1px solid #ccc;
        border-radius: 5px;
        box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
      }
      form {
        display: flex;
        flex-direction: column;
        align-items: center;
      }
      input,
      button {
        width: 90%;
        padding: 10px;
        margin: 10px 0;
        box-sizing: border-box;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>フォーム入力</h1>
      <form action="#" method="post">
        <label for="name">名前:</label>
        <input type="text" id="name" name="name" required />

        <label for="email">メールアドレス:</label>
        <input type="email" id="email" name="email" required />

        <button type="submit">送信</button>
      </form>
      <button onclick="location.href='index.html'">トップへ戻る</button>
    </div>
  </body>
</html>

作成後、S3にHTMLをアップロードしておきましょう。

動作検証

準備が整ったので、AWS WAF CAPTCHAの動作検証をしていきます。

まず、トップ画面にアクセスしてみます。CAPTCHAチャレンジが要求されることなく、トップ画面に遷移します。

次に、フォーム画面にアクセスしてみると、CAPTCHAチャレンジが要求されます。

チャレンジに成功すると、無事フォーム画面に遷移しました。

なお、チャレンジに失敗すると「正しくありません。もう一度お試しください。」と表示され、新たなチャレンジが要求されます。

参考

https://aws.amazon.com/jp/about-aws/whats-new/2022/06/aws-waf-captcha-generally-available/
https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/waf-captcha-and-challenge.html

注意事項

  • 本記事は万全を期して作成していますが、お気づきの点がありましたら、ご連絡よろしくお願いします。
  • なお、本記事の内容を利用した結果及び影響について、筆者は一切の責任を負いませんので、予めご了承ください。
Accenture Japan (有志)

Discussion