AWS WAFだけで、Basic認証を設定してみた

2021.12.10

AWS事業本部の梶原@福岡オフィスです。 Basic認証のコードをよく見てたら、もしかして401のレスポンスが返せれば、WAFだけでいけんじゃね?って思ったので
やってみました。

AWS WAF Rule 作成

AWS コンソールで作成する場合は、以下のRuleをコンソールより作成します。 Rule部のみのご案内ですが、適時適切なRoleGroup, WAF ACL等に紐づけて頂ければと思います。 CloudFormationでサクッと作成されたい方は飛ばしてください

AWS WAF 条件部

項目 備考
If a request doesn't match the statement (NOT)

Statement

項目 備考
Inspect Header
Header field name authorization
Match type Exactly matches string
String to match Basic XXXXXXXXXX echo -n user:password | base64

※ String to match で設定する Basic XXXXXXXXXX の部分は事前にLinuxのshell等で

echo -n user:password | base64

を実施し、User:Password を Base64エンコードした値を設定してください

AWS WAF レスポンス部分

Action

項目 備考
Action Block

Custom response

項目 備考
Enable
Response code 401

Response headers

項目 備考
Key www-authenticate
Value Basic

JSON

{
  "Name": "BasicAuthRule",
  "Priority": 0,
  "Statement": {
    "NotStatement": {
      "Statement": {
        "ByteMatchStatement": {
          "SearchString": "Basic XXXXXXXXXX",
          "FieldToMatch": {
            "SingleHeader": {
              "Name": "authorization"
            }
          },
          "TextTransformations": [
            {
              "Priority": 0,
              "Type": "NONE"
            }
          ],
          "PositionalConstraint": "EXACTLY"
        }
      }
    }
  },
  "Action": {
    "Block": {
      "CustomResponse": {
        "ResponseCode": 401,
        "ResponseHeaders": [
          {
            "Name": "www-authenticate",
            "Value": "Basic"
          }
        ]
      }
    }
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": false,
    "MetricName": "BasicAuthRule"
  }
}

CloudFormation で AWS WAF Rule 作成

相変わらず、CloudFormationもご用意しているのでAWSコンソールにログイン後、下記URLをクリックしてください (クラウドフロント用のWAF のRuleを作成するため、米国東部 (バージニア北部) us-east-1 でスタックが作成されます。

Ruleの条件追加の余地を持たせるため、Capacityは10で作成していますが、条件を増やしたい場合はテンプレートをご自由に変更して下さい

ベーシック認証をサイト全体に設定する場合

https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateUrl=https%3A%2F%2Fpub-devio-blog-vtuisp2o.s3.amazonaws.com%2Ftemplate%2Fcfn-waf-basic-auth-rulegroup.yml&stackName=cfn-waf-basic-auth-rulegroup&param_CreateWebACL=false&param_User=user

パラメータ設定

User 名、Passwordをパラメータに設定し、スタックの作成を行ってください
CloudFormationの処理で、Basic認証のユーザー、パスワード変換部分を処理しています。 CreateWebACLをtrueに設定すると作成した、Basic認証のRuleGroupと紐づいたWebACLが作成されます。
ベーシック認証のみをおこなうWebACLを使用する機会はあまりないかと思いますが、確認等で検証いただけたらと思います。

ベーシック認証を特定のURL配下に設定する場合

特定のパス配下のみにベーシック認証を掛けたいというご要望にお応えしました!

https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateUrl=https%3A%2F%2Fpub-devio-blog-vtuisp2o.s3.amazonaws.com%2Ftemplate%2Fcfn-waf-basic-auth-rulegroup-path.yml&stackName=cfn-waf-basic-auth-rulegroup&param_CreateWebACL=false&param_User=user

パラメータ設定

UriPath に設定したい特定のパスを設定してください。 User 名、Passwordをパラメータに設定し、スタックの作成を行ってください
CloudFormationの処理で、Basic認証のユーザー、パスワード変換部分を処理しています。

作成されたリソースの確認

正常にスタックが作成されますと、BasicAuthRuleGroup-XXXXXXXX が作成されているかと思うので確認してください

https://console.aws.amazon.com/wafv2/homev2/rule-groups?region=global

WebACLへRuleの追加

既存のWebACLへ設定する場合は、作成したRuleGroupを選択し、ルールを追加してください。

検証

WAFを紐づけたCloudFront のサイトにアクセスし、Basic認証が適切に行われているかご検証ください。

まとめ

このためだけに、WAFのBasic認証を導入するのは、あまりないかもしれませんが、
すでにWAFを導入されている場合、既存のCloudFrontへ影響なく、一つ外側のレイヤーで認証処理をかけることができます。 Bot対策や、公開前コンテンツの一時保護など既存のCloudFrontでLambda@Edge, CloudFront Functionsで処理をしている場合、影響をださずに実現できるかと思います

また、IPアドレス制限、Bot対策などの別のWAFの条件と組み合わせて設定することもできるかと思いますので、ご要件にあえば応用できるかと思います。

参考

CloudFormationを使用して、既存のCloudFrontにCloudFront Functions(CF2)でBasic認証を設定する

CloudFormation テンプレート

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  User:
    Type: String
    Default: user
  Password:
    NoEcho: true
    Type: String

  CreateWebACL:
    Type: String
    Default: no
    AllowedValues: [yes, no]

Conditions:
  CreateWebACL: !Equals [ !Ref CreateWebACL, yes ]

Resources:
  BasicAuthRuleGroup:
    Type: AWS::WAFv2::RuleGroup        
    Properties:
      Capacity: 10
      Scope: CLOUDFRONT
      VisibilityConfig:
        CloudWatchMetricsEnabled: false
        MetricName: BasicAuthRuleGroup
        SampledRequestsEnabled: true
      Rules:
        - Name: BasicAuthRule
          Priority: 0
          Statement:
            NotStatement:
              Statement:
                ByteMatchStatement:
                  SearchString: !Sub 
                    - Basic ${authString}
                    - authString : !Base64
                        Fn::Join: [ ":", [ !Ref User,  !Ref Password ] ]
                  FieldToMatch:
                    SingleHeader:
                      Name: authorization
                  TextTransformations:
                    - Priority: 0
                      Type: NONE
                  PositionalConstraint: EXACTLY
          Action:
            Block:
              CustomResponse:
                ResponseCode: 401
                ResponseHeaders:
                  - Name: www-authenticate
                    Value: Basic
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: false
            MetricName: BasicAuthRule

  MyWebACL:
    Condition: CreateWebACL
    Type: AWS::WAFv2::WebACL
    Properties:
      DefaultAction: 
        Allow: {} 
      Scope: CLOUDFRONT
      Rules:
        - Name: MyRule
          Priority: 0
          Statement:
            RuleGroupReferenceStatement:
              Arn: !GetAtt BasicAuthRuleGroup.Arn
          OverrideAction:
            None: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: false
            MetricName: MyRule
      VisibilityConfig:
        CloudWatchMetricsEnabled: false
        MetricName: MyWebACL
        SampledRequestsEnabled: true

CloudFormation テンプレート(特定URLを条件に追加)

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  UriPath:
    Type: String
    Default: /basic-auth
  User:
    Type: String
    Default: user
  Password:
    NoEcho: true
    Type: String

  CreateWebACL:
    Type: String
    Default: no
    AllowedValues: [yes, no]

Conditions:
  CreateWebACL: !Equals [ !Ref CreateWebACL, yes ]

Resources:
  BasicAuthRuleGroup:
    Type: AWS::WAFv2::RuleGroup        
    Properties:
      Capacity: 10
      Scope: CLOUDFRONT
      VisibilityConfig:
        CloudWatchMetricsEnabled: false
        MetricName: BasicAuthRuleGroup
        SampledRequestsEnabled: true
      Rules:
        - Name: BasicAuthRule
          Priority: 0
          Statement:
            AndStatement:
              Statements:
                - ByteMatchStatement: 
                    SearchString: !Ref UriPath
                    FieldToMatch:
                      UriPath: {}
                    TextTransformations:
                      - Priority: 0
                        Type: NONE
                    PositionalConstraint: STARTS_WITH
                - NotStatement:
                    Statement:
                      ByteMatchStatement:
                        SearchString: !Sub 
                          - Basic ${authString}
                          - authString : !Base64
                              Fn::Join: [ ":", [ !Ref User,  !Ref Password ] ]
                        FieldToMatch:
                          SingleHeader:
                            Name: authorization
                        TextTransformations:
                          - Priority: 0
                            Type: NONE
                        PositionalConstraint: EXACTLY
          Action:
            Block:
              CustomResponse:
                ResponseCode: 401
                ResponseHeaders:
                  - Name: www-authenticate
                    Value: Basic
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: false
            MetricName: BasicAuthRule
  MyWebACL:
    Condition: CreateWebACL
    Type: AWS::WAFv2::WebACL
    Properties:
      DefaultAction: 
        Allow: {} 
      Scope: CLOUDFRONT
      Rules:
        - Name: MyRule
          Priority: 0
          Statement:
            RuleGroupReferenceStatement:
              Arn: !GetAtt BasicAuthRuleGroup.Arn
          OverrideAction:
            None: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: false
            MetricName: MyRule
      VisibilityConfig:
        CloudWatchMetricsEnabled: false
        MetricName: MyWebACL
        SampledRequestsEnabled: true