I am attempting to use Ansible to provision AWS EC2 security groups.
I want some of these groups to have multiple rules. Some rules will apply only to my internal VPC while others are open to the world.
I would like to create the security groups in a loop that reads configuration from a list variable, but I don't want to hard code the CIDR for the internal VPC. I would rather get the CIDR from my VPC facts, but I haven't found a satisfactory way to substitute the CIDR into the rule.
To make this more clear, here is a (contrived) example. My original list of groups had all of the CIDRs hard coded:
aws_security_groups:
- name: Webservers
description: Security group for webservers
region: my_aws_region
rules:
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: 0.0.0.0/0
- proto: tcp
from_port: 80
to_port: 80
cidr_ip: 0.0.0.0/0
- name: Databases
description: Security group for internal database access
region: my_aws_region
rules:
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: 0.0.0.0/0
- proto: tcp
from_port: 3306
to_port: 3306
cidr_ip: <vpc.cidr.hard.coded/16>
The original play works and is very simple:
- name: Provision EC2 security groups
ec2_group:
name: "{{ item.name }}"
description: "{{ item.description }}"
region: "{{ item.region }}"
state: present
rules: "{{ item.rules }}"
with_items: "{{ aws_security_groups }}"
I can add or subtract rules and know that the groups will be synced. However, I don't want to hard code the CIDRs... I want my list of groups to look like:
aws_security_groups:
- name: Webservers
description: Security group for webservers
region: my_aws_region
rules:
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: all
- proto: tcp
from_port: 80
to_port: 80
cidr_ip: all
- name: Databases
description: Security group for internal database access
region: my_aws_region
rules:
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: all
- proto: tcp
from_port: 3306
to_port: 3306
cidr_ip: internal
My current strategy is to use with_subelements
to run a bunch of ec2_group
commands for each rule individually:
- name: Gather EC2 VPC facts
ec2_vpc_net_facts:
region: my_aws_region
register: vpcs
- name: Provision EC2 security groups
ec2_group:
name: "{{ item.0.name }}"
description: "{{ item.0.description }}"
region: "{{ item.0.region }}"
state: present
purge_rules: false
rules:
- from_port: "{{ item.1.from_port }}"
to_port: "{{ item.1.to_port }}"
proto: "{{ item.1.proto }}"
cidr_ip: "{{ (item.1.cidr_ip == 'internal') | ternary(vpcs.vpcs.0.cidr_block, (item.1.cidr_ip == 'all') | ternary('0.0.0.0/0', item.1.cidr_ip)) }}"
with_subelements:
- "{{ aws_security_groups }}"
- rules
The substitutions work, but I am stuck with some uncomfortable trade offs.
First, I have to turn off purge_rules
since every loop will just erase the previous rules if I don't. Because of this, if I make changes to the rules I may be stuck with old rules that I will need to keep track of and clean up.
Second, the nested ternaries are awkward and not easily expandable.
Third, I'm making multiple calls when one would suffice.
I feel like I am missing something obvious or that I am over-complicating this. How can I accomplish the CIDR substitution, or more generally, any substitution of this kind in an Ansible playbook? Is substitution preferable or is there a better method to accomplish the same task?
Any help on this is greatly appreciated.
The easiest way to pass Pass Variables value to Ansible Playbook in the command line is using the extra variables parameter of the “ansible-playbook” command. This is very useful to combine your Ansible Playbook with some pre-existent automation or script.
Change an instance's security group You can change the security groups when the instance is in the running or stopped state. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/ . In the navigation pane, choose Instances. Select your instance, and then choose Actions, Security, Change security groups.
Ansible treats values of the extra variables as strings. To pass values that are not strings, we need to use JSON format. To pass extra vars in JSON format we need to enclose JSON in quotation marks: ansible-playbook extra_var_json.
The following way you avoid purge_rules
problem and it's cleaner, imho.
Tasks:
- set_fact:
from_template: "{{ lookup('template', './template.j2') }}"
vars:
to_template_aws_security_groups: "{{ aws_security_groups }}"
to_template_vpcs: "{{ vpcs }}"
- name: Provision EC2 security groups
ec2_group:
name: "{{ item.name }}"
description: "{{ item.description }}"
region: "{{ item.region }}"
state: present
rules: "{{ item.rules }}"
with_items: "{{ from_template.aws_security_groups }}"
template.j2
:
{
"aws_security_groups": [
{% for aws_security_group in to_template_aws_security_groups %}
{
"description": "{{ aws_security_group.description }}",
"name": "{{ aws_security_group.name }}",
"region": "{{ aws_security_group.region }}",
"rules": [
{% for rule in aws_security_group.rules %}
{
"cidr_ip": "{{ (rule.cidr_ip == 'internal') | ternary(to_template_vpcs.vpcs.0.cidr_block, (rule.cidr_ip == 'all') | ternary('0.0.0.0/0', rule.cidr_ip)) }}",
"from_port": "{{ rule.from_port }}",
"proto": "{{ rule.proto }}",
"to_port": {{ rule.to_port }}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With