mikegleasonjr / ansible-role-firewall

A role to manage iptables rules which doesn't suck.
BSD 2-Clause "Simplified" License
95 stars 39 forks source link

group_vars rules are not intercepted between groups #1

Closed glukinho closed 8 years ago

glukinho commented 8 years ago

I have some group based rules:

  1. ansible/roles/mikegleasonjr.firewall:

---
firewall_v4_default_rules:
  001 default policies:
    - -P INPUT ACCEPT
    - -P OUTPUT ACCEPT
    - -P FORWARD DROP
  002 allow loopback:
    - -A INPUT -i lo -j ACCEPT
  003 allow ping replies:
    - -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
    - -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
  100 allow established related:
    - -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
  200 allow ssh:
    - -A INPUT -p tcp --dport ssh -j ACCEPT 
  201 allow zabbix agent:
    - -A INPUT -p tcp --match multiport --dports 10050,10051 -j ACCEPT
  204 allow http:
    - -A INPUT -p tcp --dport 80 -j ACCEPT
  205 allow syslog:
    - -A INPUT -p udp --dport 514 -j ACCEPT
    - -A INPUT -p tcp --dport 514 -j ACCEPT
  250 allow haproxy web interface:
    - -A INPUT -p tcp --dport 8080 -j ACCEPT
  999 drop everything:
    - -P INPUT DROP

firewall_v4_group_rules: {}

firewall_v4_host_rules: {}

1.ansible/group_vars/sql_hosts:


---
firewall_v4_group_rules:
  320 Allow Percona XtraDB Cluster ports:
    - -A INPUT -p tcp --match multiport --dports 3306,4444,4567,4568 -j ACCEPT
  321 allow mysql through haproxy:
    - -A INPUT -p tcp --dport 13306 -j ACCEPT

2.ansible/group_vars/pbx_hosts:


---
firewall_v4_group_rules:
  300 allow sip and rtp:
    - -A INPUT -p udp --dport 5060 -j ACCEPT
    - -A INPUT -p tcp --dport 5060 -j ACCEPT
    - -A INPUT -p udp --match multiport --dports 10000:20000 -j ACCEPT
  301 allow T.38 UDPTL:
    - -A INPUT -p udp --match multiport --dports 4000:4999 -j ACCEPT
  302 allow voipmonitor sensor:
    - -A INPUT -p tcp --dport 5029 -j ACCEPT

3.In 'ansible/hosts' file I have some servers intercepted between groups:

[pbx_hosts]
srv1
srv2

[sql_hosts]
srv1
srv2
srv3

After applying playbook to all hosts I see only sql_hosts-based rules and not pbx_hosts-based:

[root@srv1 filter.d]# iptables -L -n
Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           icmp type 8
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           ctstate RELATED,ESTABLISHED
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:22
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           multiport dports 10050,10051
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:80
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           udp dpt:514
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:514
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:8080
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           multiport dports 3306,4444,4567,4568
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:13306

Chain FORWARD (policy DROP)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           icmp type 0

I supposed I should have seen all rules I described in both files.

glukinho commented 8 years ago

And if I remove srv1 from [sql_hosts] group and re-apply the playbook, I see my pbx rules applied on it instead of sql ones:

[root@srv1 filter.d]# iptables -L -n
Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           icmp type 8
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           ctstate RELATED,ESTABLISHED
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:22
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           multiport dports 10050,10051
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:80
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           udp dpt:514
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:514
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:8080
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           udp dpt:5060
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:5060
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           multiport dports 10000:20000
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           multiport dports 4000:4999
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:5029

Chain FORWARD (policy DROP)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           icmp type 0
glukinho commented 8 years ago

I understood ansible group variables inheritance mechanism, so for my expected behavior I have done that:

---
firewall_v4_group_rules_pbx:
  300 allow sip and rtp:
    - -A INPUT -p udp --dport 5060 -j ACCEPT
    - -A INPUT -p tcp --dport 5060 -j ACCEPT
    - -A INPUT -p udp --match multiport --dports 10000:20000 -j ACCEPT
  301 allow T.38 UDPTL:
    - -A INPUT -p udp --match multiport --dports 4000:4999 -j ACCEPT
  302 allow voipmonitor sensor:
    - -A INPUT -p tcp --dport 5029 -j ACCEPT
---
firewall_v4_group_rules_sql:
  320 Allow Percona XtraDB Cluster ports:
    - -A INPUT -p tcp --match multiport --dports 3306,4444,4567,4568 -j ACCEPT
  321 allow mysql through haproxy:
    - -A INPUT -p tcp --dport 13306 -j ACCEPT
#!/bin/sh
# {{ ansible_managed }}
{% set merged = firewall_v4_default_rules.copy() %}
{% set _ = merged.update(firewall_v4_group_rules) %}
{% set _ = merged.update(firewall_v4_group_rules_pbx) %}     # here
{% set _ = merged.update(firewall_v4_group_rules_sql) %}     # and here
{% set _ = merged.update(firewall_v4_host_rules) %}
...

After that, all my rules were applied:

[root@srv1 filter.d]# iptables -L -n
Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           icmp type 8
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           ctstate RELATED,ESTABLISHED
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:22
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           multiport dports 10050,10051
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:80
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           udp dpt:514
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:514
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:8080
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           udp dpt:5060
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:5060
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           multiport dports 10000:20000
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           multiport dports 4000:4999
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:5029
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           multiport dports 3306,4444,4567,4568
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:13306
...

Thinking how to manage this without hard-coding dictionary names in library code...

glukinho commented 8 years ago

Another session of googling led me to the decision. All I had to do is to set

# if inventory variables overlap, does the higher precedence one win
# or are hash values merged together?  The default is 'replace' but
# this can also be set to 'merge'.

hash_behaviour = merge

in ansible.cfg. All rules were applied as they should have been, in right order, without any trick with dictionary names.

Thanks to http://stackoverflow.com/questions/23525546/ansible-host-in-multiple-groups

I think you should include this important thing into the documentation.

mightydok commented 8 years ago

@glukinho such settings can broke other roles, so i think change hash_behaviour a not so good idea.

glukinho commented 8 years ago

I agree, but at least write about this issue and user will choose himself. Is it possible to have hash_behaviour defined on per role basis?

mightydok commented 8 years ago

@glukinho from doc for Ansible 2.0 - "If you want to merge hashes without changing the global settings, use the combine filter described in Jinja2 filters"

glukinho commented 8 years ago

Thanks. I'm on 1.9 now.

mikegleasonjr commented 8 years ago

Hello guys and thanks for your interest in this role.

I think the overwrite is normal in this case. See this complete example given here: http://serverfault.com/questions/666457/ansible-a-host-appears-in-more-than-one-group-and-both-groups-have-the-same-ta

The problem here is that two "separate instances" of the firewall role is applied to the same host consecutively, with the last one overwriting the previous. Hence your results.

I suggest you re-organize your inventory so that a host appears only one time (is in a single group).

You could have pbx servers being children of webservers. I like to describe groups in my inventories to define the behavior as a whole of the hosts in that group.

See http://docs.ansible.com/ansible/intro_inventory.html for examples on how to define group hierarchies.

Hope this helps!

glukinho commented 8 years ago

@mikegleasonjr thanks for the reply. In my case the role is applied once, but must be applied with the whole set of rules: default set for all hosts, each 'group' rule of all groups the host belongs to, and each 'host' rules created for the particular host (not in this my case, but in general), mixed between each other. This is because of my infrastructure when some servers have both PBX and DB functionality at the same time (and some don't), so iptables must allow both PBX ports (5060 + 10000:20000) and DB ports (3306 and some else). And I can't imagine how to reorganize my inventory to have only one host per group (BTW, ansible docs allow that possibility and I find it quite useful too) and stay flexible and easy to use.

Of course, I could have inventory like that:

[db_hosts]
srv3

[pbx_and_db_hosts]
srv1
srv2

and corresponding dictionaries in group_vars/ files. But I find it not flexible: I have to reorganize groups and group based rules in case of changing one server's functionality. This kills the whole idea of groups that can contain any hosts and intercept with another groups with sharing of functionality.

mikegleasonjr commented 8 years ago

@glukinho Thanks for the feedback, can you provide an exemple of your playbook?

glukinho commented 8 years ago

@mikegleasonjr It is very simple:

---
- hosts: all
  roles: 
   - iptables    # I renamed folder 'mikegleasonjr.firewall' to 'iptables'

This appies iptables rules according to groups membership of hosts.

mikegleasonjr commented 8 years ago

@glukinho cool I'll have a look when I'll have my computer around and see what we can do!

mightydok commented 8 years ago

Please reopen issue.

mikegleasonjr commented 8 years ago

The link provided above explains the issue. Also, I found: https://github.com/ansible/ansible/issues/9065

I'll still see what can be done here for you, I'm still away from my computer right now... Stay tuned!

glukinho commented 8 years ago

My solution with 'hash_behaviour = merge' satisfied me, after all :)

mikegleasonjr commented 8 years ago

https://github.com/ansible/ansible/issues/9065#issuecomment-77695853

This comment sums up perfectly why Ansible is behaving that way...

Should we close the issue?

mightydok commented 8 years ago

@mikegleasonjr i think we need update README with that info and update templates for asible 2.0 with combine jinja2 filter. This filter allow vars merging, so it will help without change hash_behaviour. Ansible developers dont recommend that.

mikegleasonjr commented 8 years ago

@mightydok I can update the README to explain the "shortcomings" of Ansible in this particular scenario. Ansible 2.0 won't help here because the jinja2 filter will act on variables already constructed by Ansible.

If you carefully read the issue ansible/ansible#9065 (in particular this comment https://github.com/ansible/ansible/issues/9065#issuecomment-77695853), Ansible has a procedure to determine what the variable will hold at execution time. In this case the variable will already hold an "incorrect" value before even you'll have the chance to play with it. So having a jinja2 filter to merge dictionaries won't help.

Besides, I already use a dictionary merge function under the hood (the python method update) in the template file: https://github.com/mikegleasonjr/ansible-role-firewall/blob/master/templates/generated.v4.j2#L4

I'm really afraid that this is a matter related to Ansible.

glukinho commented 8 years ago

I understand. In any case, I like how it works with 'hash_behaviour = merge' and it corresponds with my notion of groups and roles. I just wanted you to mention this issue in README. Thanks a lot,

mightydok commented 8 years ago

@glukinho good news =). @mikegleasonjr i can view ansible code tomorrow, maybe will find cause of this problem.

mikegleasonjr commented 8 years ago

Allright let's leave it open until we all agree or find a workaround!

mikegleasonjr commented 8 years ago

Closing since this is the normal behaviour in Ansible, OP found a workaround to his problem and no one proposed something else.

Thank you