Easily restrict Kubernetes access to CloudFront
CloudFront is an easy to use CDN that can be placed in front of an Elastic Load Balancer (ELB). However, placing CloudFront in front of an ELB does not guarantee that all traffic will go through CloudFront. It is possible to bypass it completely and go straight to the ELB which poses a security concern when relying on restrictions implemented on CloudFront including AWS WAF.
CloudFront allows you to send a custom header and value to an origin. This feature can be used to validate that requests have originated from CloudFront. When using Kubernetes and an Nginx controller, it is possible to configure Nginx to check for incoming headers. If the custom header exists with a valid shared token, then the request is valid.
If the request does not contain the custom header, the request will be dropped. Therefore, all requests going directly to the load balancer will not have the custom header and will be dropped.
Step 1: Configuring CloudFront
To lockdown CloudFront to Kubernetes, we need to make CloudFront add a custom header on every request to the origin. This can be done by adding a new header and value to the section Origin Custom Headers. We will look at two methods of configuring this.
Option 1: Configuring through UI
For an existing distribution, the configuration location can be found by navigating to:
Selected Distribution -> Origins and Origin Groups -> Edit Origin
The Origin Custom Header can be found at the bottom of the edit screen as shown below
Option 2: Configuring with CloudFormation
It is possible to use CloudFormation or Terraform to configure the Origin Custom Header. The following is a simple CloudFormation template that shows how to add the custom headers to an origin:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
OriginDomain:
Type: String
CloudfrontToken:
Type: String
OriginRequestPolicyId:
Type: String
Default: 216adef6-5c7f-47e4-b989-5492eafa07d3
CachePolicyId:
Type: String
Default: 4135ea2d-6df8-44a3-9df3-4b5a84be39adResources:
CloudfrontDistributionWithToken:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
DefaultCacheBehavior:
TargetOriginId: !Ref OriginDomain
ViewerProtocolPolicy: allow-all
OriginRequestPolicyId: !Ref OriginRequestPolicyId
CachePolicyId: !Ref CachePolicyId
Enabled: True
Origins:
- Id: !Ref OriginDomain
DomainName: !Ref OriginDomain
OriginCustomHeaders:
- HeaderName: cf-token
HeaderValue: !Ref CloudfrontToken
CustomOriginConfig:
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: match-viewer
Step 2: Configuring Nginx Controller
In order to make Nginx check for incoming headers, a small script will need to be added. This can be done by updating the Nginx controller configmap. We will look at updating the configmap directly as well as updating it through the nginx-controller helm chart.
Option 1: Updating configmap directly
On your kubernetes cluster, locate the configmap used by the nginx controller. This is usually called ingress-nginx-controller in the ingress-nginx namespace.
Within the configmap, update the data variable and add the following lines:
location-snippet: |
if ($http_cf_token != 'random_value') { return 403;
}
This small script will check whether or not the header cf-token is equal to the specified value. Remember to set random_value to your token value. Your configmap should look something like this:
apiVersion: v1
kind: ConfigMap
data:
location-snippet: |
if ($http_cf_token != 'random_value') {
return 403;
}
Option 2: Updating helm chart
If you are using helm chart for your Nginx controller deployment, you can add the config variable to your values.yaml file as shown below and update your helm release.
controller:
config:
location-snippet: |
if ($http_cf_token != ‘${cf_token}’) {
return 403;
}
Automation Ideas
All of the mentioned steps can be automated using infrastructure as code tools such as Terraform and CloudFormation. For example, it is possible to generate random value in Terraform and CloudFormation which is used as the shared token between CloudFront and the Nginx controller
With terraform, this can be done by using the random provider. With CloudFormation, it is a bit more difficult but a range of options are available which you can search online. I have used the Stack ID approach before where a random value is retrieved from the Stack ID.
Summary
It is possible to restrict Kubernetes access to CloudFront by making CloudFront add a Custom header on each request. This header is then checked by the Nginx controller. With these changes, all requests to Kubernetes will be validated to ensure that requests come from CloudFront. This ensures that no one can bypass and potentially evade restrictions implemented on CloudFront.