AWS CDK で Amazon Cognito user pools に AWS WAF Web ACL を関連付けてみた

2024.02.29

こんにちは、CX 事業本部製造ビジネステクノロジー部の若槻です。

2 年ほど前ですが、Amazon Cognito user pools の AWS WAF Web ACL による保護がサポートされました。

これにより、Cognito の次の認証エンドポイントが保護されるようになります。

  • サインアップ、サインインおよびサインアップに関連するエンドポイント
    • フェデレーションエンドポイント
    • Hosted UI エンドポイント
  • 公開 API オペレーションのエンドポイント
    • InitiateAuth
    • RespondToAuthChallenge
    • GetUser

今回は、Amazon Cognito user pools に AWS WAF Web ACL を関連付ける構成を AWS CDK で作成してみました。

やってみた

CDK コード

AWS CDK のコード(TypeScript)は以下の通りです。aws_wafv2 は現時点で L1 Construct のみの提供となっています。CfnWebACLAssociation メソッドにより Web ACL と User Pool の関連付けを行います。

lib/cdk-sample-stack.ts

import {
  aws_wafv2,
  aws_cognito,
  Stack,
  CfnOutput,
  RemovalPolicy,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

const MY_IP_V6_ADDRESS =
  process.env.MY_IP_V6_ADDRESS || '2001:db8:a0b:12f0::1/128';
const USER_POOL_DOMAIN = '20240229-cm-wakatsuki';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // WAFv2 の IPSet(IPv6)の作成
    const ipV6Set = new aws_wafv2.CfnIPSet(this, 'IPV6Set', {
      ipAddressVersion: 'IPV6',
      scope: 'REGIONAL',
      addresses: [MY_IP_V6_ADDRESS],
    });

    // WAFv2 の WebACL の作成
    const webAclForUserPool = new aws_wafv2.CfnWebACL(
      this,
      'WebAclForUserPool',
      {
        defaultAction: { block: {} },
        scope: 'REGIONAL',
        visibilityConfig: {
          cloudWatchMetricsEnabled: true,
          sampledRequestsEnabled: true,
          metricName: 'WebAclForUserPool',
        },
        rules: [
          {
            name: 'IpV6SetRule',
            priority: 0,
            statement: {
              ipSetReferenceStatement: {
                arn: ipV6Set.attrArn,
              },
            },
            action: { allow: {} },
            visibilityConfig: {
              sampledRequestsEnabled: false,
              cloudWatchMetricsEnabled: false,
              metricName: 'IpV6SetRule',
            },
          },
        ],
      }
    );

    // Cognito User Pool の作成
    const userPool = new aws_cognito.UserPool(this, 'UserPool', {
      selfSignUpEnabled: true,
      signInAliases: { email: true },
      removalPolicy: RemovalPolicy.DESTROY,
    });

    // Web ACL と User Pool の関連付け
    new aws_wafv2.CfnWebACLAssociation(this, 'WebAclUserPoolAssociation', {
      resourceArn: userPool.userPoolArn,
      webAclArn: webAclForUserPool.attrArn,
    });

    // Cognito User Pool Client の作成
    const userPoolClient = userPool.addClient('UserPoolClient', {
      generateSecret: false,
      oAuth: {
        callbackUrls: ['https://dev.classmethod.jp/'],
        logoutUrls: ['https://classmethod.jp/'],
        flows: { authorizationCodeGrant: true },
        scopes: [
          aws_cognito.OAuthScope.EMAIL,
          aws_cognito.OAuthScope.PROFILE,
          aws_cognito.OAuthScope.OPENID,
        ],
      },
    });

    // Cognito User Pool Domain の追加
    const a = userPool.addDomain('UserPoolDomain', {
      cognitoDomain: { domainPrefix: USER_POOL_DOMAIN },
    });

    // Cognito User Pool の ID を出力
    new CfnOutput(this, 'UserPoolId', {
      value: userPool.userPoolId,
    });

    // Cognito User Pool Client の ID を出力
    new CfnOutput(this, 'UserPoolClientId', {
      value: userPoolClient.userPoolClientId,
    });
  }
}

Web ACL はデフォルトでブロックとし、IPv6 の IPSet を使って指定の IP アドレスからのアクセスを許可するようにルールを設定してします。

上記を CDK デプロイしてリソースを作成します。この時、MY_IP_V6_ADDRESS は未指定なので私のネットワーク環境のものではないデフォルト値のアドレスからのアクセスのみ許可されます。

cdk deploy --require-approval never --method=direct

ユーザー作成

User Pool にユーザーを作成します。

aws cognito-idp admin-create-user \
  --user-pool-id ${userPoolId} \
  --username ${mailAddress}

Hosted UI の URI 取得

User Pool Domain と Client ID を使って Hosted UI の URI を取得します。

echo "https://${userPoolDomain}.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize?client_id=${userPoolClientId}&response_type=code&scope=email+openid+profile&redirect_uri=https://dev.classmethod.jp/"

アクセス確認(ブロック)

まず、IP Set で未許可の IP アドレスからアクセスしてみると、403 Forbidden が返されてブロックされました。

マネジメントコンソールから Web ACL の Sampled request を確認すると、私のネットワーク環境の IpV6 アドレスからのリクエストがブロックされていることが分かります。

アクセス確認(許可)

確認した IPv6 アドレスを指定して、再度デプロイを行います。

export MY_IP_V6_ADDRESS=2a09:XXXX:XXXX:XXXX:XXXX:XXXX/128

cdk deploy --require-approval never --method=direct

再デプロイ完了後に改めて Hosted UI にアクセスしてみると、正常にアクセスできました。リクエストが許可されたようです。

Hosted UI でのサインインが成功すると、コールバックアドレスへのリダイレクトも正常に行われました。

マネージドルールを設定するなら

今回は IPSet を使用したルールによるアクセス制限を AWS WAF で実装しましたが、必要に応じてマネージドルールも設定しましょう。

推奨

  • AWSManagedRulesCommonRuleSet(コアルールセット (CRS) マネージドルールグループ)
  • AWSManagedRulesKnownBadInputsRuleSet(既知の不正な入力マネージドルールグループ)
  • AWSManagedRulesAmazonIpReputationList(Amazon IP 評価リストマネージドルールグループ)

匿名アクセス対策を強化したい場合

  • AWSManagedRulesAnonymousIpList(匿名 IP リストマネージドルールグループ)

Lambda トリガーで RDB にアクセスする場合

  • AWSManagedRulesSQLiRuleSet(SQL データベースマネージドルールグループ)

ボット対策を強化したい場合

  • AWSManagedRulesBotControlRuleSet(Bot Control ルールグループ)

Cognito では使えないマネージドルールも一部ある

逆に、下記のマネージドルールは Cognito では使用できません。

  • AWSManagedRulesACFPRuleSet(AWS WAF Fraud Control Account Creation Fraud Prevention (ACFP) ルールグループ)
  • AWSManagedRulesATPRuleSet(アカウント乗っ取り防止 (ATP) のルールグループ)

REST API や ALB 、CloudFront などのリソースに対して使用するものとなります。

おわりに

AWS CDK で Amazon Cognito user pools に AWS WAF Web ACL を関連付けてみました。

認証機能の保護を強化することは、不正アクセスや認証機能自体の可用性を維持するうえで非常に重要です。忘れずに設定しておきましょう。

以上