AWS Managed Prefix Lists

How-to provide managed prefix lists for services that users can consume either manually or through automation using the an SSM Parameter Store hierarchy.

AWS Managed Prefix Lists

Some time ago AWS released a new feature called Customer Managed Prefix Lists.

https://aws.amazon.com/about-aws/whats-new/2020/06/amazon-virtual-private-cloud-customers-use-prefix-lists-simplify-configuration-security-groups-route-tables/

The concept isn't new, they already exist for services like S3 Gateway or DynamoDB Endpoints. You can use the Prefix list ID within your Security Group rules to follow the practice of least privilege when crafting rules.

To allow access to only S3 from my instance, I create a rule as follows instead of just allowing the default outbound any.

Behind the scenes, the Prefix list ID contains a list of CIDR blocks that cover all the IP address ranges for the S3 service in the target region.

With this release we can now create our own Managed Prefix Lists with a few of caveats.

  • Can't change the address family once created
  • Max CIDR entries must be defined on creation and can't be modified
  • When referenced in a Security Group, the value of max is consumed from the rule limit

Why should I care

In my career I've been sad enough to learn the IP addresses of BIND servers or Wiki sites but most people don't retain that sort of thing and just find it tedious. If you are wanting to provide a common way to consume known CIDR ranges they are extremely useful. They can also be tagged with appropriate values to provide more context to the person consuming them.

Creating a structure

We don't want to just blindly start creating these things or else it's going to get messy very quickly!

At my company we are a heavy user of Cloudformation StackSets to provide base configuration across all accounts from Cloudtrail through to setting up SAML Authentication.

Using this new feature, we can provide common managed prefix lists for services that our customers can consume either manually or through automation using the an SSM Parameter Store hierarchy.

In the above structure we will have many service names for all our managed service endpoints.

Querying the following /Cloud/ServiceName/PrefixListId will provide the customer the managed prefix for that service.

The following extract shows example code for our service endpoints deployed through Cloudformation.

  CloudServiceNameCIDR1:
    Type: AWS::SSM::Parameter
    Properties:
      Name: "/Cloud/ServiceName/CIDR1"
      Type: String
      Value: '10.130.1.0/24'
      Description: Cloud ServiceName CIDR1
      AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[0-9]|2[0-9]|3[0-2]))$

  CloudServiceNameCIDR2:
    Type: AWS::SSM::Parameter
    Properties:
      Name: "/Cloud/ServiceName/CIDR2"
      Type: String
      Value: '10.130.2.0/24'
      Description: Cloud ServiceName CIDR2
      AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[0-9]|2[0-9]|3[0-2]))$

  CloudServiceNameCIDR3:
    Type: AWS::SSM::Parameter
    Properties:
      Name: "/Cloud/ServiceName/CIDR3"
      Type: String
      Value: '10.130.3.0/24'
      Description: Cloud ServiceName CIDR3
      AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[0-9]|2[0-9]|3[0-2]))$

  CloudServiceNamePrefixList:
    Type: AWS::EC2::PrefixList
    Properties:
      PrefixListName: !Join ["-", ["Cloud", "ServiceName"]]
      AddressFamily: "IPv4"
      MaxEntries: 3
      Entries:
        - Cidr: !GetAtt "CloudServiceNameCIDR1.Value"
          Description: "Cloud ServiceName CIDR1"
        - Cidr: !GetAtt "CloudServiceNameCIDR2.Value"
          Description: "Cloud ServiceName CIDR2"
        - Cidr: !GetAtt "CloudServiceNameCIDR3.Value"
          Description: "Cloud ServiceName CIDR3"

  CloudServiceNamePrefixListId:
    Type: AWS::SSM::Parameter
    Properties:
      Name: "/Cloud/ServiceName/PrefixListId"
      Type: String
      Value: !GetAtt "CloudServiceNamePrefixList.PrefixListId"
      Description: Cloud ServiceName

  CloudServiceNamePrefixListId:
    Description: Cloud ServiceName
    Value: !GetAtt "CloudServiceNamePrefixList.PrefixListId"
    Export:
      Name: "CloudServiceNamePrefixListId"

Infrastructure as Code

With the above in place, customers can now reference this value within Cloudformation using the AWS::SSM::Parameter Type

  CloudServiceNamePrefixListId:
    Type: "AWS::SSM::Parameter::Value<String>"
    Description: "Retrieve the Managed Prefix from SSM for use in Cloudformation template"
    Default: "/Cloud/ServiceName/PrefixListId"

Or if Terraform is your preference use the aws_ssm_parameter data source

data "aws_ssm_parameter" "CloudServiceNamePrefixListId" {
  name = "/Cloud/ServiceName/PrefixListId"
}

Summary

While not the most exciting release, this feature is very helpful in our environment to provide the following to our customers.

  • A managed prefix that can be used within Security Groups for services available across our hybrid cloud infrastructure
  • A managed prefix that can be consumed easily via automation
  • A managed prefix that can managed and updated by our operations teams via cloudformation at scale

Theres always a catch....

When you hit that max limit of CIDRs per prefix list, you will have to remove and re-created if you need to add additional CIDR values. You can either add a bit of fat initially or just accept a change of the ID value. You will have to audit your Security Groups to see if they are being referenced.

Hope someone else finds this useful.

Cheers!