3. January 2017 21:13
by Aaron Medacco
2 Comments

Automating Backups of Your Route 53 Hosted Zone DNS Records

3. January 2017 21:13 by Aaron Medacco | 2 Comments

Not too long ago I was editing entries in a Route 53 hosted zone and thought to myself what would happen if the record sets for the zones were lost. A pretty epic disaster would need to occur for you to somehow lose your DNS record sets. Maybe someone accidentally gets rid of a zone believing it to no longer be necessary, or perhaps someone configured IAM permissions incorrectly and a disgruntled employee who notices he has access wreaks havoc on your DNS before he finds the door. Either way, without your DNS, your perfectly designed system architecture might as well be driftwood. Therefore, I created a serverless method for storing backups of all Route 53 hosted zone records in S3, just in case:

Route 53 Backup Diagram

Creating the S3 bucket:

  1. Navigate to S3 in your management console.
  2. Click "Create Bucket".
  3. Enter an appropriate name and select a region.
  4. Click "Create".

Creating an IAM policy for the first Lambda function's role:

  1. Navigate to IAM in your management console.
  2. Select "Policies" in the sidebar.
  3. Click "Create Policy".
  4. Select "Create Your Own Policy".
  5. Enter an appropriate policy name and description.
  6. Paste the following JSON into the policy document:

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "route53:ListResourceRecordSets"
                ],
                "Resource": [
                    "*"
                ]
            },
            {
                "Effect": "Allow",
                "Action": [
                    "s3:PutObject"
                ],
                "Resource": [
                    "Your Bucket ARN"
                ]
            }
        ]
    }
  7. Substitute "Your Bucket ARN" with the ARN for the S3 bucket you created. Make sure you add "/*" after the bucket ARN. For instance, if your bucket ARN was "arn:aws:s3:::furyandzealbrothers", you would use "arn:aws:s3:::furyandzealbrothers/*".
  8. Click "Create Policy".

Creating the IAM role for the first Lambda function:

  1. Select "Roles" in the sidebar.
  2. Click "Create New Role".
  3. Enter an appropriate role name and click "Next Step".
  4. Select "AWS Lambda" within the AWS Service Roles.
  5. Change the filter to "Customer Managed", check the box of the policy you just created, and click "Next Step".
  6. Click "Create Role".

Creating the first Lambda function:

  1. Navigate to Lambda in your management console.
  2. Click "Create a Lambda function".
  3. Select the "Blank Function" blueprint.
  4. Click "Next".
  5. Enter an appropriate function name and description. Select Node.js for the runtime.
  6. Under "Lambda function code", select "Edit code inline" for the Code entry type and paste the following code in the box:

    var AWS = require("aws-sdk");
    
    exports.handler = (event, context, callback) => {
        var route53 = new AWS.Route53();
        var id = event.id;
        var name = event.name;
        var recordParams = { HostedZoneId: id };
        route53.listResourceRecordSets(recordParams, function(err, data){
            if (err) {
                console.log(err, err.stack);
            }
            else {
                console.log(JSON.stringify(data));
                var records = [];
                for (var j = 0; j < data.ResourceRecordSets.length; j++){
                    records.push(data.ResourceRecordSets[j]);
                }
                var zone = { id:id, name:name, records:records };
                uploadBackupToS3(zone);
            }
        });
    };
    
    var uploadBackupToS3 = function(data) {
        var s3 = new AWS.S3();
        var bucket = "Your Bucket Name";
        var timeStamp = Date.now();
        var key = data.name + "_" + data.id.replace(/\//g, '').replace("hostedzone", '') + "_backup_" + timeStamp;
        key = key.replace(/[.]/g, "_");
        var body = JSON.stringify(data);
        var param = { Bucket: bucket, Key: key, Body: body, ContentType: "text/plain", StorageClass: "STANDARD_IA" };
        s3.upload(param, function(err, data) {
            if (err){
                console.log(err, err.stack);
            } else{
                console.log("Route 53 backup successful.")
            }
        });
    };
  7. Substitute "Your Bucket Name" with the name of the bucket you created earlier.
  8. Leave Handler as "index.handler".
  9. Choose to use an existing role and select the IAM role you created earlier.
  10. Leave the other default values and click "Next".
  11. Click "Create function".

Creating an IAM policy for the second Lambda function's role:

  1. Navigate to IAM in your management console.
  2. Select "Policies" in the sidebar.
  3. Click "Create Policy".
  4. Select "Create Your Own Policy".
  5. Enter an appropriate policy name and description.
  6. Paste the following JSON into the policy document:

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "route53:ListHostedZones"
                ],
                "Resource": [
                    "*"
                ]
            },
            {
                "Action": [
                    "lambda:InvokeFunction"
                ],
                "Effect": "Allow",
                "Resource": "Your Lambda Function ARN"
            }
        ]
    }
  7. Substitute "Your Lambda Function ARN" with the ARN of the lambda function you created earlier and click "Create Policy".

Creating the IAM role for the second Lambda function:

  1. Select "Roles" in the sidebar.
  2. Click "Create New Role".
  3. Enter an appropriate role name and click "Next Step".
  4. Select "AWS Lambda" within the AWS Service Roles.
  5. Change the filter to "Customer Managed", check the box of the second policy you just created, and click "Next Step".
  6. Click "Create Role".

Creating the second Lambda function:

  1. Navigate to Lambda in your management console.
  2. Click "Create a Lambda function".
  3. Select the "Blank Function" blueprint.
  4. Under "Configure triggers", click the grey box and select "CloudWatch Events - Schedule".
  5. Enter an appropriate rule name and description.
  6. Select the frequency you'd like Lambda to backup your Route 53 hosted zone records in the Schedule expression input. I chose "rate(30 days)" for my usage.
  7. Check the box to "Enable trigger" and click "Next".
  8. Enter an appropriate function name and description. Select Node.js for the runtime.
  9. Under "Lambda function code", select "Edit code inline" for the Code entry type and paste the following code in the box:

    var AWS = require("aws-sdk");
    
    exports.handler = (event, context, callback) => {
        var route53 = new AWS.Route53();
        var lambda = new AWS.Lambda();
        var params = {};
        route53.listHostedZones(params, function(err, data){
            if (err) {
                console.log(err, err.stack);
            } 
            else {
                for (var i = 0; i < data.HostedZones.length; i++) {
                    var id = data.HostedZones[i].Id;
                    var name = data.HostedZones[i].Name;
                    var payload = { id:id, name:name };
                    var lambdaParams = {
                        FunctionName: "Your Lambda Function Name", 
                        InvocationType: "Event",
                        Payload: JSON.stringify(payload)
                    };
                    lambda.invoke(lambdaParams, function(err, data) {
                        if (err) {
                            console.log(err, err.stack);
                        }
                        else {
                            console.log(data);  
                        }
                    });
                }
            }
        });
    };
  10. Substitute "Your Lambda Function Name" with the name of the first lambda function you created earlier.
  11. Leave Handler as "index.handler".
  12. Choose to use an existing role and select the second IAM role you created earlier.
  13. Leave the other default values and click "Next".
  14. Click "Create function".

Depending on how frequently you schedule the backups, you might also want to configure a lifecycle policy in S3 to archive or delete them after a period of time.

Cheers!

Comments (2) -

this was perfect for someone new to lambda, thanks for sharing it works great.

Thx Aaron.    I ran through the steps and they work great.  I did notice that if you have more than 100 DNS records, the route53.listResourceRecordSets only returns that many. I tried to bump MaxItems up in the first function.. .. but it did not seem to take..

var recordParams = { HostedZoneId: id ,
                         MaxItems: '20000'
    };

Do I have to rebuild the function from scratch?  

Add comment

Copyright © 2016-2017 Aaron Medacco