AWS

ALBへのアクセスをCloudFront経由に制限する

ALBへのアクセスをCloudFront経由に制限する

以下のページで、CloudFrontのオリジンにALBを指定する構成をご紹介しました。

上記のページでもご説明しましたが、CloudFrontのオリジンにALBを指定したとしても、引き続きALBへの直接アクセスは可能です。

本ページでは、以下のページを参考にしつつ、カスタムヘッダーを設定して、ALBへの直接アクセスを制限する方法を確認します。

Application Load Balancers へのアクセスを制限する - Amazon CloudFront
Amazon でカスタムオリジンヘッダー CloudFront を使用して、ユーザー (ビューワー) が Application Load Balancer に直接アクセスできないようにします。

構築する環境

Detail of restricting access to ALB to via CloudFront.

構成は冒頭でご紹介したページと同じです。

ただしALBへの直接アクセスを制限し、CloudFrontを経由するように強制します。
CloudFrontおよびALBに、カスタムヘッダーの設定を行います。

カスタムヘッダーに関する情報は、Secrets Managerに格納します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

awstut-fa/132 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

本ページは、ALBへの直接アクセスを制限する方法を中心に取り上げます。

CloudFrontに関する基本的な事項は、以下のページをご確認ください。

ALBに関する基本的な事項は、以下のページをご確認ください。

(参考) Secrets Manager

Resources:
  Secret:
    Type: AWS::SecretsManager::Secret
    Properties: 
      Description: test secret
      GenerateSecretString: 
        ExcludeCharacters: ""
        ExcludeLowercase: false
        ExcludeNumbers: false
        ExcludePunctuation: false
        ExcludeUppercase: false
        GenerateStringKey: !Ref CustomHeaderValueJsonKey
        IncludeSpace: false
        PasswordLength: !Ref CustomHeaderValueLength
        RequireEachIncludedType: true
        SecretStringTemplate: !Sub '{"${CustomHeaderNameJsonKey}": "${CustomHeaderName}", "${CustomHeaderValueJsonKey}": ""}'
      KmsKeyId: alias/aws/secretsmanager
      Name: !Ref Prefix
Code language: YAML (yaml)

ALBへの直接アクセスを制限するために、CloudFrontおよびALBにカスタムヘッダーに関する設定を行います。
このカスタムヘッダー情報の取り扱いについて、AWS公式では以下のように言及されています。

ヘッダー名と値が機密でない場合、他の HTTP クライアントは、Application Load Balancer に直接送信するリクエストにヘッダー名や値を含める可能性があります。これにより、リクエストをしていない時に、リクエストが CloudFront から送信されたかのように Application Load Balancer を動作させる可能性があります。これを防ぐためには、カスタムヘッダー名と値を機密にしておきます。

Application Load Balancers へのアクセスを制限する

上記に従い、今回はカスタムヘッダー情報をSecrets Managerに格納します。

今回生成するシークレット情報ですが、カスタムヘッダー名は「X-Custom-Header」と固定とします。
一方、カスタムヘッダー値は、シークレット作成時に、自動的にランダムパスワードを生成し、これを使用します。

Secrets Maangerを使用して、ランダムなパスワードを生成する方法に関しては、以下のページをご確認ください。

今回作成するシークレット情報ですが、JSON形式ですと、以下のような情報となります。

{“CustomHeaderName”: “X-Custom-Header”, “CustomHeaderValue”: “[random-string]”}

CloudFront

Resources:
  Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          Compress: true
          ForwardedValues:
            Cookies:
              Forward: none
            QueryString: false
          TargetOriginId: !Ref ALBDNSName
          ViewerProtocolPolicy: allow-all
          DefaultTTL: !Ref CacheTTL
          MaxTTL: !Ref CacheTTL
          MinTTL: !Ref CacheTTL
        Enabled: true
        Origins:
          - CustomOriginConfig:
              OriginProtocolPolicy: http-only
            DomainName: !Ref ALBDNSName
            Id: !Ref ALBDNSName
            OriginCustomHeaders:
              - HeaderName: !Sub "{{resolve:secretsmanager:${Secret}:SecretString:${CustomHeaderNameJsonKey}}}"
                HeaderValue: !Sub "{{resolve:secretsmanager:${Secret}:SecretString:${CustomHeaderValueJsonKey}}}"
        PriceClass: PriceClass_All
Code language: YAML (yaml)

ポイントはOriginCustomHeadersプロパティです。
ここで設定したヘッダー情報が、CloudFront〜ALB間のトラフィックに追加されます。

先述のSecrets Managerの値を参照して、カスタムヘッダーを設定します。
具体的には、CloudFormationの動的な参照を使用します。

動的な参照を使用してテンプレート値を指定する - AWS CloudFormation
動的な参照では、他のサービスに格納および管理されている外部値をスタックテンプレートに指定するための簡潔で強力な方法が提供されます。

HeaderNameプロパティは、Secrets ManagerのJSONデータから、CustomHeaderNameの値を参照します。
HeaderValueプロパティは、Secrets ManagerのJSONデータから、CustomHeaderValueの値を参照します。

ALB

Resources:
  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub "${Prefix}-ALB"
      Scheme: internet-facing
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      Type: application

  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VPC
      Name: !Sub "${Prefix}-ALBTargetGroup"
      Protocol: HTTP
      Port: !Ref HTTPPort
      HealthCheckProtocol: HTTP
      HealthCheckPath: /
      HealthCheckPort: traffic-port
      HealthyThresholdCount: !Ref HealthyThresholdCount
      UnhealthyThresholdCount: !Ref UnhealthyThresholdCount
      HealthCheckTimeoutSeconds: !Ref HealthCheckTimeoutSeconds
      HealthCheckIntervalSeconds: !Ref HealthCheckIntervalSeconds
      Matcher: 
        HttpCode: !Ref HttpCode
      Targets:
        - Id: !Ref Instance1
        - Id: !Ref Instance2
        
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties: 
      DefaultActions: 
        - FixedResponseConfig:
            ContentType: text/plain
            MessageBody: Access denied
            StatusCode: 403
          Type: fixed-response
      LoadBalancerArn: !Ref ALB
      Port: !Ref HTTPPort
      Protocol: HTTP
      
  ALBListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties: 
      Actions: 
        - TargetGroupArn: !Ref ALBTargetGroup
          Type: forward
      Conditions: 
        - Field: http-header
          HttpHeaderConfig:
            HttpHeaderName: !Sub "{{resolve:secretsmanager:${Secret}:SecretString:${CustomHeaderNameJsonKey}}}"
            Values: 
              - !Sub "{{resolve:secretsmanager:${Secret}:SecretString:${CustomHeaderValueJsonKey}}}"
      ListenerArn: !Ref ALBListener
      Priority: 1
Code language: YAML (yaml)

ポイントは2点です。

1点目はALBリスナーのデフォルトアクションです。
冒頭でご紹介したページの構成ですと、デフォルトアクションとして、ALBターゲットグループへのルーティングするように設定していました。
今回の構成のデフォルトアクションでは、HTTPステータスコード403を返すように変更します。

2点目はリスナールールの追加です。
ALBターゲットグループへルーティングするルールを追加します。
Conditionプロパティで、このルールが適用される条件を設定します。
この条件はヘッダー情報に基づくものです。
着信したHTTPリクエストが、CloudFrontで設定したカスタムヘッダーを持つ場合に限り、ターゲットグループにルーティングされます。
チェックするカスタムヘッダー情報に関しては、CloudFrontと同様に、Secrets Managerの値を参照します。

2つのポイントをまとめますと、CloudFrontで設定したカスタムヘッダーを持つHTTPリクエストの場合は、ALBターゲットグループにルーティングされ、それ以外については403が返されます。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • Secrets Manager:fa-132
  • インスタンス1:i-09f7807d448e1b62e
  • インスタンス2:i-0e734e5fa1b1da141
  • ALBのDNS名:fa-132-ALB-1742616783.ap-northeast-1.elb.amazonaws.com
  • CloudFrontディストリビューションのDNS名:dq15nc7eow2lq.cloudfront.net

AWS Management Consoleからも、各リソースの作成状況を確認します。

Secrets Managerを確認します。

Detail of Secrets Manager 1.

正常にシークレットが作成されています。
保存されている値を見ると、ヘッダー名とヘッダー値が保存されていることがわかります。
特にヘッダー値の方は、自動的にランダムな文字列が設定されています。

ALBを確認します。

Detail of ALB 1.
Detail of ALB 2.

正常にALBが作成されています。
ALBのターゲットグループを見ると、2つのインスタンスが登録されていることがわかります。

このALBのリスナールールを確認します。

Detail of ALB 3.

2つのルールが設定されています。
最初のルールはターゲットグループへのルーティングに関するものです。
カスタムヘッダーの値が合致する場合に限り、ターゲットグループにルーティングされます。
この設定は、先述のSecrets Managerに保存した値に基づいて設定されています。

最後のルールは、上記のルールを満たさなかったトラフィック用のものです。
例えば直接ALBにアクセスした場合は、カスタムヘッダーの設定を持たないトラフィックとなりますので、ALBが403コードを返します。

CloudFrontを確認します。

Detail of CloudFront 1.
Detail of CloudFront 2.

正常にCloudFrontディストリビューションが作成されています。
ディストリビューションのオリジンに、先述のALBが指定されています。

このオリジンに関する設定を確認します。

Detail of CloudFront 3.

カスタムヘッダーに関する項目を見ると、ALBリスナールールと同様の設定があります。
つまりこのCloudFrontディストリビューションを経由するトラフィックには、このカスタムヘッダーが付与されるということです。

動作確認

準備が整いましたので、CloudFrontにアクセスします。

$ curl https://dq15nc7eow2lq.cloudfront.net
instance-id: i-09f7807d448e1b62e

$ curl https://dq15nc7eow2lq.cloudfront.net
instance-id: i-0e734e5fa1b1da141
Code language: Bash (bash)

応答がありました。
ALB配下の2台のインスタンスにアクセスできています。
CloudFront経由でアクセスすると、途中でカスタムヘッダーが設定されるため、正常にALB配下のインスタンスにアクセスすることができました。

続いてALBに直接アクセスします。

$ curl http://fa-132-ALB-1742616783.ap-northeast-1.elb.amazonaws.com
Access denied
Code language: Shell Session (shell)

アクセスが拒否されました。
ALBに直接アクセスすると、カスタムヘッダーが設定されないため、ALBで403が返されました。

まとめ

カスタムヘッダーを設定して、ALBへの直接アクセスを制限する方法を確認しました。

タイトルとURLをコピーしました