[AWS CDK] WAF v2のAWSマネージドルールをCloudFrontに適用する
こんにちは、CX事業本部の若槻です。
AWS WAFではAWS Managed Rules for AWS WAF(マネージドルール)という機能を使うことにより、独自のルールを記述することなく一般的なアプリケーションの脆弱性を保護することができます。
今回は、AWS CDKでWAF v2のAWSマネージドルールをCloudFrontに適用してみました。
やってみる
アプリの作成
AWS WAFで保護するアプリケーションとして、下記の記事を参考にAWS CDKでデプロイしたReactアプリを使用します。
注意点として、今回CloudFrontに紐付けるAWS WAF v2のWeb ACLは、対応リージョンであるus-east-1
で作成する必要があるため、必要に応じてbin/aws-cdk-deploy-react.ts
に下記のように記述してリージョンを指定した上でcdk bootstrap
を行います。
#!/usr/bin/env node import "source-map-support/register"; import * as cdk from "@aws-cdk/core"; import { AwsCdkDeployReactStack } from "../lib/aws-cdk-deploy-react-stack"; const app = new cdk.App(); new AwsCdkDeployReactStack(app, "AwsCdkDeployReactStack", { /* If you don't specify 'env', this stack will be environment-agnostic. * Account/Region-dependent features and context lookups will not work, * but a single synthesized template can be deployed anywhere. */ /* Uncomment the next line to specialize this stack for the AWS Account * and Region that are implied by the current CLI configuration. */ // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, /* Uncomment the next line if you know exactly what Account and Region you * want to deploy the stack to. */ // env: { account: '123456789012', region: 'us-east-1' }, env: { region: "us-east-1", }, /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ });
CloudFrontへのマネージドルールの適用
@aws-cdk/aws-wafv2
をnpmでインストールします。
% npm i -D @aws-cdk/aws-wafv2
CDKスタックを下記のように定義します。ハイライト部分がWAFに関する追記箇所です。
CLOUDFRONT
scopeのWeb ACLを作成する- Web ACLのrulesに使用したいマネージドルールを記載する
- マネージドルールでは
overrideAction: { none: {} }
を指定する - 作成したWebACLをCloudFrontディストリビューションに適用する
import * as cdk from "@aws-cdk/core"; import * as cloudfront from "@aws-cdk/aws-cloudfront"; import * as s3 from "@aws-cdk/aws-s3"; import * as s3deploy from "@aws-cdk/aws-s3-deployment"; import * as iam from "@aws-cdk/aws-iam"; import * as wafv2 from "@aws-cdk/aws-wafv2"; export class AwsCdkDeployReactStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const websiteBucket = new s3.Bucket(this, "WebsiteBucket", { websiteErrorDocument: "index.html", websiteIndexDocument: "index.html", }); const websiteIdentity = new cloudfront.OriginAccessIdentity( this, "WebsiteIdentity" ); const webSiteBucketPolicyStatement = new iam.PolicyStatement({ actions: ["s3:GetObject"], effect: iam.Effect.ALLOW, principals: [websiteIdentity.grantPrincipal], resources: [`${websiteBucket.bucketArn}/*`], }); websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement); //AWSマネージドルールを適用するWebACLの作成 const websiteWafV2WebAcl = new wafv2.CfnWebACL(this, "WafV2WebAcl", { defaultAction: { allow: {} }, scope: "CLOUDFRONT", visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: "websiteWafV2WebAcl", }, rules: [ { name: "AWSManagedRulesCommonRuleSet", priority: 1, statement: { managedRuleGroupStatement: { vendorName: "AWS", name: "AWSManagedRulesCommonRuleSet", }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: "AWSManagedRulesCommonRuleSet", }, }, { name: "AWSManagedRulesAdminProtectionRuleSet", priority: 2, statement: { managedRuleGroupStatement: { vendorName: "AWS", name: "AWSManagedRulesAdminProtectionRuleSet", }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: "AWSManagedRulesAdminProtectionRuleSet", }, }, { name: "AWSManagedRulesKnownBadInputsRuleSet", priority: 3, statement: { managedRuleGroupStatement: { vendorName: "AWS", name: "AWSManagedRulesKnownBadInputsRuleSet", }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: "AWSManagedRulesKnownBadInputsRuleSet", }, }, { name: "AWSManagedRulesAmazonIpReputationList", priority: 4, statement: { managedRuleGroupStatement: { vendorName: "AWS", name: "AWSManagedRulesAmazonIpReputationList", }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: "AWSManagedRulesAmazonIpReputationList", }, }, { name: "AWSManagedRulesAnonymousIpList", priority: 5, statement: { managedRuleGroupStatement: { vendorName: "AWS", name: "AWSManagedRulesAnonymousIpList", }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: "AWSManagedRulesAnonymousIpList", }, }, ], }); const websiteDistribution = new cloudfront.CloudFrontWebDistribution( this, "WebsiteDistribution", { errorConfigurations: [ { errorCachingMinTtl: 300, errorCode: 403, responseCode: 200, responsePagePath: "/index.html", }, { errorCachingMinTtl: 300, errorCode: 404, responseCode: 200, responsePagePath: "/index.html", }, ], originConfigs: [ { s3OriginSource: { s3BucketSource: websiteBucket, originAccessIdentity: websiteIdentity, }, behaviors: [ { isDefaultBehavior: true, }, ], }, ], priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL, webACLId: websiteWafV2WebAcl.attrArn, //作成したWebACLをCloudFrontに適用する } ); new s3deploy.BucketDeployment(this, "WebsiteDeploy", { sources: [s3deploy.Source.asset("./web/build")], destinationBucket: websiteBucket, distribution: websiteDistribution, distributionPaths: ["/*"], }); } }
今回はAWS提供の下記のマネージドルールを適用しています。
- ベースラインルールグループ
- AWSManagedRulesCommonRuleSet
- AWSManagedRulesAdminProtectionRuleSet
- AWSManagedRulesKnownBadInputsRuleSet
- IP 評価ルールグループ
- AWSManagedRulesAmazonIpReputationList
- AWSManagedRulesAnonymousIpList
マネージドルールの一覧と詳細は下記から確認可能です。要件に応じて必要なルールを選択してください。
CDKスタックの更新をデプロイします。
% cdk deploy
動作確認
デフォルトアクションで許可された場合
CloudFrontのURLからアプリにアクセスします。Reactアプリが表示されます。
AWS WAF v2の管理コンソールからWeb ACLで記録されたサンプルリクエストを確認してみます。
どのマネージドルールでもブロックされず、デフォルトアクションでアクセスが許可されていることが分かります。
マネージドルールで拒否された場合
CloudFrontのURL + /admin
にアクセスしてみます。攻撃者が管理ページへのアクセスを試みているという想定です。すると今度もReactアプリが表示されました。
しかしWeb ACLでのサンプルリクエストの記録を見てみると、マネージドルールグループAWSManagedRulesAdminProtectionRuleSet
のうちAdminProtection_URIPATH
ルールでブロックされていることが分かります。
Web ACLでブロックされているにも関わらずページが表示されたのは、今回使用したReactアプリは既定の設定でパスによらず同じコンテンツを返すようになっているためです。/admin
ページへのルーティングを定義している場合はブロックにより何も表示されない動作となります。
デフォルトアクションで拒否された場合
デフォルトアクションを拒否とした場合にブロックされた場合の動作も見てみます。
下記のようにWeb ACLリソースのdefaultAction
記述をblock
に変更します。
//AWSマネージドルールを適用するWebACLの作成 const websiteWafV2WebAcl = new wafv2.CfnWebACL(this, "WafV2WebAcl", { defaultAction: { block: {} }, scope: "CLOUDFRONT",
アプリにアクセスしてみると、CloudFrontからコンテンツの配信が行われず何も表示されません。
サンプルリクエストを見てみると、デフォルトアクションで拒否されたリクエストは、ActionはBLOCK
、Rule inside rule groupは-
と記録されるようです。
おわりに
AWS CDKでWAF v2のAWSマネージドルールをCloudFrontに適用してみました。
AWS CDK + WAF v2 + マネージドルールは、ネット上にもサンプルがなかなか出回っていないパターンだったので、少し苦労しましたが実装が出来てよかったです。
参考
- AWS::WAFv2::WebACL - AWS CloudFormation
- S3 + CloudFront + WAFv2 をCDKで構築した時にregion等でハマった話 - Qiita
- AWS Managed Rules for AWS WAF を構成するCloudFormationテンプレートを作ってみた | DevelopersIO
- AWS Managed Rules rule groups list - AWS WAF, AWS Firewall Manager, and AWS Shield Advanced
- 20200324 AWS Black Belt Online Seminar AWS WAFアップデート
以上