こんにちは、広野です。
2025 年 6 月に Amazon Cognito マネージドログインに AWS WAF の Web ACL をアタッチすることができるようになりました。
何ら真新しいことではないのですが、気を付けた方がよい点があるので書いておきます。
公式ドキュメント
以下に公式ドキュメントがあります。
アタッチした AWS WAF Web ACL によるチェック対象は以下になります。
- Managed login and the classic hosted UI
- Public API operations
つまり、UI と API なんですが。さらっと書かれすぎているので、以降、別ブログ記事の図を引用して説明します。
どこがチェック対象なのか
図に示すと、以下の赤丸の 2 箇所がチェック対象になります。
もう少し概念レベルで Amazon Cognito ユーザープールを機能分割すると、以下のように表現できます。
Amazon Cognito のマネージドログイン UI と、API がチェック対象になるのですが、それぞれのソースが異なります。
ソース IP アドレス制限をかけるときの注意
このため、Amazon Cognito マネージドログインを使用する環境でソース IP アドレス制限をかけるときには注意事項があります。
ソースは複数箇所あると考えた方がよいです。上記のケースでは、ユーザーデバイスと Amazon EC2 インスタンスの IP アドレスを両方許可するようにしてあげないと、認証フローが通りません。
おまけ AWS CloudFormation テンプレート
AWS Cognito に AWS WAF をアタッチする AWS CloudFormation テンプレートを参考までに貼り付けます。
今回検証したのは特定のソース IP アドレス (サブネット) を許可する設定です。
アプリケーションクライアントの設定は省略しています。Amazon Cognito ユーザープールと、AWS WAF およびそのログ保存に絞っています。登場する IP アドレスは架空のものです。
AWSTemplateFormatVersion: 2010-09-09 Description: The CloudFormation template that creates a Cognito user pool with Managed Login. # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: SystemName: Type: String Description: System name. use lower case only. (e.g. example) Default: example MaxLength: 10 MinLength: 1 SubName: Type: String Description: System sub name. use lower case only. (e.g. prod or dev) Default: dev MaxLength: 10 MinLength: 1 SesId: Type: String Description: Amazon SES ID for sending emails. (email addreess or domain) Default: xxxx.xxx MaxLength: 100 MinLength: 5 SesConfigurationSet: Type: String Description: Amazon SES configuration set for sending emails. Default: xxxxxxxxxxxxxxxx MaxLength: 100 MinLength: 5 CognitoReplyTo: Type: String Description: Cognito Reply-to email address. (e.g. xxx@xxx.xxx) Default: xxxxx@xxx.xxx MaxLength: 100 MinLength: 5 CognitoEmailFrom: Type: String Description: Cognito e-mail from address. (e.g. xxx@xxx.xxx) Default: xxxxx@xxx.xxx MaxLength: 100 MinLength: 5 LogRetentionDays: Type: Number Description: The retention period (days) for AWS WAF logs. Enter an integer between 35 to 540. Default: 365 MaxValue: 540 MinValue: 35 SourceIpv4RangeList: Type: CommaDelimitedList Description: The comma delimited list of allowed source IPv4 address range (CIDR) except /0 to access this web site. (e.g. xxx.xxx.xxx.xxx/xx,yyy.yyy.yyy.yyy/yy) Default: "8.8.8.8/32,8.8.4.4/32" Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "General Configuration" Parameters: - SystemName - SubName - Label: default: "Email Configuration" Parameters: - SesId - SesConfigurationSet - CognitoReplyTo - CognitoEmailFrom - Label: default: "Security Configuration" Parameters: - LogRetentionDays - SourceIpv4RangeList Resources: # ------------------------------------------------------------# # Cognito # ------------------------------------------------------------# UserPool: Type: AWS::Cognito::UserPool Properties: UserPoolName: !Sub ${SystemName}-${SubName} MfaConfiguration: "OFF" Policies: PasswordPolicy: MinimumLength: 8 RequireUppercase: true RequireLowercase: true RequireNumbers: true RequireSymbols: false TemporaryPasswordValidityDays: 180 AccountRecoverySetting: RecoveryMechanisms: - Name: verified_email Priority: 1 AdminCreateUserConfig: AllowAdminCreateUserOnly: true AutoVerifiedAttributes: - email DeviceConfiguration: ChallengeRequiredOnNewDevice: false DeviceOnlyRememberedOnUserPrompt: false EmailConfiguration: ConfigurationSet: !Ref SesConfigurationSet EmailSendingAccount: DEVELOPER From: !Sub "${SystemName}-${SubName} admin <${CognitoEmailFrom}>" ReplyToEmailAddress: !Ref CognitoReplyTo SourceArn: !Sub arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/${SesId} EmailVerificationMessage: !Sub "${SystemName}-${SubName} Verification code: {####}" EmailVerificationSubject: !Sub "${SystemName}-${SubName} Verification code" UsernameConfiguration: CaseSensitive: false UserPoolAddOns: AdvancedSecurityMode: "OFF" UserPoolTags: Cost: !Sub ${SystemName}-${SubName} UserPoolTier: ESSENTIALS UserPoolDomain: Type: AWS::Cognito::UserPoolDomain Properties: Domain: !Sub ${SystemName}-${SubName} ManagedLoginVersion: 2 UserPoolId: !Ref UserPool # ------------------------------------------------------------# # WAF Web Acl for Cognito # ------------------------------------------------------------# WebAclCognito: Type: AWS::WAFv2::WebACL Properties: Name: !Sub ${SystemName}-${SubName}-cognito Description: WAFv2 WebACL for Cognito Scope: REGIONAL DefaultAction: Block: {} Rules: - Name: !Sub ${SystemName}-${SubName}-cognito-IpWhiteList Action: Allow: {} Priority: 0 Statement: IPSetReferenceStatement: Arn: !GetAtt WAFv2Ipv4WhiteList.Arn VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: !Sub ${SystemName}-${SubName}-cognito-IpWhiteList SampledRequestsEnabled: true VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: !Sub ${SystemName}-${SubName}-cognito SampledRequestsEnabled: true Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} DependsOn: - WAFv2Ipv4WhiteList WAFv2Ipv4WhiteList: Type: AWS::WAFv2::IPSet Properties: Name: !Sub ${SystemName}-${SubName}-Ipv4WhiteList Description: WAF v2 IPv4 white list for the IP-based restricted access IPAddressVersion: IPV4 Addresses: !Ref SourceIpv4RangeList Scope: REGIONAL Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} WebACLAssociationCognito: Type: AWS::WAFv2::WebACLAssociation Properties: ResourceArn: !GetAtt UserPool.Arn WebACLArn: !GetAtt WebAclCognito.Arn DependsOn: - UserPool - WebAclCognito WebAclLoggingConfigurationCognito: Type: AWS::WAFv2::LoggingConfiguration Properties: ResourceArn: !GetAtt WebAclCognito.Arn LogDestinationConfigs: - !GetAtt S3BucketWafLogs.Arn LoggingFilter: DefaultBehavior: KEEP Filters: - Behavior: KEEP Conditions: - ActionCondition: Action: BLOCK Requirement: MEETS_ANY DependsOn: - S3BucketWafLogs - WebAclCognito # ------------------------------------------------------------# # S3 # ------------------------------------------------------------# S3BucketWafLogs: Type: AWS::S3::Bucket Properties: BucketName: !Sub aws-waf-logs-${SystemName}-${SubName}-cognito LifecycleConfiguration: Rules: - Id: AutoDelete Status: Enabled ExpirationInDays: !Ref LogRetentionDays OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} # ------------------------------------------------------------# # Output Parameters # ------------------------------------------------------------# Outputs: # Cognito CognitoUserPoolId: Value: !Ref UserPool Export: Name: !Sub CognitoUserPoolId-${SystemName}-${SubName} CognitoUserPoolArn: Value: !GetAtt UserPool.Arn Export: Name: !Sub CognitoUserPoolArn-${SystemName}-${SubName} CognitoUserPoolProviderName: Value: !GetAtt UserPool.ProviderName Export: Name: !Sub CognitoUserPoolProviderName-${SystemName}-${SubName} CognitoUserPoolProviderUrl: Value: !GetAtt UserPool.ProviderURL Export: Name: !Sub CognitoUserPoolProviderUrl-${SystemName}-${SubName}
まとめ
いかがでしたでしょうか。
今回は小ネタでした。Amazon Cognito 周りの通信パスをご存知の方にとっては当たり前と思いましたが、意外と知られていないかもな?と思ったので書きました。
本記事が皆様のお役に立てれば幸いです。