Blog archive

AWS Managed Prefix Lists for Reusable IP Allowlists

Create reusable IP allowlists with AWS customer-managed prefix lists and apply them consistently across security groups and access controls.

Published · Republished on Medium

AWSSecurityPci Dss ComplianceAws Security Group

Screenshot from AWS: Enhancing AWS Security with Customer Managed Prefix Lists for Dynamic IP Whitelisting and Access Control

Customer-managed prefix lists solve a specific problem: the same trusted CIDR ranges need to be reused across several AWS resources. Instead of copying those CIDRs into every security group or route table, one prefix list becomes the controlled source of truth.

They do not turn a domain name into a durable network identity. DNS answers can change, return different addresses by location, or point to shared infrastructure. For SaaS or partner access, consume an IP range feed that the provider explicitly publishes and supports. Do not build a security allowlist by periodically resolving an arbitrary hostname.

When a prefix list is a good fit

  • corporate office or VPN egress CIDRs used by several applications
  • partner ranges supplied through an authenticated, documented feed
  • shared private-network ranges referenced by route tables
  • centrally governed ranges shared with other AWS accounts

AWS supports customer-managed prefix-list references in resources including VPC security groups, subnet route tables, transit gateway route tables, and AWS Network Firewall rule groups. A security group can use a prefix list as the source of an inbound rule or the destination of an outbound rule.

Create the prefix list

Choose MaxEntries deliberately. The maximum size affects quotas in resources that reference the list, even when the list currently contains fewer entries.

bash

aws ec2 create-managed-prefix-list \
  --prefix-list-name "trusted-partner-egress" \
  --address-family "IPv4" \
  --max-entries 20 \
  --entries \
    Cidr=192.0.2.10/32,Description=partner-primary \
    Cidr=198.51.100.0/28,Description=partner-secondary

The example ranges are documentation-only addresses. Replace them with ranges whose ownership and purpose you have verified.

Reference it from a security group

bash

aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --ip-permissions \
    'IpProtocol=tcp,FromPort=443,ToPort=443,PrefixListIds=[{PrefixListId=pl-0123456789abcdef0,Description="Trusted partner HTTPS"}]'

The rule follows subsequent prefix-list versions. Adding a CIDR therefore expands access everywhere the list is referenced; removing one can immediately interrupt legitimate traffic. Treat updates as controlled network-policy changes.

Reconcile entries safely

An updater should compare desired and current entries, then submit only the additions and removals. It must use the current prefix-list version because AWS applies optimistic concurrency control to modifications.

This Lambda example expects the desired IPv4 CIDRs in a JSON environment variable such as:

json

["192.0.2.10/32", "198.51.100.0/28"]

python

import ipaddress
import json
import os

import boto3


ec2 = boto3.client("ec2")


def get_all_entries(prefix_list_id):
    entries = []
    next_token = None

    while True:
        request = {"PrefixListId": prefix_list_id, "MaxResults": 100}
        if next_token:
            request["NextToken"] = next_token

        response = ec2.get_managed_prefix_list_entries(**request)
        entries.extend(response["Entries"])
        next_token = response.get("NextToken")
        if not next_token:
            return entries


def normalize_ipv4_cidrs(values):
    return {
        str(ipaddress.ip_network(value, strict=True))
        for value in values
        if ipaddress.ip_network(value, strict=True).version == 4
    }


def lambda_handler(event, context):
    prefix_list_id = os.environ["PREFIX_LIST_ID"]
    desired = normalize_ipv4_cidrs(json.loads(os.environ["DESIRED_CIDRS_JSON"]))

    prefix_list = ec2.describe_managed_prefix_lists(
        PrefixListIds=[prefix_list_id]
    )["PrefixLists"][0]
    current = {
        entry["Cidr"] for entry in get_all_entries(prefix_list_id)
    }

    add = sorted(desired - current)
    remove = sorted(current - desired)

    if not add and not remove:
        return {"changed": False, "version": prefix_list["Version"]}

    if len(desired) > prefix_list["MaxEntries"]:
        raise ValueError("Desired CIDRs exceed prefix-list MaxEntries")

    request = {
        "PrefixListId": prefix_list_id,
        "CurrentVersion": prefix_list["Version"],
    }
    if add:
        request["AddEntries"] = [
            {"Cidr": cidr, "Description": "managed-source"} for cidr in add
        ]
    if remove:
        request["RemoveEntries"] = [{"Cidr": cidr} for cidr in remove]

    result = ec2.modify_managed_prefix_list(**request)["PrefixList"]
    return {
        "changed": True,
        "added": add,
        "removed": remove,
        "version": result["Version"],
        "state": result["State"],
    }

This is a reconciliation example, not a complete ingestion pipeline. Production automation still needs an authenticated source, schema validation, retry handling for concurrent-version conflicts, change approval where required, CloudWatch alarms, and a maximum-change guard. A feed returning an empty or unexpectedly large set should fail closed instead of removing every trusted range.

Validation and rollback

After an update, wait for the prefix list to reach modify-complete, inspect its entries, and test both an allowed and a denied source.

bash

aws ec2 describe-managed-prefix-lists \
  --prefix-list-ids pl-0123456789abcdef0

aws ec2 get-managed-prefix-list-entries \
  --prefix-list-id pl-0123456789abcdef0

AWS stores prefix-list versions. If a bad update is applied, restore a known previous version after confirming that it contains the intended ranges:

bash

aws ec2 restore-managed-prefix-list-version \
  --prefix-list-id pl-0123456789abcdef0 \
  --current-version 7 \
  --previous-version 2

Operational guardrails

  • Grant the updater permission only for the intended prefix list.
  • Store operational configuration such as the prefix-list ID outside the code.
  • Log the old and new CIDR sets without logging credentials or confidential feed tokens.
  • Alarm on failed updates and lists stuck outside modify-complete.
  • Review prefix-list and security-group quota impact before raising MaxEntries.
  • Prefer immutable provider feeds over DNS resolution.
  • Use VPC IPAM prefix-list automation when the desired ranges already come from managed IPAM resources.

References