Kostas Demiris
Kostas Demiris

Reputation: 3631

AWS CloudFormation template: Is it possible to add many CidrIp as a list?

I want to create the inbound rules of a security group in a CloudFormation template. I want to open the 3306 port from many different IPs.

"SecurityGroupIngress": [{
  "IpProtocol": "tcp",
  "CidrIp": "0.0.0.0/0",
  "FromPort": "3306",
  "ToPort": "3306"
}]

I know that the documentation says String as the CidrIp type but would it be possible to do something like this ["100.10.77.66/32", "100.10.66.66/32" , "101.10.77.66/32"] in order to avoid writing the same block many times?

Upvotes: 17

Views: 18356

Answers (6)

Uday Pradhan
Uday Pradhan

Reputation: 11

This is now possible using AWS Language Transform macros. See: https://awstip.com/dynamically-creating-security-group-ingress-rules-in-aws-cloudformation-bb3f48b049ba for details.

In summary, you can use the Fn::Foreach intrinsic function to add multiple ingress rules to your Security group

Upvotes: 1

Kaustubh
Kaustubh

Reputation: 267

I am not sure why the prefix list has not been mentioned. You can create the prefix list with all the CIDR's and mention the prefix list id as the security group source in the CloudFormation template.

Example :

PrefixList:
  Type: AWS::EC2::PrefixList
  Properties:
    PrefixListName: "Name of PL"
    AddressFamily: "IPv4"
    MaxEntries: 2 # the number of CIDR's you want to add
    Entries:
      - Cidr:  "10.10.0.0/16"
        Description: "CIDR1"
      - Cidr: "10.100.0.0/16"
        Description: "CIDR2"
    Tags:
      - Key: "Name"
        Value: "PL

Please go through the link below.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-prefixlist.html

Upvotes: 6

ymkjp
ymkjp

Reputation: 91

Based on wjordan's approach, here's a similar way you can specify CidrIp in separated files from ERB template. First, create an ERB file. For example, let's see sgi-trusted-ip-range.erb:

Type: "AWS::EC2::SecurityGroupIngress"
<%- Dir.glob("trusted-ip-range/cidr-*.csv").map {|f| File.readlines(f)}.flatten.map(&:strip).each do |cidr| -%>
- IpProtocol: tcp
  CidrIp: <%=cidr%>
  FromPort: 80
  ToPort: 80
- IpProtocol: tcp
  CidrIp: <%=cidr%>
  FromPort: 443
  ToPort: 443
<%- end -%>

Place CIDR list files as many as your requirement meets with following the naming convention trusted-ip-range/cidr-*.csv where the content is for example:

100.10.77.66/32
100.10.66.66/32
101.10.77.66/32

Then, generate a YAML file by this one-liner:

ruby -r erb -e 'ERB.new(ARGF.read, nil, "-").run' sgi-trusted-ip-range.erb

Besides, you can validate the generated YAML file like below:

aws cloudformation validate-template --template-body "file://__PATH_TO_FILE__.yml"

Upvotes: 1

I am curious on why there is no answer referring to Cloudformation Custom resources. You can still create your own custom security group using a lambda function that creates/deletes a security group based on your cloudformation status (creating, updating, deleting). I wrote a simple custom resource here, feel free to modify the custom resource parameters as you find convenient. Then you can input a comma separated cidr blocks when creating the stack. You can reference this security group on an instance (or other resource) using:
!GetAtt CustomSG.SecGroupId

Upvotes: 0

wjordan
wjordan

Reputation: 20390

Unfortunately, there's no iteration available through CloudFormation's Intrinsic Functions, and as you pointed out the AWS::EC2::SecurityGroupIngress resource itself only accepts a single String for its CidrIp property.

As an alternative, I would recommend choosing an intermediate format to compile down to CloudFormation template JSON using a preprocessor, if/when greater expressive power is needed. You can use a full-featured library like troposphere, but it's also easy enough to code up your own basic preprocessing layer to suit your use-case and programming-language/library preferences.

My current choice is a combination of YAML with embedded Ruby (ERB), mostly because I'm already familiar with them. Here's an example template.yml.erb file that would generate the example JSON above:

SecurityGroupIngress:
<% ["100.10.77.66/32", "100.10.66.66/32" , "101.10.77.66/32"].each do |cidr| -%>
- IpProtocol: tcp
  CidrIp: <%=cidr%>
  FromPort: 3306
  ToPort: 3306
<% end -%>

Here's a minimal preprocessor script, process_template.rb:

require 'erb'
require 'yaml'
require 'json'
puts JSON.pretty_generate(YAML.load(ERB.new(ARGF.read, nil, '-').result))

Running ruby ./process_template.rb template.yml.erb produces:

{
  "SecurityGroupIngress": [
    {
      "IpProtocol": "tcp",
      "CidrIp": "100.10.77.66/32",
      "FromPort": 3306,
      "ToPort": 3306
    },
    {
      "IpProtocol": "tcp",
      "CidrIp": "100.10.66.66/32",
      "FromPort": 3306,
      "ToPort": 3306
    },
    {
      "IpProtocol": "tcp",
      "CidrIp": "101.10.77.66/32",
      "FromPort": 3306,
      "ToPort": 3306
    }
  ]
}

Upvotes: 11

Aeladru
Aeladru

Reputation: 341

Afraid not, as the documentation states it only accepts String and not List therefore multiple blocks are required.

Think of it the same way as ingress rules are created within the web console, one new rule for each CIDR.

Upvotes: 16

Related Questions