Wednesday, February 24, 2021

Configure Lamda@Edge using CloudFormation

 



In the previous post, we have created a CDN CloudFront using the AWS CloudFormation. In this post we will configure a Lambda@Edge.

The Lambda@Edge can be run on the request or on the response, and also on the viewer side or on the origin side. In this example we will start the lambda on the origin request, which means it will e started upon cache miss on the CDN. The lambda will write to a S3 bucket the request details.


First let's configure the S3 bucket.



S3Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
BucketName: cdn-po-transactions
Tags:
- Key: Name
Value: transactions



Next add the lambda role, and add permissions to write to the S3 bucket.



LambdaRole:
Type: AWS::IAM::Role
Properties:
Tags:
- Key: Name
Value: lambda-role
Path: "/"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AmazonS3FullAccess
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Sid: AllowLambdaServiceToAssumeRole
Effect: Allow
Action:
- sts:AssumeRole
Principal:
Service:
- lambda.amazonaws.com
- edgelambda.amazonaws.com



Now we configure a lambda version, allowing us to deploy it.



LambdaVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName:
Ref: Lambda



And configure the lambda itself, including its NodeJS based code.



Lambda:
Type: AWS::Lambda::Function
Properties:
Role: !GetAtt LambdaRole.Arn
Runtime: nodejs12.x
Timeout: 10
Tags:
- Key: Name
Value: lambda
Handler: index.handler
Code:
ZipFile: |
const https = require('https');
const aws = require('aws-sdk');
const keepAliveAgent = new https.Agent({keepAlive: true});
const s3 = new aws.S3({region: 'us-east-1', httpOptions: {agent: keepAliveAgent}});

exports.handler = function(event, context, callback) {
const path = Date.now() + '/' + (Math.random().toString(36).substr(2, 9)).toUpperCase();
const params = {
Bucket: 'cdn-po-transactions',
Key: path,
Body: JSON.stringify(event),
};

s3.upload(params, function(err, data) {
if (err) {
throw err;
}
console.log(`File uploaded successfully. ${data.Location}`);
});

const request = event.Records[0].cf.request;
callback(null, request);
}



The last thing to do is to update the CloudFront configuration (presented in  the previous post), to include the lambda invocation.



CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
HttpVersion: http2
IPV6Enabled: false
DefaultCacheBehavior:
CachePolicyId: !Ref CloudFrontCachePolicy
OriginRequestPolicyId: !Ref CloudFrontOriginRequestPolicy
TargetOriginId: !Ref ApplicationLoadBalancer
ViewerProtocolPolicy: allow-all
LambdaFunctionAssociations:
- EventType: origin-request
IncludeBody: false
LambdaFunctionARN: !Join
- ":"
-
- !GetAtt Lambda.Arn
- !GetAtt LambdaVersion.Version
Origins:
- Id: !Ref ApplicationLoadBalancer
DomainName: !GetAtt ApplicationLoadBalancer.DNSName
CustomOriginConfig:
HTTPPort: 80
OriginProtocolPolicy: match-viewer
OriginSSLProtocols:
- TLSv1



Final Note


In this post we have configured Lambda@Edge on the AWS CloudFront using AWS CloudFormation. The Lambda@Edge enables use to manipulate the requests and the response, in addition to saving various parts of the request and the response.



Create AWS CloudFront using CloudFormation

 



In the previous post we have created an application load balancer using CloudFormation. In this post we will configure CloudFront CDN using CloudFormation.


To create CDN, we first configure the cache policy, which configures the cache TTL, and which parameters of the request are used to create the key of the item in the cache.



CloudFrontCachePolicy:
Type: AWS::CloudFront::CachePolicy
Properties:
CachePolicyConfig:
DefaultTTL: 10
MinTTL: 10
MaxTTL: 10
Name: po-cache-policy
ParametersInCacheKeyAndForwardedToOrigin:
EnableAcceptEncodingGzip: false
CookiesConfig:
CookieBehavior: none
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: all


Next we configure the origin request policy, which configures which of the request parameters are sent upstream to the origin web server.


CloudFrontOriginRequestPolicy:
Type: AWS::CloudFront::OriginRequestPolicy
Properties:
OriginRequestPolicyConfig:
Name: po-origin-policy
CookiesConfig:
CookieBehavior: all
HeadersConfig:
HeaderBehavior: allViewer
QueryStringsConfig:
QueryStringBehavior: all



Lastly we configure the CDN itself, and ask it to use the application load balancer that we have created n the previous post as its upstream server.



CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
HttpVersion: http2
IPV6Enabled: false
DefaultCacheBehavior:
CachePolicyId: !Ref CloudFrontCachePolicy
OriginRequestPolicyId: !Ref CloudFrontOriginRequestPolicy
TargetOriginId: !Ref ApplicationLoadBalancer
ViewerProtocolPolicy: allow-all
Origins:
- Id: !Ref ApplicationLoadBalancer
DomainName: !GetAtt ApplicationLoadBalancer.DNSName
CustomOriginConfig:
HTTPPort: 80
OriginProtocolPolicy: match-viewer
OriginSSLProtocols:
- TLSv1



Final Note


In this post we have created a CDN by configuring AWS CloudFront. In the next post, we will configure a Lambda@Edge for the CDN.


Create AWS Application Load Balancer Using CloudFormation


 

In this post we will create an AWS application load balancer using CloudFormation.



The EC2 servers


In the previous post, we have presented a creation of VPC and EC2 based web server. To create an application load balancer we need to use at least two different availability zones. I have modified the previous template to create two EC2 machines in two availability zones.



Parameters:
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Tags:
- Key: Name
Value: po-vpc
Subnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: us-east-1a
Tags:
- Key: Name
Value: po-subnet1
Subnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: us-east-1b
Tags:
- Key: Name
Value: po-subnet2
InternetGateway:
Type: AWS::EC2::InternetGateway
DependsOn: VPC
Properties:
Tags:
- Key: Name
Value: po-route-table
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
RouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: po-route-table1
RouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: po-route-table2
RouteTableAssociate1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: Subnet1
RouteTableId:
Ref: RouteTable1
RouteTableAssociate2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: Subnet2
RouteTableId:
Ref: RouteTable2
PublicRoute1:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref RouteTable1
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicRoute2:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref RouteTable2
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupDescription: the security group
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: po-security-group
Server1:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: !Ref LatestAmiId
SubnetId: !Ref Subnet1
KeyName: PO
SecurityGroupIds:
- !Ref SecurityGroup
Tags:
- Key: Name
Value: po-server1
Server2:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: !Ref LatestAmiId
SubnetId: !Ref Subnet2
KeyName: PO
SecurityGroupIds:
- !Ref SecurityGroup
Tags:
- Key: Name
Value: po-server2
ElasticIP1:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
InstanceId: !Ref Server1
Tags:
- Key: Name
Value: po-elastic-ip1
ElasticIP2:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
InstanceId: !Ref Server2
Tags:
- Key: Name
Value: po-elastic-ip2



The Application Load Balancer


Now we have 2 EC2 servers running in two different availability zones, and we can configure the application load balancer.



ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
DependsOn: AttachGateway
Properties:
IpAddressType: ipv4
Scheme: internet-facing
SecurityGroups:
- !Ref SecurityGroup
Subnets:
- !Ref Subnet1
- !Ref Subnet2
Type: application
Tags:
- Key: Name
Value: po-alb


The application load balancer is connected to the two subnets, each one in a different availability zone. Next we configure the targets for the application load balancer to be the two EC2 servers.


TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Port: 80
Protocol: HTTP
TargetType: instance
VpcId: !Ref VPC
Targets:
- Id: !Ref Server1
- Id: !Ref Server2
Tags:
- Key: Name
Value: po-alb-tg


The last step is to configure a listener that the application load balancer will serve request on its port.


ApplicationLoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 80
Protocol: HTTP
DefaultActions:
- TargetGroupArn: !Ref TargetGroup
Type: forward


Final Note


We have presented how to configure an application load balancer on AWS using the AWS CloudFormation. In the next post, we will configure a CloudFront CDN for it.


Tuesday, February 16, 2021

CloudFormation - Create a VPC and an EC2 based Web Server



 

In the previous post, we've used AWS console to create a VPC, and deployment of a sample web server on a new EC2 instance.

However, if there is a need to deploy this automatically, we can use AWS CloudFormation.

The CloudFormation uses a template YAML or JSON file (aka stack) to create AWS entities. 

Unlike AWS CLI, the CloudFormation support add, update, and delete of the AWS entities as a transaction, so if one of the entities creation had failed, the entire entities in the stack will be rolledback. Also, update of the AWS entities in the template file, will update only the required items in the stack.



Stack Install and Update


To use the CloudFormation, I've found the best method is to use a CLI, which installs or updates the stack, based of if it exists or not. 


deploy.sh

#!/bin/bash
set -e
cd "$(dirname "$0")"
deployFolder=${PWD}

export AWS_SHARED_CREDENTIALS_FILE=${deployFolder}/aws-config/credentials
export AWS_CONFIG_FILE=${deployFolder}/aws-config/config
export AWS_PAGER=""


stackName=mystack
stackExists=$(aws cloudformation describe-stacks | jq -r ".Stacks[].StackName" | grep ${stackName} | wc -l)
if [[ "${stackExists}" = "0" ]]; then
echo "install stack"
aws cloudformation create-stack --stack-name ${stackName} --template-body file://po.yaml
aws cloudformation wait stack-create-complete --stack-name ${stackName}
else
echo "update stack"
aws cloudformation update-stack --stack-name ${stackName} --template-body file://po.yaml
aws cloudformation wait stack-update-complete --stack-name ${stackName}
fi



The Template File


The template file can handle all AWS entities, except for the key pair for EC2, that we need to manually create in advanced. Let review the parts of the template file.


Parameters:
LatestAmiId:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'


This fetch from AWS the latest Linux 64 buts image ID. We will later use it for the EC2 instance configuration.


Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Tags:
- Key: Name
Value: sample-vpc


Now we present the resources that we want to configure, starting with the VPC configuration. Next we want to configure the subnet.


Subnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: us-east-1a
Tags:
- Key: Name
Value: sample-subnet1



Notice that the subnet configuration requires the VPC ID. Here we use the CloudFront template function !Ref to fill this information. Next we add the internet gateway, and assign it to the VPC.


InternetGateway:
Type: AWS::EC2::InternetGateway
DependsOn: VPC
Properties:
Tags:
- Key: Name
Value: sample-route-table
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway


Using the AWS console handles these two resources in one dialog, but in the template file, we are exposed to the real AWS implementation, hence we are required to make some more configuration.

Let's add the route table, and add the internet gateway route:


RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: sample-route-table
RouteTableAssociate:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: Subnet1
RouteTableId:
Ref: RouteTable
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway



And lastly, add the EC2 instance, and  assign an elastic IP to it.



SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupDescription: the ec2 security group
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: sample-ec2-security-group
EC2:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: !Ref LatestAmiId
SubnetId: !Ref Subnet1
KeyName: PO
SecurityGroupIds:
- !Ref SecurityGroup
Tags:
- Key: Name
Value: sample-web
ElasticIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
InstanceId: !Ref EC2
Tags:
- Key: Name
Value: sample-elastic-ip



Final Note


In this post we have reviewed usage of AWS CloudFormation to create AWS entities for VPC, and a EC2 instance on it, which is available as a web server. The CloudFormation is a great tool for automation, and to keep AWS entities configuration. CloudFormation GUI designer can also be used to create the file.

For clarify purpose, I attach the entire file here.


Parameters:
LatestAmiId:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Tags:
- Key: Name
Value: sample-vpc
Subnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: us-east-1a
Tags:
- Key: Name
Value: sample-subnet1
Subnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: us-east-1b
Tags:
- Key: Name
Value: sample-subnet1
InternetGateway:
Type: AWS::EC2::InternetGateway
DependsOn: VPC
Properties:
Tags:
- Key: Name
Value: sample-route-table
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: sample-route-table
RouteTableAssociate:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: Subnet1
RouteTableId:
Ref: RouteTable
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupDescription: the ec2 security group
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: sample-ec2-security-group
EC2:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: !Ref LatestAmiId
SubnetId: !Ref Subnet1
KeyName: PO
SecurityGroupIds:
- !Ref SecurityGroup
Tags:
- Key: Name
Value: sample-web
ElasticIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
InstanceId: !Ref EC2
Tags:
- Key: Name
Value: sample-elastic-ip


Wednesday, February 10, 2021

AWS - Create a VPC and an EC2 based Web Server


 


In this post we will review the steps to use AWS for creation of a VPC, and deployment of a sample web server on a new EC2 instance.

The steps are:

  • Login to AWS Management Console
  • VPC
  • Subnet
  • Internet Gateway
  • Routing Table
  • EC2 instance
  • Elastic IP
  • Security Group
  • Web Server


Login to AWS Management Console


To use AWS, you should get an account. In most cases, you already have an account paid by the company where you're employed at. In case you are working on you own, you can start with AWS Free Tier based account.

Once your account is setup, login to the AWS Management console.


VPC


A VPC is a Virtual Private Cloud, a logically isolated private network that you use for your servers. The VPC can include multiple network subnets. 
In the AWS management console, use the search box at the top to locate the VPC service, an open it.
Next click on Your VPCs in the left menu, and then on Create VPC.
Fill in the following:
  • Name: sample-vpc
  • IPv4 CIDR: 10.0.0.0/16
and click Create VPC.




Subnet


A subnet is a network segment that is part of a VPC. It has its own routing table, and security groups.

To add a subnet click on Subnets in the left menu, and click Create Subnet.
Fill in the following:
  • vpc: sample-vpc
  • name: sample-subnet
  • IPv4 CIDR: 10.0.0.0/24
and click Create Subnet.




Internet Gateway


An internet gateway is a logical router allowing access from and to a subnet.

To add an internet gateway click on Internet Gateways in the left menu, and click Create internet gateway.
Fill in the following:
  • name: sample-internet-gateway
and click Create internet gateway.

Now click on Actions, Attach to VPC, and select the sample-vpc.





Routing Table


Routing table configures how do we access from and to a subnet. 
We will configure access from and to the subnet to the internet through the internet gateway.
Open the VPC service in AWS console, click on Route Tables in the left menu, and select the route table related to the VPC.
Next add route destination 0.0.0.0/24 to the internet gateway that we have created.





EC2 Instance


An EC2 instance is a virtual machine running within the VPC on the cloud.


In the AWS management console, use the search box at the top to locate the EC2 service, an open it.
Follow the next steps:
  • Click on the Launch instance button
  • Select "Amazon Linux 2"
  • Select t2.macro, and click next
  • Select the sample-vpc, and the sample-subnet, and click review and launch
  • Click launch
  • Create a new key pair and save it locally
  • Click Launch instance
A few seconds later the EC2 instance is already running.





Elastic IP


Elastic IP is a static public IP that is allocated by AWS for our use.
To create an Elastic IP, open the VPC service in AWS console, click on Elastic IPs in the left menu, and click Allocate Elastic IP address, and click Allocate.
Next click on the added IP, and click on Associate Elastic IP address, add fill in the EC2 instance name, the the IP of the EC2 instance (all these fields have auto-fill, you do not need to keep notes...), and click associate.





Security Group


A security group configures the rules that allow access to an entity such as a EC2 instance and a Subnet.
We need to allow access to the EC2 instance on port 80.
Open the VPC service in AWS console, click on Security Groups in the left menu, and select the security group that was automatically added as part of the EC2 server creation (the name should look like launch-wizard-1).
Edit the inbound rules, and add HTTP access from all IPs 0.0.0.0/0.






Web Server


We will setup a sample web server based on the juice shop project.

SSH to the EC2 using the IP that was allocated by AWS for the Elastic IP, and the key pair that we have created as part of the EC2 instance creation.


ssh -i keypair.pem ec2-user@3.128.70.18
sudo yum -y install docker
sudo service docker start
sudo docker run -d --rm -p 80:3000 bkimminich/juice-shop


and our site is up, and accessible through the public web:





Final Note


In this post we have reviewed the basic steps to setup a public web server on AWS using VPC and an EC2 instance.
This is only a basic quick-start setup which does not handle the security implications, and the availability considerations, but it can provide a great boost for your first project.



Tuesday, February 2, 2021

Injecting JavaScript Agent Using NGINX


 


In this post we will review how to inject JavaScript code to a site using NGINX.

We will review several methods:

  1. Static JavaScript code injection
  2. Dynamic JavaScript code injection
  3. Long dynamic JavaScript code injection



The Start Position


Lets assume that we have an NGINX running with the following configuration:


nginx.conf

user  nginx;
worker_processes 10;

error_log /dev/stdout debug;
pid /var/run/nginx.pid;

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/resolvers.conf;
access_log on;
sendfile on;
port_in_redirect off;

upstream backend {
server www.backend.com;
keepalive 1000;
keepalive_requests 1000000;
keepalive_timeout 300s;
}

server {
listen 8080;
server_name localhost;

location / {
proxy_set_header Accept-Encoding "";
proxy_set_header Host $http_host;
proxy_pass http://backend;
}
}
}



So we simply use the NGINX as a reverse proxy to an upstream server www.backend.com.

Next we will examine various methods to inject JavaScript code base on this setup.



Static JavaScript Code Injection


We want to inject a short JavaScript code, for example:



console.log('Hello World')



To inject the JavaScript, we update the following under the location / section in the NGINX configuration:


nginx.conf (partial view)

proxy_set_header            Accept-Encoding "";
sub_filter '<head>' '<head><script>console.log("Hello World")</script>';
sub_filter_once on;



We are using the substitute directive to replace the <head> of the result HTML. Notice that we have also used the Accept-Encoding header to ensure that the result from the upstream server will not be returned as a compressed HTML, and hence preventing from the substitute to locate the <head> tag.



Dynamic JavaScript Code Injection


Now we want the code to be dynamic, for example based on a MY_CLIENT_TOKEN header in the request, we will decide on the result injection code. If the MY_CLIENT_TOKEN header is valid, we will add the following script:



console.log('Welcome to my site. We will enable everything for you')



But if the MY_CLIENT_TOKEN header is invalid, we will inject the following JavaScript



console.log('Nothing is enabled for you yet')


To inject the JavaScript, we will use an NGINX auth request that will be sent to our own microservice. The auth microservice will generate a header including the dynamic JavaScript code. Let start by adding the auth upstream under the http section in the NGINX configuration.


nginx.conf (partial view)

upstream auth {
server my-auth-service;
keepalive 1000;
keepalive_requests 1000000;
keepalive_timeout 300s;
}



Next, we update the location to use auth request, and then get the script header from the auth response to be used as the replacement:



location /auth {
proxy_pass http://auth;
}

location / {
auth_request /auth;
auth_request_set $script $sent_http_script;

proxy_set_header Accept-Encoding "";
sub_filter '<head>' '<head><script>${script}</script>';
sub_filter_once on;

proxy_set_header Accept-Encoding "";
proxy_set_header Host $http_host;
proxy_pass http://backend;
}



Long Dynamic JavaScript Code Injection


So our dynamic injection works great, but at some point, we might have a very big java script code injection, and we get to a problem, as the maximum valid HTTP header size is 10KB.

To overcome this issue, we inject a script that loads our javascript. To make sure that the script is running as the first JavaScript on the HTML page, we can use the async=false attribute.

The NGINX configuration to handle this is the same as before, BUT instead of returning our actual JavaScript from the auth service, we return the following JavaScript:



var s = document.createElement('script')
s.src = '/auth/get-actual-script'
s.type = 'text/javascript'
s.async = false
document.getElementsByTagName('head')[0].appendChild(s)



We can dynamically decide on our auth service which script source to return. In the example above it is /auth/get-actual-script, and we can set this dynamically, for example according to the request headers.



Final Note


In this post we have reviewed method to inject JavaScript to an HTML page using NGINX as a reverse proxy. 

The static injection will be usually used for small modifications in the HTML page, when you cannot update the original backend server.

The dynamic injection will be usually used  for an additional logic before that backend, for example to implement another authentication layer.

The large dynamic will be usually used when a dynamic injection is used, and the JavaScript grow large, for example due to use of babel to support old browsers.