# Simple Dynamic DNS Client for AWS Route53

This Dynamic DNS client for AWS Route53 hosted DNS utilizes a lambda function to update the DNS records for a single hosted zone. 

The script is designed in fashion that it will only call the AWS Lambda function to execute in the event that your public ip address has changed since the last time the script is ran. This limits the lambda function execution to only when needed, which saves money, as you will only be charged for when the function is actually executed.

If multiple hosted zone updates are need, you can simply copy the "On Prem Client" files to multiple directories, like:

/opt/DDNS/domain1
/opt/DDNS/domain2
/opt/DDNS/domain3

Edit the setting.conf file located in each instance of the script that you placed in the different subdirectories. And create multiple cron table entries pointing to the different iteration of the script. This will then allow you to have multiple different hosted zones being updated, and will use the same AWS Lambda function (Only 1 lambda function deployed in AWS) as the scripts will all be calling the same lambda function to push updates. You'll simply run the `aws_ddns_update.py` script from each sub-directory to update each respective hosted zone.

## Requirements

#### AWS

- A DNS Zone (domain) hosted in AWS Route53
- Domain is pointed to use AWS's Nameservers (If your domain is registered with AWS also, this would be default).
- Domain and sub-domain "A" records defined within AWS Hosted Zone that will be updated via these scripts.
- An AWS IAM Group for the Service Account
- `AWSLambdaRole` AWS Managed role attached to the AWS IAM Group
- AWS IAM user to use as a AWS-CLI service account
- Auth Token generated within AWS IAM for the serivces user account AWS IAM user (Noting the Key and Secret that is generated, you will need it when setting up AWS-CLI on th on-prem host).
- Service account AWS IAM user added to the AWS IAM Group for service account.

#### On-Prem Host
- An on premises host to run the "On Prem" script from that has internet access. (I use a small dedicated Ubuntu container located on my proxmox cluster).
- Python installed
- Python Modules installed
-- requests
-- boto3
-- os
-- json
-- datetime
-- configparser
- AWS-CLI installed on the "On-Prem" host 
- AWS-CLI auth setup to use the Auth-Token Key and Secret for service account as the default profile.

## Installation / Setup

### AWS Lambda Function

There are two items associated with the lambda function here. there is a zip file named `Function.zip`. This is the archived AWS Lambda Function code we will need to put into place within AWS Lambda.

Note: You can unzip the archive as normal and inspect it's contents for nefarious code, should you choose to. The function itself is called `function.py` within the archive, and the rest of the files and folders are supporting code for the function such as python modules that set up the entire lambda function environment when it's ran.

### Creation of The Lambda Function

- Within AWS Lambda, create a new function.
-- Select `Author from Scratch`
-- Define the functions's name (Note: remember this, you'll need it later.)
-- Define the runtime as `Python 3.9`
-- Click `Create Function`
- Along the right-hand side of the page you'll see a `Upload From` drop down box. Select it, and click `.zip file`.
-- Locate and select the included `Function.zip` and upload.
- Now go to `AWS IAM` and along the left hand menu, select `Roles`.
-- WIthin the list given, look for the role that begins with the name you game your lamda function and click on it.
-- Along the right-hand side you'll see an `Add Permissions` button. Select it, and then click on `Create Inline Policy`. We will need to give the lambda function explicit permission to interact with Route53's domains.
-- Along the top right corner of the page, click `json`
-- Paste this json code: `
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "route53:ChangeResourceRecordSets",
      "Resource": "arn:aws:route53:::hostedzone/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:ListResourceRecordSets"
      ],
      "Resource": "*"
    }
  ]
}
`
-- At the bottom right-hand corner of the page, click `Next`
-- Define a name for the inline policy.
-- At the bottom right-hand corner of the page select `Create Policy`.

The permissions defined within the above inline policy gives the lambda function the explicit permission to modify existing recordsets of any hosted zone within associated AWS account.

The lambda function setup is now complete.

### On-Prem Host

- On the on-prem host, copy the entire folder named `aws_ddns` and it's contents that is located within the `On Prem Client Agent` folder to your on premises machine.
- Within the `AWS_DDNS` folder there is a `settings.conf` file. Edit this file and define the following parameters.
-- `function_name`: Lambda Function name defined earlier in AWS
-- `HostedZoneId`: The Zone ID for the hosted zone. (Found in the Route53 zone details)
-- `TTL`: The Time to Live setting (In seconds) to set the "A" records to when updating. Set this to the same schedule that you'll be running the script.
-- `domain_list`: A comma separated list of the domain/sub-domains records you wish to update (No Spaces between domain names. (EXAMPLE: example.com,domain1.example.com,domain2.example.com
-- Edit the cron table and add the follwing  entry:
 `*/5 * * * * /usr/bin/python3 {PathToFiles}/aws_ddns/aws_ddns_update.py >> /opt/aws_ddns/dns_updates.log 2>&1`
 -- Edit the cron schedule portion of the entry to match the TTL time you listed in settings.conf (The */5 of the cron entry denotes 5 minutes or 300 seconds. 
 -- Edit the `{PathToFiles}` portion of the entry to match the location where you placed the files. Such as `/root`. and save the cron table.

# Usage

Right now the script should be ran auto-magically at the interval you set in the cron table. but the script can be called at any time manually also.

# Operation

## First run:
- The on-prem script will check the public IP address associated with the location of the on-prem host.
- It then will invoke the lambda function to update the domain's IP address and the TTL , of the hosted zone that matches the HostZoneID defined also within settings.conf.
- It then will write the public IP address found to a file named `last_ip` in the same directory as the script.
- if the first run was ran via cron, all the output will be written to a logfile named: `dns_updates.log` in the same directory as the script.
- If the script is manually ran, the output is only to the terminal.

## Subsequent runs:
- The on-prem script will check the public IP address associated with the location of the on-prem host.
- If the file `last_ip` does not exist, or the public IP found does not match the entry within `last_ip`:
-- It then will invoke the lambda function to update the domain's IP address and the TTL , of the hosted zone that matches the HostZoneID defined also within settings.conf.
-- It then will write/update the public IP address found to a file named `last_ip` in the same directory as the script.
- If the public IP found matches the entry within `last_ip`
-- The lambda function will NOT be invoked, and domains will not be updated.

## Logging
- if the script run was ran via cron, all the output will be written to a logfile named: `dns_updates.log` in the same directory as the script.
- If the script is manually ran, the output is only to the terminal.

## AWS Charges
The costs associated with the lambda function deployed should be negligible as it will only be invoked when there is a change of IP detected, or if the `last_ip` file is deleted, essentially making the script believe it's the first time it's ran. So the cost should be free depending on the schedule that your ISP changes your IP address since the lambda function is only called "If" there is a change detected. With Lambda being within the "Free Tier" if  the total invocations are below 1,000,000/Mo and the total runtime of those invocations are below 400,000 Seconds/Mo total, there should be any charges. The lambda function in most cases I've encountered take about 2 seconds to update about 8 domain records.

