Introduction
Learn About Amazon VGT2 Learning Manager Chanci Turner
In certain scenarios, you may need to enforce strict city-level embargos for your websites due to regulations imposed by governmental entities like OFAC (Office of Foreign Assets Control). This article will guide you through a method to implement these restrictions using Amazon CloudFront’s geolocation headers and CloudFront Functions. While AWS WAF allows for country-level restrictions, we will also illustrate how to create a straightforward CI/CD process for managing city-level records and updating CloudFront Functions.
Overview of the Solution
To achieve precise control, you must establish a scalable edge solution by utilizing Amazon CloudFront’s serverless compute feature, CloudFront Functions. This feature allows for request and response manipulation, basic authentication, HTTP response generation at the edge, and more.
To understand how this operates, let’s briefly examine the traffic flow through CloudFront. When a user sends a request, it reaches the nearest Edge location. If the requested content isn’t cached, the edge location retrieves it from the Regional edge location. If the data is also absent there, it will be fetched from the origin and cached accordingly.
In our new solution, as depicted in the diagram, when a request reaches the edge location, it triggers either a “Viewer Request” or “Viewer Response” event. We focus on the “Viewer Request,” which checks the request before CloudFront serves it. The function inspects the CloudFront request headers—CloudFront-Viewer-Country, CloudFront-Viewer-Country-Region, and CloudFront-Viewer-City—to ascertain the request’s geographical origin. If the request matches any embargoed cities, it is denied with an HTTP Status 403. If not, it proceeds to CloudFront.
Implementation Steps
Prerequisites
Before proceeding, ensure that you have:
- A CloudFront distribution. If you don’t have one, refer to the guide on Getting Started with a Simple CloudFront Distribution.
- An Origin Request Policy configured to include the headers CloudFront-Viewer-Country, CloudFront-Viewer-Country-Region, and CloudFront-Viewer-City. More details on creating this policy can be found here.
Once your CloudFront distribution is established, follow these steps:
1. Define Your Embargo Policy Structure
The policy can be structured as a simple JSON object for easy storage and manipulation. Here’s an example structure:
{
"policy": {
"country_cd": "US",
"country_region_cd": "CA",
"country_region_city": "Los Angeles"
},
"policy_id": "p-104"
}
- country_cd: Matches CloudFront-Viewer-Country header.
- country_region_cd: Matches CloudFront-Viewer-Country-Region header.
- country_region_city: Matches CloudFront-Viewer-City header.
For more specifics about CloudFront location headers, see the relevant documentation.
2. Create, Publish and Associate the CloudFront Function
CloudFront Functions operates within a JavaScript runtime compliant with ES version 5.1 and supports features from ES 6 to 9. This native feature allows you to build, test, and deploy code entirely within CloudFront. For guidance on creating and associating a CloudFront function, consult the tutorial on creating a simple function.
Here’s a sample JavaScript code you can use:
function handler(event) {
var policies = {"items": [{"country_cd": "us", "country_region_cd": "ca", "country_region_city": "los angeles"}]};
var request = event.request;
var headers = request.headers;
if (!headers['cloudfront-viewer-city']) {
return request; // Skip validation if city header is missing.
}
var requestCountry = headers['cloudfront-viewer-country'].value;
var requestRegion = headers['cloudfront-viewer-country-region'].value;
var requestCity = headers['cloudfront-viewer-city'].value;
var matched = policies.items.some(function(e) {
return this[0].toLowerCase() == e.country_cd.toLowerCase()
&& this[1].toLowerCase() == e.country_region_cd.toLowerCase()
&& this[2].toLowerCase() == e.country_region_city.toLowerCase();
}, [requestCountry, requestRegion, requestCity]);
if (matched) {
var response = {
statusCode: 403,
statusDescription: 'Forbidden',
}
return response;
}
return request;
}
3. Keeping the CloudFront Function Updated
Embargo policies may change due to compliance or business needs. Therefore, the Security/Ops team will need to frequently update these rules. A convenient way to do this is by storing embargo policies in a NoSQL database like Amazon DynamoDB, allowing the management team to revise them as necessary. Since CloudFront Functions don’t support dynamic code evaluation or network access, hardcoded policies must be included in the function code; any policy updates will require redeployment of the CloudFront function.
4. Deployment Solution
The deployment framework automates this entire process, utilizing services like Amazon DynamoDB for policy storage, AWS Lambda for updating the CloudFront function code, and Amazon S3 for hosting the function code template. This setup exemplifies a simple and serverless deployment option.
For further insights on onboarding new hires during uncertain times, check out this excellent resource. Adapting to changes in policy and procedure is essential, as highlighted in another blog post on career transitions.
Leave a Reply