Wednesday, February 15, 2023

 

The Application Migration scenario for serving static content.

Single Page applications and static websites are quite popular for hosting code. By their nature, they are extremely portable and can run from filesystems as well as internet open directories. Yet, security and performance are not always properly considered for their deployments.  This articled delves into an architectural pattern to host static website content in the public cloud.

We choose Amazon AWS as the public cloud for this scenario but the pattern is universal and applies across most major public clouds.

When static content is hosted on AWS, the recommended approach is to use an S3 bucket as the origin and the CloudFront to distribute the content geographically. There are two primary benefits to this solution. The first is the convenience of caching static content at edge locations. The second involves defining web access control lists for the CloudFront distribution.  This helps to secure requests to the content with minimal configuration and administrative overhead.

The only limitation to this standard recommended approach is that in some cases, virtual firewall appliances may need to be deployed in a virtual private cloud to inspect all content. The standard approach does not route traffic through the virtual private cloud. Therefore, an alternative is needed that still uses a CloudFront distribution to serve static content in an S3 bucket but the traffic is routed through the VPC by using an Application Load Balancer. An AWS Lambda function then retrieves and returns the content from the S3 bucket.

The resources in this pattern must be in a single AWS region, but they can be provisioned in different AWS accounts. The limits apply to maximum request and response size that the Lambda function can receive and send, respectively.

There must be a good balance between performance, scalability, security and cost-effectiveness when using this approach. While Lambda can scale for high availability, the number of concurrent executions must not exceed the maximum quota otherwise requests will be denied.

The architecture lays out the CloudFront as facing the client and communicating with a firewall and two load balancers – one in each availability zone of the region hosting all these resources. The load balancers are created in the public subnet within a virtual private cloud and both communicate with a Lambda function that serves content from a private S3 bucket. When the client requests a URL, the CloudFront distribution forwards the request to a firewall which filters the request using the web ACLs applied to the CloudFront distribution. If the request cannot be served from the internal cache within the CloudFront, it is forwarded to the load balancer which has a listener associated with the target group based on a Lambda function. When the Lambda function is invoked, it performs a GetObject operation on the S3 bucket and returns the content as a response.

The deployment of the static content can be updated using a Continuous Integration / Continuous Deployment facilitating pipeline.

The Lambda function introduced in this pattern can scale to meet the load from all the load balancers and its security can be tightened by specifying the origin as the S3 bucket and similary for the distribution to have the origin as the load balancer. Instead of IP, dns name can be used to refer to the resources.

The following code demonstrates the Lambda function:

var AWS = require('aws-sdk');

exports.handler = function(event, context, callback) {

var bucket = process.env.S3_BUCKET;

var key = event.path.replace('/', '');

if (key == '') { key = 'index.html'; }

// Fetch from S3 var s3 = new AWS.S3();

return s3.getObject({Bucket: bucket, Key: key}, function(err, data) {

if (err) { return err; }

var isBase64Encoded = false;

var encoding = 'utf8';

if (data.ContentType.indexOf('image/') > -1) {

isBase64Encoded = true;

encoding = 'base64'

}

var resp = { statusCode: 200, headers: {

'Content-Type': data.ContentType,

},

 body: new Buffer(data.Body).toString(encoding),

 isBase64Encoded: isBase64Encoded

};

callback(null, resp);

} );

};

 

No comments:

Post a Comment