Nerdy Drunk

Drunk on technology

User Tools

Site Tools


aws:lambda:route53_dynamic_dns

Route 53 Dynamic DNS Lambda Function

This Lambda function will get the public IP of an EC2 instance and update a corresponding DNS record in Route 53. It can be used for systems that need to be publicly available but might not always need to be running such as a bastion host.

Requirements

The following items are needed for this Lambda function to … function correctly.

  • Route 53 Hosted Zone for the desired domain name
  • The EC2 instance needs to have the following tags
    • Name used for informational purposes in Lambda logs
    • DNSName with the FQDN that will be updated for the instance
    • HostedZoneId for the domain name's Route53 Hosted Zone
  • Lambda role
  • CloudWatch Event to trigger Lambda function

Deployment Rundown

  1. Create Route 53 Hosted Zone for desired domain name
  2. Create IAM policy and role for Lambda function
  3. Deploy Lambda function and assign IAM role
  4. Verify that EC2 instance has the needed tags and values
  5. Create CloudWatch Event
  6. Stop and start EC2 instance and verify that Lambda function works as expected

Known Issues / Limitations

Below is a list of known issues and limitations with this implementation.

  • This works for me and has only been tested in my environment
  • Minimal error checking is being used
  • Installation, deployment, and configuration are done manually
  • Lambda function is dependent on using an AWS default VPC
  • Does not account for instances with multiple ENIs
  • Does not account for regions where lots of instances are started (auto scaling groups for example)

Diagram


https://nerdydrunk.info/_media/images:svg:route_53_dynamic_dns_lambda_function.svg

  1. Instance starts and triggers CloudWatch Event
  2. CloudWatch Event launches Lambda function and passes instance ID as input
  3. Lambda function used describe instances to gather tags and public IP from Instance information
  4. Lambda function updates DNS record based on the instance tag values

Lambda Function Operation

  • Checks to see if instance ID was provided in the input
  • Gathers required tag values from instance
  • Checks that all required tag values were gathered
    • Exits if any of the required tags are missing
  • In the provided Hosted Zone it updates the DNS record for the provided FQDN with the instance's public IP

Lambda Function Role Permissions

The following items in IAM policy need to be updated to reflect your environment and deployment.

  • Hosted zone ids Z1111111111111 and Z2222222222222
  • Region us-east-1
  • AWS account 123456789012
  • Lambda function name dynamic-dns
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "GetInstanceInformation",
            "Effect": "Allow",
            "Action": "ec2:DescribeInstances",
            "Resource": "*"
        },
        {
            "Sid": "UpdateDNSReocrd",
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": [
                "arn:aws:route53:::hostedzone/Z1111111111111",
                "arn:aws:route53:::hostedzone/Z2222222222222"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:us-east-1:123456789012:/aws/lambda/dynamic-dns"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/dynamic-dns:*"
            ]
        }
    ]
}

CloudWatch Event

A CloudWatch Event is used to run the Lambda function anytime any EC2 instance starts. The CloudWatch Event will need to pass “Matches events” as input to the Lambda function. This is used to get the instance ID.

Event pattern:

{
  "source": [
    "aws.ec2"
  ],
  "detail-type": [
    "EC2 Instance State-change Notification"
  ],
  "detail": {
    "state": [
      "running"
    ]
  }
}

Lambda Function Code

r53dyndns.py
import boto3
 
def lambda_handler(event, context):
    if 'detail' in event:
        if 'instance-id' in event['detail']:
            instance_id = event['detail']['instance-id']
        else:
            return
    else:
        return
 
    client_ec2 = boto3.client('ec2')
    instance_details = client_ec2.describe_instances(InstanceIds=[instance_id])
 
    for tag in instance_details['Reservations'][0]['Instances'][0]['Tags']:
        if tag['Key'] == 'Name':
            tag_name = tag['Value']
        if tag['Key'] == 'DNSName':
            tag_dnsname = tag['Value']
        if tag['Key'] == 'HostedZoneId':
            tag_hostedzoneid = tag['Value']
 
    try:
        print('Found tags: {}, {}, {}'.format(tag_name, tag_dnsname, tag_hostedzoneid))
    except:
        print('Required tags missing.')
        return
 
    client_r53 = boto3.client('route53')
    client_r53.change_resource_record_sets(
        HostedZoneId=tag_hostedzoneid,
        ChangeBatch={
            'Changes': [
                {
                    'Action': 'UPSERT',
                    'ResourceRecordSet': {
                        'Name': tag_dnsname,
                        'Type': 'A',
                        'TTL': 360,
                        'ResourceRecords': [
                            {
                                'Value': instance_details['Reservations'][0]['Instances'][0]['PublicIpAddress']
                            }
                        ]
                    }
                }
            ]
        }
    )
    print('Updated {} for {} to {}'.format(tag_dnsname,instance_id,instance_details['Reservations'][0]['Instances'][0]['PublicIpAddress']))
 
    return
aws/lambda/route53_dynamic_dns.txt · Last modified: 2022/07/21 10:41 by 127.0.0.1