🌐
CDKを用いたAWS WAF CAPTCHA設定
導入
背景・目的
- 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チャレンジが要求されます。
チャレンジに成功すると、無事フォーム画面に遷移しました。
なお、チャレンジに失敗すると「正しくありません。もう一度お試しください。」と表示され、新たなチャレンジが要求されます。
参考
注意事項
- 本記事は万全を期して作成していますが、お気づきの点がありましたら、ご連絡よろしくお願いします。
- なお、本記事の内容を利用した結果及び影響について、筆者は一切の責任を負いませんので、予めご了承ください。
Discussion