Given I have restricted IP/SecurityGroup access LoadBalancer (or single EC2) When I try to configure AWS CloudFront CDN to access it Then I cannot as CloudFront don’t have security groups

So there is no way to add CloudFront security group to Security Group of a EC2 instance (or LoadBalancer).

This makes sense. CDN is public distribution system (yes there are cases where this is not true but this is outside the scope of the article). If you use CDN it’s somewhat expected that your EC2 instance / Load Balancer is 0.0.0.0 accessible for port 80 or 443

So if you are in a situation you have hidden EC2 instance / LB but the assets (JS,CSS) can be public, the only way to enable the CloudFront CDN access to your server is to add IP addresses to your instance/LB Security Group

Now if that sound like crazy idea here is source:

  • https://forums.aws.amazon.com/thread.jspa?threadID=218019
  • https://www.youtube.com/watch?v=gUAuhdtHacI

So one solution is to configure AWS Lambda scheduled periodically by AWS CloudWatch to pull list of AWS IPs (from here) and add them to EC2/LB SecurityGroup

in Reddit discussion it was pointed out that this is “not secure”. Yes it’s not it depends how much you want your server hidden and how you configure your CloudFront distribution.

You need to be sure your CF CDN is pointing origin only to assets folder (/assets or /js or /css) and not entire / as then the attacker can call your website via CDN

If you want to create super secret government project that not even CSS can get away to public, then yes this is not a solution for you.

But this solution (I’m using) is just for a staging server of a public service. I just want to hide the staging server so that it’s not accessed by real users sending me emails “my password is not working”, yet I want to keep the CloudFront configuration so that I can test similar production setup. I reccomend to read the Reddit discussion

Think about business / security aspects before you copy paste

There is already good blog on how to do this:

https://spin.atomicobject.com/2016/03/01/aws-cloudfront-security-group-lambda/

…I recommend to follow it but I’ve found that the Python script needed some alteration to be working:

from __future__ import print_function

import json, urllib2, boto3


def lambda_handler(event, context):
    response = urllib2.urlopen('https://ip-ranges.amazonaws.com/ip-ranges.json')
    json_data = json.loads(response.read())
    new_ip_ranges = [ x['ip_prefix'] for x in json_data['prefixes'] if x['service'] == 'CLOUDFRONT' ]
    #print(new_ip_ranges)

    ec2 = boto3.resource('ec2')
    security_group = ec2.SecurityGroup('sg-6rrrrr10')
    current_ips = security_group.ip_permissions
    if len(current_ips) == 0:
        current_ip_ranges = []
    else:
        current_ip_ranges = [ x['cidrip'] for x in current_ips[0]['ipranges'] ]
   
    print(current_ip_ranges)

    params_dict = {
        u'PrefixListIds': [],
        u'FromPort': 80,
        u'IpRanges': [],
        u'ToPort': 443,
        u'IpProtocol': 'tcp',
        u'UserIdGroupPairs': []
    }

    authorize_dict = params_dict.copy()
    for ip in new_ip_ranges:
        if ip not in current_ip_ranges:
            authorize_dict['IpRanges'].append({u'CidrIp': ip})

    revoke_dict = params_dict.copy()
    for ip in current_ip_ranges:
        if ip not in new_ip_ranges:
            revoke_dict['IpRanges'].append({u'CidrIp': ip})

    print("the following new ip addresses will be added:")
    print(authorize_dict['IpRanges'])

    print("the following new ip addresses will be removed:")
    print(revoke_dict['IpRanges'])

    security_group.revoke_ingress(IpPermissions=[revoke_dict])

    security_group.authorize_ingress(IpPermissions=[authorize_dict])

    return {'authorized': authorize_dict, 'revoked': revoke_dict}

reference for changes: https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#instance

I don’t know Python, so if you know how to write this better please submit PR, I’ll be happy to merge it in.

Entire flow

again this is based entirely on JUSTIN KULESZA’s blog (all credits to him) I just want to have mirror T.I.L. here in case if the website ever dissapears or something.

Note: make sure you are configuring everything in one region!

step 1 create custom security grop

Create new security grup where the Lambda will be sending the IPs. Make sure no other Inbound rules are there as we will be deleting them with the script.

Let say your security group is sg-6rrrrr10. For rest of the blog I’ll be referencing it.

Then assign this security group. If you using just EC2 instance and no LoadBalancer, assign the SG to EC2 instance (If you use ElasticBeanstalk there is a option in instances configuration that will repricate to newly added instances). If you using LoadBalancer assign the security group to LoadBalancer

Load balancer example:

step 2 create lambda

AWS Lambda > New Function > Blank Blueprint >

configure lamda

notice that cloudfrant is trigering this every 1 hour

Inside Configure Function paste the Python script pasted above, choose ENV Python 2.7 and create (and then assign to lambda) a security policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeNetworkAcls"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:RevokeSecurityGroupIngress"
            ],
            "Resource": "arn:aws:ec2:eu-west-1:*:security-group/sg-6rrrrr10"
        }
    ]
}

note that Resource ARN is pointing to region eu-west-1 change that to your need

That should be it. If something is not woring check if you by accident not restricting EC2 security groups to limit access instead of LB security group.

Aknowlegement

All credits to Justin Kulesza.