Automating AWS DNS Firewall Domain List Updates Using S3, Lambda, and CLI

Esteban - Nov 4 - - Dev Community

In a previous article, I outlined a ClickOps process for manually creating a DNS Firewall domain list on AWS [link]. While this manual approach is straightforward, automating updates to your DNS list can streamline management and enhance security by keeping your firewall rules current without requiring constant manual intervention. In this article, I’ll provide a step-by-step guide to automating DNS Firewall domain list updates using AWS S3, Lambda, and CLI methods, offering a more efficient way to maintain DNS protections dynamically and at scale.

Benefits of Automation with AWS DNS Firewall

  • Efficiency: Automating DNS list updates saves time and reduces human error.
  • Scalability: Easily manage large lists of blocked domains across multiple VPCs.
  • Rapid Response: Automatically incorporate threat intelligence and updates in real time, improving security posture.

Automation Methods

Here are two ways to automate your AWS DNS Firewall updates:

Method 1: Automate Updates with AWS S3 and Lambda

This approach enables domain list updates whenever a file is uploaded to an S3 bucket.

Image description

Prerequisites

  • Prepare the text file, compile the file containing all the domains you want to block or allow.
  • Create a S3 Bucket.

Step 1 — Create the S3 Bucket and Upload the File:

Upload a file (e.g., blocked_dns_list.txt) containing the domains you want to block to the designated S3 bucket.

Step 2— Create the Lambda Function:

  • Go to the Lambda Console and click “Create Function”
  • Enter a Name, and choose as the Runtime “Python 3.10”
  • Click on “Create Function”.

Image description

Use the following Lambda function code to automate updates when a file is uploaded to S3:

import boto3
import json

def get_firewall_domain_list_id(r53resolver_client, list_name):
    try:
        paginator = r53resolver_client.get_paginator('list_firewall_domain_lists')
        for page in paginator.paginate():
            for domain_list in page['FirewallDomainLists']:
                if domain_list['Name'] == list_name:
                    return domain_list['Id']
        raise ValueError(f"Firewall domain list '{list_name}' not found")
    except Exception as e:
        raise Exception(f"Error getting firewall domain list ID: {str(e)}")

def lambda_handler(event, context):
    try:
        BUCKET_NAME = "S3-BUCKET-NAME"
        DOMAIN_LIST_NAME = "DOMAIN_LIST_NAME"

        key = event['Records'][0]['s3']['object']['key']
        source_bucket = event['Records'][0]['s3']['bucket']['name']
        if source_bucket != BUCKET_NAME:
            raise ValueError(f"Unexpected bucket: {source_bucket}")

        s3_client = boto3.client('s3')
        r53resolver_client = boto3.client('route53resolver')

        firewall_domain_list_id = get_firewall_domain_list_id(r53resolver_client, DOMAIN_LIST_NAME)

        response = s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
        file_content = response['Body'].read().decode('utf-8')
        domains = [domain.strip() for domain in file_content.splitlines() if domain.strip()]

        response = r53resolver_client.update_firewall_domains(
            FirewallDomainListId=firewall_domain_list_id,
            Operation='REPLACE',
            Domains=domains
        )

        print(f"Successfully updated DNS Firewall Domain List with {len(domains)} domains")
        return {
            'statusCode': 200,
            'body': json.dumps({
                'message': 'Successfully updated DNS Firewall Domain List',
                'domainsUpdated': len(domains)
            })
        }

    except Exception as e:
        error_message = str(e)
        print(f"Error: {error_message}")
        return {
            'statusCode': 500,
            'body': json.dumps({
                'error': 'Error updating DNS Firewall Domain List',
                'message': error_message
            })
        }
Enter fullscreen mode Exit fullscreen mode

Step 3— Configure IAM Permissions for the Lambda Function:

S3 Permissions:

To ensure the Lambda function can access the S3 bucket and update the DNS Firewall domain list, assign it the following IAM permissions:

  • s3:GetObject — This permission allows the Lambda function to read the content of the uploaded file in the S3 bucket, enabling it to retrieve the list of domains.
  • s3:ListBucket — While not strictly required for reading the file, this permission can be useful for verifying the existence of objects within the bucket, which aids in error handling and improves function reliability.

Adding these permissions to your Lambda function’s execution role ensures it can effectively retrieve and process domain lists from S3.


{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::my-threat-intel-bucket",
                "arn:aws:s3:::my-threat-intel-bucket/*"
            ]
        }
    ]
}

Enter fullscreen mode Exit fullscreen mode

Route 53 Resolver Permissions

In addition to the S3 permissions, the Lambda function also interacts with Route 53 Resolver, necessitating the following permissions:

  • route53resolver:ListFirewallDomainLists — This permission allows the Lambda function to list existing firewall domain lists in Route 53, which is necessary for retrieving the ID of the domain list to update.

  • route53resolver:UpdateFirewallDomains — This permission is essential for allowing the Lambda function to modify the contents of the specified firewall domain list, enabling the addition or replacement of domains.


 {
      "Effect": "Allow",
      "Action": [
                "route53resolver:ListFirewallDomainLists",
                "route53resolver:UpdateFirewallDomains"
            ],
            "Resource": "*"
 }
Enter fullscreen mode Exit fullscreen mode

Step 4— Set Up S3 Trigger for Lambda:

  • Configure S3 to invoke this Lambda function whenever a file is uploaded to your specified bucket. This trigger ensures that your DNS firewall domain list is automatically updated with each upload.
  • Go to your S3 Bucket and click on “Properties”
  • Create an Event Notification
  • Enter a name, and at the Event Type choose “All object create event”

Image description

  • At “Destinations” choose Lambda Function, and select the name of function.

Image description


Method 2: Automate with AWS CLI

For users who prefer command-line automation, AWS CLI offers an alternative:


aws route53resolver update-firewall-domains \
    --firewall-domain-list-id "rslvr-fdl-<Your_Domain_List_ID>" \
    --operation ADD \
    --domains "example1.com" "example2.com" "example3.com"
Enter fullscreen mode Exit fullscreen mode

Steps:

  • Replace with your Route 53 Resolver domain list ID.
  • List your domains to block as arguments under the --domains parameter.
  • Run the command in your CLI to quickly add or remove domains as needed.

Additional Tips

  • Automation Frequency: Consider setting a schedule to update your lists based on threat intelligence feeds.
  • Logging and Monitoring: Enable CloudWatch logging for Lambda functions to track updates or errors.

Conclusion

Automating DNS Firewall domain list updates can significantly improve your AWS environment’s security while reducing operational overhead. By following these methods, you’ll ensure that your firewall rules are up-to-date, scalable, and responsive to the latest threats.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .