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.
The following items are needed for this Lambda function to … function correctly.
Name
used for informational purposes in Lambda logsDNSName
with the FQDN that will be updated for the instanceHostedZoneId
for the domain name's Route53 Hosted ZoneBelow is a list of known issues and limitations with this implementation.
https://nerdydrunk.info/_media/images:svg:route_53_dynamic_dns_lambda_function.svg
The following items in IAM policy need to be updated to reflect your environment and deployment.
Z1111111111111
and Z2222222222222
us-east-1
123456789012
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:*" ] } ] }
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" ] } }
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