Start EC2 and set DNS in CloudFlare using Lambda

In my previous posts, I showed two ways to set DNS for an EC2 server that does not have an Elastic IP attached to it. The first method used a Python Script to set DNS and the second method used a Lambda Function to set DNS. So, if you had a server running 24×7 you could use the first method to run the script from crontab or Jenkins and if you went with Lambda approach you could use CloudWatch Events to trigger the run.

Background

A few days ago, I decided to see if I could enable some cache or CDN for my website. There are a few services available that one could use to enable CDN and I knew it was not going to be easy to decide which way to go. However, in my case cost was going to be a big factor in deciding what service to choose. In the end I decided to go with CloudFlare as it had a free plan that I could try.

After making this decision I realized that I would have to move my domain over from Route53 to CloudFlare to avail of their free plan. Only for upgraded plans, it is possible to CNAME just a single subdomain. This got me wondering how I would set DNS record for EC2 servers that do not have an EIP. Thankfully CloudFlare has a good set of API services available to take care of this.

So here in this post I show how to put together a Step Function invoking Lambda with a layer to make an HTTPS call to CloudFlare API to set DNS.

Procedure

In some of my earlier posts, I have already described how to start an EC2 and set its DNS, so I am not going to cover a lot of those steps. Instead I will focus on the Lambda Function that we will need to make the API call to CloudFlare.

The Lambda function I wrote uses a Layer for adding the Python package ‘requests’. In case you need help setting it up, see my earlier post to build a layer.

The new function will be invoked via Step Function as I already have set it up as a CloudWatch Event to start everyday at a predetermined time.

Another important thing to keep in mind is that you would need an API key to authenticate your request with CloudFlare. I do not wish to hard code this key in my Lambda code, therefore I stored it in Systems Manager Parameter Store. I have another post where I used Parameter Store to save database credentials. Please take a look at this post to get an idea of how this is done. This example follows the same method of getting the API key from an encrypted store.

Steps

Create a Lambda Layer for Python package ‘requests’. See this post.

Create a new Lambda Function similar to the previous post. See this post.

Add the correct SSM Paramater Store lookup variable corresponding to your key.

#ssm
ssm_ppath = os.environ['SSM_PARAMETER_PATH']
ssm = boto3.client('ssm')
lookup = ssm_ppath + '/rw'
SSM PAth

In my case the SSM Parameter Store key I have defined is

/cloudflare/api/rw which corresponds to 
{"api": "your api-key", "zone_id": "your zone-id"}

and modify the server_dns method to make CloudFlare API call.

def server_dns(id, config):
zone_id = config[lookup]['zone_id']
api = config[lookup]['api']

iresponse = ec2client.describe_instances(InstanceIds=[id])
for ireservation in iresponse["Reservations"]:
for i in ireservation["Instances"]:
IP = i["PublicIpAddress"]

data = '{"type":"A","name":"server.skbali.com","content":"' +\
IP + '","ttl":1,"proxied":false}'
url = 'https://api.cloudflare.com/client/v4/zones/' +\
zone_id + '/dns_records/identifier'

headers = {}
headers['X-Auth-Key'] = api
headers['X-Auth-Email'] = 'your email here'
headers['Content-Type'] = 'application/json'

resp = requests.put(url=url, headers=headers, data=data)
print(resp.json())

return resp.status_code

Note:

  • The data passed in the HTTP request has the TTL setting, the A record name in my case and the proxy setting for the record in CloudFlare.
  • In the code above where the ‘url‘ is defined the identifier should be replaced by the identifier corresponding to you record at CloudFlare. If you do not know this identifier, you can look it up using another API call. To keep the function simple I did not add a second API call. However, you could add the lookup identifier call before you form this ‘url‘.
  • You could also save the identifier used in the API call, in the Parameter Store if you wish.
  • In this post, the PUT call is used because the record already exists in CloudFlare.

The method to load the configuration is same as in this post.

Now that your function is ready, navigate over to Step Function and edit your State Machine to call the correct Lambda Function. Refer to this post.

To test it out you can invoke your Step Function from the console or modify your CloudWatch Event to schedule an immediate run of the Lambdas. Navigate to your CloudFlare account and see if the DNS is set correctly. You could also verify the same using ‘dig’ or ‘nslookup’ tools.

Further reading and Improvements:

If you still have some questions or would like to see the full source code let me know in the comment sections.

Leave a Reply