Restrict access to ALB to via CloudFront

Restrict access to ALB to via CloudFront

In the following page, we introduced a configuration that specifies ALB as the CloudFront origin.

あわせて読みたい
Specify ALB as the origin of CloudFront 【Specify ALB as the origin of CloudFront】 The following pages cover the basics of CloudFront. https://awstut.com/en/2022/03/12/improved-origin-server-perfo...

As explained in the page above, direct access to the ALB is still possible even if the ALB is specified as the CloudFront origin.

In this page, we will check how to restrict direct access to the ALB by setting a custom header, referring to the following page.

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/restrict-access-to-load-balancer.html

Environment

Detail of restricting access to ALB to via CloudFront.

The structure is the same as the page introduced at the beginning of this document.

However, we will restrict direct access to the ALB and force it to go through CloudFront.
Configure custom headers for CloudFront and ALB.

Information about custom headers is stored in the Secrets Manager.

CloudFormation template files

The above configuration is built using CloudFormation.
The CloudFormation template is located at the following URL

https://github.com/awstut-an-r/awstut-fa/tree/main/132

Explanation of key points of template files

This page focuses on how to restrict direct access to the ALB.

For basic information on CloudFront, please refer to the following pages.

あわせて読みたい
Improved Origin Server Performance with CloudFront Cache 【Configuration using CloudFront cache to reduce number of requests to origin server】 This is one of the topics covered in AWS SAA, which is about designing...

For basic information on ALB, please refer to the following pages.

あわせて読みたい
Attaching instances in private subnet to ALB 【Configure instances in private subnets to be attached to ALB】 We will see how to attach an instance located in a private subnet to an ALB. The following m...

(Reference) 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)

To restrict direct access to ALB, configure CloudFront and ALB regarding custom headers.
The handling of this custom header information is mentioned in the AWS official documentation as follows

If the header name and value are not secret, other HTTP clients could potentially include them in requests that they send directly to the Application Load Balancer. This can cause the Application Load Balancer to behave as though the requests came from CloudFront when they did not. To prevent this, keep the custom header name and value secret.

Restricting access to Application Load Balancers

Follow the above, this time storing custom header information in the Secrets Manager.

The custom header name is fixed as “X-Custom-Header”.
On the other hand, the custom header value is a random password that is automatically generated when the secret is created.

For more information on how to generate a random password using Secrets Maanger, please see the following page.

あわせて読みたい
Use Secrets Manager to generate random password 【Use Secrets Manager to generate random password】 Secrets Manager can be used to generate random passwords. https://docs.aws.amazon.com/secretsmanager/late...

The secret information to be created this time, in JSON format, is as follows.

{“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)

The key point is the OriginCustomHeaders property.
The header information set here is added to the traffic between CloudFront and ALB.

The custom headers are set by referencing the aforementioned Secrets Manager values.
Specifically, use the dynamic reference in CloudFormation.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html

The HeaderName property refers to the value of CustomHeaderName from the JSON data in the Secrets Manager.
The HeaderValue property refers to the value of CustomHeaderValue from the JSON data in Secrets Manager.

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)

There are two points.

The first point is the default action of the ALB listener.
In the configuration of the page introduced in the introduction, the default action was set to route to the ALB target group.
The default action in this configuration will be changed to return HTTP status code 403.

The second point is to add a listener rule.
Add a rule to route to the ALB target group.
In the Condition property, set the condition under which this rule is applied.
This condition is based on header information.
Incoming HTTP requests will be routed to the target group only if they have custom headers configured in CloudFront.
As for the custom header information to be checked, it refers to the values in Secrets Manager as well as CloudFront.

To summarize the two points, HTTP requests with custom headers configured in CloudFront will be routed to the ALB target group, while all others will return 403.

Architecting

Using CloudFormation, we will build this environment and check the actual behavior

Create CloudFormation stacks and check the resources in the stacks

Create CloudFormation stacks.
Please refer to the following pages for information on how to create stacks and check each stack

あわせて読みたい
CloudFormation’s nested stack 【How to build an environment with a nested CloudFormation stack】 Examine nested stacks in CloudFormation. CloudFormation allows you to nest stacks. Nested ...

After reviewing the resources in each stack, information on the main resources created in this case is as follows

  • Secrets Manager: fa-132
  • Instance 1: i-09f7807d448e1b62e
  • Instance 2: i-0e734e5fa1b1da141
  • DNS name of ALB: fa-132-alb-1742616783.ap-northeast-1.elb.amazonaws.com
  • DNS name of CloudFront distribution: dq15nc7eow2lq.cloudfront.net

The AWS Management Console also checks the creation status of each resource.

Check Secrets Manager.

Detail of Secrets Manager 1.

The secret is successfully created.
The stored values show that the header name and header value are stored.
In particular, the header value is automatically set to a random string.

Check the ALB.

Detail of ALB 1.
Detail of ALB 2.

The ALB is successfully created.
If you look at the target group of the ALB, you will see that two instances have been registered.

Check the listener rules for this ALB.

Detail of ALB 3.

Two rules are configured.
The first rule concerns routing to the target group.
Only if the value of the custom header matches will it be routed to the target group.
This setting is based on the values stored in the Secrets Manager described above.

The last rule is for traffic that did not meet the above rules.
For example, if you access the ALB directly, the ALB will return a 403 code because the traffic does not have a custom header setting.

Check CloudFront.

Detail of CloudFront 1.
Detail of CloudFront 2.

The CloudFront distribution has been successfully created.
The aforementioned ALB is specified as the origin of the distribution.

Check the settings for this origin.

Detail of CloudFront 3.

If you look at the section on custom headers, you will see a setting similar to the ALB listener rule.
This means that traffic going through this CloudFront distribution will be given this custom header.

Operation Check

Now that you are ready, access CloudFront.

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

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

Response.
We were able to access the two instances under the ALB.
When accessing via CloudFront, I was able to successfully access the instances under the ALB because a custom header is set on the way.

Then access the ALB directly.

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

Access denied.
A direct access to the ALB returned a 403 in the ALB because the custom header was not set.

Summary

We have identified a way to limit direct access to the ALB by setting a custom header.