Setting up Security Groups for AWS CloudFront to an EC2 Instance

This guide will show you how to secure your CloudFront connection to your instances by only allowing the two to talk to each other, restricting the EC2 instances from the outside world. This could also apply to other resources like a Load Balancer

This guide is based off a solution provided by the AWS team. You can find their post here. Their solution is somewhat outdated and no longer working for current AWS. This is an extension that is working as of the time of this post.

We will be using Lambda and SNS in this guide.

Step 1: Setup Policies and Roles for Lambda

Before we begin, we need to setup some permissions for Lambda to work.

Start by navigating to IAM in the AWS Console.

Add Policy

First go to Policies, and click the Create policy button.

Go to the JSON tab, and add the following in below.

NOTE: You will need to edit the JSON with your account number. Your region may also be different.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ec2:RevokeSecurityGroupIngress",
                "ec2:AuthorizeSecurityGroupIngress",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:ec2:us-east-1:<ACCOUNT-ID>:security-group/*",
                "arn:aws:logs:*:<ACCOUNT-ID>:log-group:*:log-stream:*"
            ]
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "ec2:DescribeSecurityGroups",
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup"
            ],
            "Resource": "arn:aws:logs:*:<ACCOUNT-ID>:log-group:*"
        },
        {
            "Sid": "VisualEditor3",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:<ACCOUNT-ID>:*"
        }
    ]
}

Select the Review policy button. Give it a name, something easily findable and memorable. I used Lambda_UpdateCloudFrontPolicy.

And hit Create policy.

Add Role

Navigate to IAM and select the Roles section. Select Create role.

Select Lambda in the AWS service Use cases and hit Next: Permissions

Search for your created policy and select it in the tick box. In my case it was Lambda_UpdateCloudFrontPolicy.

Click Next: Tags, and add any tags you want. Then finally click Next: Review.

Give it a memorable role name, I used Lambda_UpdateCloudFrontRole. Finally hit Create role.

Step 2: Create Security Groups

Now we need to setup some security groups.

We need to create four separate groups with the following names:

cloudfront_g1
cloudfront_g2
cloudfront_r1
cloudfront_r2

These must contain the following tags:

AutoUpdate: true
Protocol: http
Name: <name_from_above>

Navigate to security groups in EC2 and click Create security group.

Add one of the above names as a name. Then remove all inbound and outbound rules from the rules sections.

Add the tags previously metioned, changing the name to the name of the security group you are creating.

Repeat this for all four security groups.

Step 3: Setup Lambda Function

Navigate to AWS Lambda and hit Create function.

Give your function a name, I used UpdateCloudFrontSecurityGroups.

Select the runtime of python 3.8 (Or latest supported)

Choose an execution role, and select Use an existing role. In the in the drop down, select the role you made previously. In my case it is Lambda_UpdateCloudFrontRole.

Continue to the code section, and copy in the Python code into the function. You can find it on my GitHub here:

https://github.com/BlueScionic/Resources/blob/master/AWSCloudFront/UpdateCloudFrontSecurityGroups.py

Or in raw format here:

https://raw.githubusercontent.com/BlueScionic/Resources/master/AWSCloudFront/UpdateCloudFrontSecurityGroups.py

Before we continue, we have to test it first.

Click the test button and add the below to the test.

{
  "Records": [
    {
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:EXAMPLE",
      "EventSource": "aws:sns",
      "Sns": {
        "SignatureVersion": "1",
        "Timestamp": "1970-01-01T00:00:00.000Z",
        "Signature": "EXAMPLE",
        "SigningCertUrl": "EXAMPLE",
        "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
        "Message": "{\"create-time\": \"yyyy-mm-ddThh:mm:ss+00:00\", \"synctoken\": \"0123456789\", \"md5\": \"7fd59f5c7f5cf643036cbd4443ad3e4b\", \"url\": \"https://ip-ranges.amazonaws.com/ip-ranges.json\"}",
        "Type": "Notification",
        "UnsubscribeUrl": "EXAMPLE",
        "TopicArn": "arn:aws:sns:EXAMPLE",
        "Subject": "TestInvoke"
      }
    }
  ]
}

Give it a name, I used SNSTest, and hit Save.

Now click test again, using the SNSTest, and you should receive an error similar to below with a MD5 Mismatch.

Response:
{
  "errorMessage": "MD5 Mismatch: got 5302033d9f139fc8f92b87cd5b957fbd expected 7fd59f5c7f5cf643036cbd4443ad3e4b",
  "errorType": "Exception",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 50, in lambda_handler\n    ip_ranges = json.loads(get_ip_groups_json(message['url'], message['md5']))\n",
    "  File \"/var/task/lambda_function.py\", line 81, in get_ip_groups_json\n    raise Exception('MD5 Mismatch: got ' + hash + ' expected ' + expected_hash)\n"
  ]
}

Copy the string before the expected. In my example above, the string was: “5302033d9f139fc8f92b87cd5b957fbd”. Your will differ.

Now select the drop down of your test and hit configure text event.

Edit the current test you are using, and change the 7fd59f5c7f5cf643036cbd4443ad3e4b in the message line to your string you have copied just before. In my case it was 5302033d9f139fc8f92b87cd5b957fbd.

Save this and test again, you will most likely get a timeout, as it takes more than the default time to execute.

Scroll down to basic settings and hit Edit.

Edit it so it has more timeout, I gave mine 30 seconds

If you run again, it should work now, and give you a response similar to below

Response:
[
  "Security Group sg-0a65818a0c224aa78 updated.",
  "Security Group sg-075b976256ab91191 updated.",
  "Security Group sg-00244abc59a3d6a91 updated.",
  "Security Group sg-0dc717073beaeb1fb updated."
]

There will be a log entry every time it runs in the CloudWatch logs.

Step 4: Subscribe to the SNS topic

Once we have finished creating our Lambda script, we need a way to have it trigger every time there is a change or update to the CloudFront distribution nodes. We can do this through an AWS provided SNS topic.

Navigate to AWS SNS and go to subscriptions in the left hand menu. Click Create Subscription.

Make the topic as below, this is the Amazon topic that triggers when IP addresses change.

arn:aws:sns:us-east-1:806199016981:AmazonIpSpaceChanged

Make the Protocol AWS Lambda, and select your previously made Lambda function from the drop down list.

Click Create subscription, and everything should now be ready to go.

Step 5: Finish Security Group Setup

Now if you check your security groups, they should have a list of IP’s in them similar to below.

Now all you have to do is assign them to the resources you are using CloudFront for. This could be your proxy server or a load balancer, etc.

Once you do so, you should have access between CloudFront and your resource.

This guide was originally written as part 7 of a series on setting up your own personal Elasticstack. You can view the other parts here:

  1. Building Your Environment in AWS
  2. Setting up and Installing Elasticsearch
  3. Setting up Kibana
  4. Using a Proxy for Kibana with HAProxy
  5. Enabling Security and Using Password Authentication
  6. Making Kibana Internet Accessible with Cloudfront
  7. Securing Cloudfront with Security Groups
  8. Inserting Data into Elasticsearch with Logstash

Once you are done with all this, you can move onto the final part, Part 8: Using Logstash to Insert Data into Elasticsearch.


Any thoughts, concerns, mistakes? Let me know in the comments or via the Contact page.

2 thoughts on “Setting up Security Groups for AWS CloudFront to an EC2 Instance

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s