From 778167608598561c28e7d4eed509f8451566a47f Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Fri, 16 Dec 2016 15:10:02 +0100 Subject: [PATCH 01/30] Redesign of the ferm rule configuration --- CHANGES.rst | 39 ++ defaults/main.yml | 104 +++- tasks/main.yml | 60 +- templates/etc/ferm/ferm.conf.j2 | 3 +- templates/etc/ferm/rules.d/rule.conf.j2 | 518 ++++++++++++++++++ templates/lookup/ferm__combined_rules.j2 | 63 +++ templates/lookup/ferm__fix_dependent_rules.j2 | 32 ++ 7 files changed, 750 insertions(+), 69 deletions(-) create mode 100644 templates/etc/ferm/rules.d/rule.conf.j2 create mode 100644 templates/lookup/ferm__combined_rules.j2 create mode 100644 templates/lookup/ferm__fix_dependent_rules.j2 diff --git a/CHANGES.rst b/CHANGES.rst index 74bcecd..ef1a9af 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -33,6 +33,45 @@ Changed - Packets blocked due to rate limits will be now dropped instead of being rejected by default. [gaudenz] +- The data format of the firewall rules has been redesigned. Rules can now be + defined as nested YAML dictionaries, existing default or dependent rules can + be easily modified through the Ansible inventory, multiple firewall rules can + be included in one configuration file. The role is compatible with the old, + list-based data format, however dictionary-based format should be preferred. + [drybjed_] + +- The firewall rules are now read from the :file:`/etc/ferm/rules.d/` directory + to help with transition to the new data format and avoid tab-completion + collision with the :file:`/etc/ferm/ferm.conf` file. [drybjed_] + +- Use of multiple rule parameters that define the final filename of the + configuration files has been dropped, now only the ``item.name`` parameter is + used to define the filename. [drybjed_] + +- The role automatically removes duplicate configuration files (based on the + ``name`` parameter) when the weight of a given rule is changed to make + modifications easier. [drybjed_] + +- The scale of the "weight" used to sort the rules in the directory has been + changed from 00-99 to 000-999. [drybjed_] + +- The ``item.weight`` parameter is now relative to the "weight class" or rule + type defined for a given firewall rule. You can use negative weight values + for better control over rule order. [drybjed_] + +Removed +~~~~~~~ + +- The ``ferm__default_weight`` variable has been removed. The default rule + weight is defined in the weight map directly. [drybjed_] + +- The role will no longer create the :file:`/etc/ferm/ferm.d/` directory by + default. Existing directories are not removed. [drybjed_] + +- The ``item.when`` and ``item.delete`` parameters are no longer supported. You + can control rule presence conditionally using ``item.rule_state`` or + ``item.state`` parameters. [drybjed_] + `debops.ferm v0.2.2`_ - 2016-12-01 ---------------------------------- diff --git a/defaults/main.yml b/defaults/main.yml index 9b3337a..0b41fb9 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -240,8 +240,13 @@ ferm__log_burst: '5' ferm__log_group: '32' # ]]] # ]]] -# Rules configuration [[[ -# ----------------------- +# Firewall rules configuration [[[ +# -------------------------------- + +# The variables below define what rules should be present in the firewall. Each +# variable is a YAML dictionary with nested dictionaries. They are combined in +# the :envvar:`ferm__combined_rules` variable. See :ref:`ferm__ref_rules` for +# more details. # .. envvar:: ferm__include_legacy [[[ # @@ -249,31 +254,54 @@ ferm__log_group: '32' # transition to the new firewall rules in the future. ferm__include_legacy: True + # ]]] +# .. envvar:: ferm__dependent_rules [[[ +# +# YAML dictionary which contains :command:`ferm` rules to manage defined by +# other Ansible roles using dependent variables. +ferm__dependent_rules: {} + + # ]]] +# .. envvar:: ferm__fix_dependent_rules [[[ +# +# For now, some rules defined by other Ansible roles are incomplete. This +# template makes sure that all required information is added if missing. This +# variable will be removed at some point in the future, therefore you should +# not rely on it. +ferm__fix_dependent_rules: '{{ lookup("template", + "lookup/ferm__fix_dependent_rules.j2", + convert_data=False) | from_json }}' + # ]]] # .. envvar:: ferm__rules [[[ # -# List of :command:`iptables` rules to manage, templates used by these rules are included -# in :file:`templates/etc/ferm/ferm.d/` directory. -# Additional variables are described below. -ferm__rules: [] +# YAML dictionary which contains :command:`ferm` rules which should be defined +# on all hosts in the Ansible inventory. +ferm__rules: {} # ]]] # .. envvar:: ferm__group_rules [[[ # -# List of :command:`iptables` rules to manage for a host group. -ferm__group_rules: [] +# YAML dictionary which contains :command:`ferm` rules which should be defined +# on a group of hosts in the Ansible inventory. +ferm__group_rules: {} # ]]] # .. envvar:: ferm__host_rules [[[ # -# List of :command:`iptables` rules to manage for an individual host. -ferm__host_rules: [] +# YAML dictionary which contains :command:`ferm` rules which should be defined +# on specific hosts in the Ansible inventory. +ferm__host_rules: {} # ]]] -# .. envvar:: ferm__dependent_rules [[[ +# .. envvar:: ferm__combined_rules [[[ # -# List of :command:`iptables` rules to manage depending on other rules. -ferm__dependent_rules: [] +# YAML dictionary which contains all of the defined :command:`ferm` rules +# combined together. This variable is used in the Ansible tasks that manage the +# rules on remote hosts. +ferm__combined_rules: '{{ lookup("template", + "lookup/ferm__combined_rules.j2", + convert_data=False) | from_yaml }}' # ]]] # .. envvar:: ferm_input_list [[[ @@ -306,28 +334,44 @@ ferm_input_host_list: [] ferm_input_dependent_list: [] # ]]] -# .. envvar:: ferm__default_weight [[[ +# .. envvar:: ferm__default_weight_map [[[ # -# Set the default rule weight for rules that do not specify it. -ferm__default_weight: '50' +# Dictionary with mapping between "rule classes" and their desired weight. +ferm__default_weight_map: + 'pre-hook': '00' + 'function': '00' + 'custom': '00' + 'loopback': '01' + 'default_policy': '05' + 'policy': '05' + 'ansible-controller': '05' + 'any-whitelist': '10' + 'filter-icmp': '15' + 'connection-tracking': '20' + 'filter-syn': '25' + 'any-blacklist': '30' + 'sshd-chain': '40' + 'accept': '50' + 'default': '100' + 'reject': '900' + 'any-reject': '900' + 'post-hook': '950' # ]]] # .. envvar:: ferm__weight_map [[[ # -# Dictionary with mapping between "rule classes" and their desired weight. -ferm__weight_map: - 'loopback': '00' - 'ansible-controller': '01' - 'any-whitelist': '02' - 'filter-icmp': '03' - 'conntrack': '05' - 'filter-syn': '07' - 'any-blacklist': '09' - 'any-ssh-whitelist': '25' - 'any-ssh-accept': '30' - 'any-ssh-filter': '31' - 'any-service': '50' - 'any-reject': '99' +# Dictionary with additional mapping between "rule classes" and their desired +# weight. This variable can be used to override weight for specific weight +# classes. +ferm__weight_map: {} + + # ]]] +# .. envvar:: ferm__combined_weight_map [[[ +# +# YAML dictionary with the combined default and custom weight maps, used by the +# Ansible tasks. +ferm__combined_weight_map: '{{ ferm__default_weight_map + | combine(ferm__weight_map) }}' # ]]] # .. envvar:: ferm__default_rules [[[ diff --git a/tasks/main.yml b/tasks/main.yml index 561f072..2fe2214 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -24,7 +24,7 @@ group: 'adm' mode: '2750' with_items: - - '/etc/ferm/ferm.d' + - '/etc/ferm/rules.d' - '/etc/ferm/filter-input.d' - '/etc/ferm/hooks/pre.d' - '/etc/ferm/hooks/post.d' @@ -68,49 +68,35 @@ mode: '0644' notify: [ 'Restart ferm' ] -- name: Remove ip(6)tables rules if requested +- name: Remove firewall rules file: - dest: '/etc/ferm/ferm.d/{{ ferm__weight_map[item.weight_class|d()] | d(item.weight | d(ferm__default_weight)) }}_{{ item.filename | d((((item.by_role|d(item.role)| replace(".", "_")) + "_" + ((item.role_weight + "_") if item.role_weight|d() else "")) if (item.by_role|d(item.role)|d()) else "") + item.type + "_" + item.name | d((item.dport[0] if item.dport|d() else "rules"))) }}.conf' + dest: '/etc/ferm/rules.d/{{ "%03d" | format((ferm__combined_weight_map[item.value.weight_class | d(item.value.type | d("default"))] | d("80"))|int + (item.value.weight | d("0"))|int) }}_rule_{{ item.value.name | d(item.key) }}.conf' state: 'absent' - with_flattened: - - '{{ ferm_rules | d([]) | list }}' - - '{{ ferm_group_rules | d([]) | list }}' - - '{{ ferm_host_rules | d([]) | list }}' - - '{{ ferm_dependent_rules | d([]) | list }}' - - '{{ ferm_default_rules | d([]) | list }}' - - '{{ ferm__rules }}' - - '{{ ferm__group_rules }}' - - '{{ ferm__host_rules }}' - - '{{ ferm__dependent_rules }}' - - '{{ ferm__default_rules }}' - when: (ferm__enabled|bool and item.type|d() and - ((item.rule_state|d("present") == "absent") or - (item.delete|d() | bool))) - register: ferm__register_rules_del + with_dict: '{{ ferm__combined_rules }}' + register: ferm__register_rules_removed + when: (item.value.rule_state|d(item.value.state|d('present')) == 'absent') tags: [ 'role::ferm:rules' ] -- name: Configure ip(6)tables rules +- name: Generate firewall rules template: - src: 'etc/ferm/ferm.d/{{ item.type }}.conf.j2' - dest: '/etc/ferm/ferm.d/{{ ferm__weight_map[item.weight_class|d()] | d(item.weight | d(ferm__default_weight)) }}_{{ item.filename | d((((item.by_role|d(item.role)| replace(".", "_")) + "_" + ((item.role_weight + "_") if item.role_weight|d() else "")) if (item.by_role|d(item.role)|d()) else "") + item.type + "_" + item.name | d((item.dport[0] if item.dport|d() else "rules"))) }}.conf' + src: 'etc/ferm/rules.d/{{ item.value.template | d("rule") }}.conf.j2' + dest: '/etc/ferm/rules.d/{{ "%03d" | format((ferm__combined_weight_map[item.value.weight_class | d(item.value.type | d("default"))] | d("80"))|int + (item.value.weight | d("0"))|int) }}_rule_{{ item.value.name | d(item.key) }}.conf' owner: 'root' group: 'adm' mode: '0644' - with_flattened: - - '{{ ferm_rules | d([]) | list }}' - - '{{ ferm_group_rules | d([]) | list }}' - - '{{ ferm_host_rules | d([]) | list }}' - - '{{ ferm_dependent_rules | d([]) | list }}' - - '{{ ferm_default_rules | d([]) | list }}' - - '{{ ferm__rules }}' - - '{{ ferm__group_rules }}' - - '{{ ferm__host_rules }}' - - '{{ ferm__dependent_rules }}' - - '{{ ferm__default_rules }}' - when: (ferm__enabled|bool and item.type|d() and - ((item.rule_state|d("present") == "present") and - not (item.delete|d() | bool))) - register: ferm__register_rules_add + with_dict: '{{ ferm__combined_rules }}' + register: ferm__register_rules_created + when: (item.value.rule_state|d(item.value.state|d('present')) not in [ 'absent', 'ignore' ]) + tags: [ 'role::ferm:rules' ] + +- name: Remove unknown firewall rules + shell: find /etc/ferm/rules.d -maxdepth 1 -type f + -name '*_rule_{{ item.item.value.name | d(item.item.key) }}.conf' + ! -name '{{ "%03d" | format((ferm__combined_weight_map[item.item.value.weight_class | d(item.item.value.type | d("default"))] | d("80"))|int + (item.item.value.weight | d("0"))|int) }}_rule_{{ item.item.value.name | d(item.item.key) }}.conf' -exec rm -vf {} + + with_items: + - '{{ ferm__register_rules_removed.results }}' + - '{{ ferm__register_rules_created.results }}' + when: (item.item.key|d() and item.changed|bool) tags: [ 'role::ferm:rules' ] - name: Remove iptables INPUT rules if requested @@ -147,7 +133,7 @@ name: 'ferm' state: 'restarted' when: (ferm__enabled | bool and (ferm__register_files.changed|bool or - ferm__register_rules_del.changed|bool or ferm__register_rules_add.changed|bool or + ferm__register_rules_created|changed or ferm__register_rules_removed|changed or ferm__register_input_rules_del.changed|bool or ferm__register_input_rules_add.changed|bool)) - name: Clear iptables rules if ferm is disabled diff --git a/templates/etc/ferm/ferm.conf.j2 b/templates/etc/ferm/ferm.conf.j2 index 73d2644..676849a 100644 --- a/templates/etc/ferm/ferm.conf.j2 +++ b/templates/etc/ferm/ferm.conf.j2 @@ -1,5 +1,4 @@ # {{ ansible_managed }} # Load configuration from parts -@include 'ferm.d/'; - +@include 'rules.d/'; diff --git a/templates/etc/ferm/rules.d/rule.conf.j2 b/templates/etc/ferm/rules.d/rule.conf.j2 new file mode 100644 index 0000000..f77996d --- /dev/null +++ b/templates/etc/ferm/rules.d/rule.conf.j2 @@ -0,0 +1,518 @@ +# {{ ansible_managed }} + +{% macro print_rule(config) %} +{# Domain, table, chain #} +{# ==================== #} +{% set ferm__tpl_domain = [] %} +{% set ferm__tpl_table = [] %} +{% set ferm__tpl_chain = [] %} +{% if config.domain|d() %} +{% set _ = ferm__tpl_domain.extend(([ config.domain ] if config.domain is string else config.domain)) %} +{% elif config.domains|d() %} +{% set _ = ferm__tpl_domain.extend(([ config.domains ] if config.domains is string else config.domains)) %} +{% endif %} +{% if config.table|d() %} +{% set _ = ferm__tpl_table.extend(([ config.table ] if config.table is string else config.table)) %} +{% elif config.tables|d() %} +{% set _ = ferm__tpl_table.extend(([ config.tables ] if config.tables is string else config.tables)) %} +{% endif %} +{% if config.chain|d() %} +{% set _ = ferm__tpl_chain.extend(([ config.chain ] if config.chain is string else config.chain)) %} +{% elif config.chains|d() %} +{% set _ = ferm__tpl_chain.extend(([ config.chains ] if config.chains is string else config.chains)) %} +{% endif %} +{% if not ferm__tpl_domain %} +{% set ferm__tpl_domain = ferm__domains %} +{% endif %} +{% if not ferm__tpl_table %} +{% set ferm__tpl_table = [ 'filter' ] %} +{% endif %} +{% if not ferm__tpl_chain %} +{% set ferm__tpl_chain = [ 'INPUT' ] %} +{% endif %} +{% set ferm__tpl_domain_args = [] %} +{% if ferm__tpl_domain %} +{% if ferm__tpl_domain | length == 1 %} +{% set _ = ferm__tpl_domain_args.append("domain " + ferm__tpl_domain | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_domain_args.append("domain (" + ferm__tpl_domain | join(" ") + ")") %} +{% endif %} +{% endif %} +{% if ferm__tpl_table %} +{% if ferm__tpl_table | length == 1 %} +{% set _ = ferm__tpl_domain_args.append("table " + ferm__tpl_table | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_domain_args.append("table (" + ferm__tpl_table | join(" ") + ")") %} +{% endif %} +{% endif %} +{% if ferm__tpl_chain %} +{% if ferm__tpl_chain | length == 1 %} +{% set _ = ferm__tpl_domain_args.append("chain " + ferm__tpl_chain | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_domain_args.append("chain (" + ferm__tpl_chain | join(" ") + ")") %} +{% endif %} +{% endif %} +{# Rule arguments #} +{# ============== #} +{% set ferm__tpl_target = 'ACCEPT' %} +{% set ferm__tpl_hashlimit_target = 'RETURN' %} +{% set ferm__tpl_recent_target = 'NOP' %} +{% set ferm__tpl_reject_with = 'icmp-admin-prohibited' %} +{% set ferm__tpl_interface = [] %} +{% set ferm__tpl_interface_present = [] %} +{% set ferm__tpl_outerface = [] %} +{% set ferm__tpl_outerface_present = [] %} +{% set ferm__tpl_protocol = [] %} +{% set ferm__tpl_protocol_syn = [] %} +{% set ferm__tpl_saddr = [] %} +{% set ferm__tpl_daddr = [] %} +{% set ferm__tpl_sport = [] %} +{% set ferm__tpl_dport = [] %} +{% set ferm__tpl_state = [] %} +{% set ferm__tpl_tracking_invalid_target = (config.tracking_invalid_target | d('DROP')) %} +{% set ferm__tpl_tracking_active_target = (config.tracking_active_target | d('ACCEPT')) %} +{% set ferm__tpl_tracking_module = (config.tracking_module | d('conntrack')) %} +{% set ferm__tpl_recent_name = '' %} +{% set ferm__tpl_recent_set_name = '' %} +{% set ferm__tpl_recent_update = False %} +{% set ferm__tpl_recent_remove = False %} +{% set ferm__tpl_recent_seconds = '' %} +{% set ferm__tpl_recent_hitcount = '' %} +{% if config.recent_name|d() %} +{% set ferm__tpl_recent_name = config.recent_name %} +{% elif config.recent_set_name|d() %} +{% set ferm__tpl_recent_set_name = config.recent_set_name %} +{% endif %} +{% if config.recent_update|d() and config.recent_update | bool %} +{% set ferm__tpl_recent_update = True %} +{% endif %} +{% if config.recent_remove|d() and config.recent_remove | bool %} +{% set ferm__tpl_recent_remove = True %} +{% endif %} +{% if config.recent_seconds|d() %} +{% set ferm__tpl_recent_seconds = config.recent_seconds | string %} +{% endif %} +{% if config.recent_hitcount|d() %} +{% set ferm__tpl_recent_hitcount = config.recent_hitcount | string %} +{% endif %} +{% if config.recent_target|d() %} +{% set ferm__tpl_recent_target = config.recent_target %} +{% endif %} +{% set ferm__tpl_subchain = (config.type|d('accept') + "-" + config.name | d((config.dport[0] if config.dport|d() else "rules"))) %} +{% if config.interface|d() %} +{% set _ = ferm__tpl_interface.extend(([ config.interface ] if config.interface is string else config.interface)) %} +{% elif config.interfaces|d() %} +{% set _ = ferm__tpl_interface.extend(([ config.interfaces ] if config.interfaces is string else config.interfaces)) %} +{% endif %} +{% if ferm__tpl_tracking_module == 'state' %} +{% set ferm__tpl_tracking_module_command = 'mod state state' %} +{% else %} +{% set ferm__tpl_tracking_module_command = 'mod conntrack ctstate' %} +{% endif %} +{% if config.interface_present|d() %} +{% if config.interface_present is string %} +{% if hostvars[inventory_hostname]["ansible_" + config.interface_present]|d() %} +{% set ferm__tpl_interface_present = [ config.interface_present ] %} +{% endif %} +{% else %} +{% for interface in config.interface_present %} +{% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} +{% set _ = ferm__tpl_interface_present.append(interface) %} +{% endif %} +{% endfor %} +{% endif %} +{% elif config.interfaces_present|d() %} +{% if config.interfaces_present is string %} +{% if hostvars[inventory_hostname]["ansible_" + config.interfaces_present]|d() %} +{% set ferm__tpl_interface_present = [ config.interfaces_present ] %} +{% endif %} +{% else %} +{% for interface in config.interfaces_present %} +{% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} +{% set _ = ferm__tpl_interface_present.append(interface) %} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} +{% if config.outerface|d() %} +{% set _ = ferm__tpl_outerface.extend(([ config.outerface ] if config.outerface is string else config.outerface)) %} +{% elif config.outerfaces|d() %} +{% set _ = ferm__tpl_outerface.extend(([ config.outerfaces ] if config.outerfaces is string else config.outerfaces)) %} +{% endif %} +{% if config.outerface_present|d() %} +{% if config.outerface_present is string %} +{% if hostvars[inventory_hostname]["ansible_" + config.outerface_present]|d() %} +{% set ferm__tpl_outerface_present = [ config.outerface_present ] %} +{% endif %} +{% else %} +{% for outerface in config.outerface_present %} +{% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} +{% set _ = ferm__tpl_outerface_present.append(outerface) %} +{% endif %} +{% endfor %} +{% endif %} +{% elif config.outerfaces_present|d() %} +{% if config.outerfaces_present is string %} +{% if hostvars[inventory_hostname]["ansible_" + config.outerfaces_present]|d() %} +{% set ferm__tpl_outerface_present = [ config.outerfaces_present ] %} +{% endif %} +{% else %} +{% for outerface in config.outerfaces_present %} +{% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} +{% set _ = ferm__tpl_outerface_present.append(outerface) %} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} +{% if config.protocol|d() %} +{% set _ = ferm__tpl_protocol.extend(([ config.protocol ] if config.protocol is string else config.protocol)) %} +{% elif config.protocols|d() %} +{% set _ = ferm__tpl_protocol.extend(([ config.protocols ] if config.protocols is string else config.protocols)) %} +{% endif %} +{% if config.protocol_syn is defined %} +{% if config.protocol_syn | bool %} +{% set ferm__tpl_protocol_syn = [ 'syn' ] %} +{% elif not config.protocol_syn | bool %} +{% set ferm__tpl_protocol_syn = [ '! syn' ] %} +{% endif %} +{% endif %} +{% if config.saddr|d() %} +{% set _ = ferm__tpl_saddr.extend(([ config.saddr ] if config.saddr is string else config.saddr)) %} +{% endif %} +{% if config.type|d('accept') == 'ansible_controller' %} +{% set ferm__tpl_ansible_controllers = [] %} +{% if ansible_local|d() and ansible_local.core|d() and ansible_local.core.ansible_controllers|d() %} +{% set _ = ferm__tpl_ansible_controllers.extend(ansible_local.core.ansible_controllers) %} +{% endif %} +{% if ansible_local|d() and ansible_local.ferm|d() and ansible_local.ferm.ansible_controllers|d() %} +{% set _ = ferm__tpl_ansible_controllers.extend(ansible_local.ferm.ansible_controllers) %} +{% endif %} +{% if ferm__ansible_controllers|d() %} +{% set _ = ferm__tpl_ansible_controllers.extend(([ ferm__ansible_controllers ] if ferm__ansible_controllers is string else ferm__ansible_controllers)) %} +{% endif %} +{% if config.ansible_controller|d() %} +{% set _ = ferm__tpl_ansible_controllers.extend(([ config.ansible_controller ] if config.ansible_controller is string else config.ansible_controller)) %} +{% elif config.ansible_controllers|d() %} +{% set _ = ferm__tpl_ansible_controllers.extend(([ config.ansible_controllers ] if config.ansible_controllers is string else config.ansible_controllers)) %} +{% endif %} +{% if ferm__tpl_ansible_controllers %} +{% set _ = ferm__tpl_saddr.extend(ferm__tpl_ansible_controllers) %} +{% endif %} +{% endif %} +{% if config.daddr|d() %} +{% set _ = ferm__tpl_daddr.extend(([ config.daddr ] if config.daddr is string else config.daddr)) %} +{% endif %} +{% if config.sport|d() %} +{% set _ = ferm__tpl_sport.extend(([ config.sport ] if config.sport is string else config.sport)) %} +{% endif %} +{% if config.dport|d() %} +{% set _ = ferm__tpl_dport.extend(([ config.dport ] if config.dport is string else config.dport)) %} +{% endif %} +{% if config.state|d() %} +{% set _ = ferm__tpl_state.extend(([ config.state ] if config.state is string else config.state)) %} +{% endif %} +{% if config.target|d() %} +{% set ferm__tpl_target = config.target %} +{% endif %} +{% if config.hashlimit_target|d() %} +{% set ferm__tpl_hashlimit_target = config.hashlimit_target %} +{% endif %} +{% if config.reject_with|d() %} +{% set ferm__tpl_reject_with = config.reject_with %} +{% endif %} +{% if config.subchain is defined %} +{% if config.subchain | bool %} +{% set ferm__tpl_subchain = config.subchain %} +{% else %} +{% if config.hashlimit|d() %} +{% set ferm__tpl_subchain = (config.type + "-" + config.hashlimit_name | d(config.name | d(rule_name))) %} +{% else %} +{% set ferm__tpl_subchain = '' %} +{% endif %} +{% endif %} +{% endif %} +{% if config.hashlimit|d() %} +{% set ferm__tpl_subchain = (config.type + "-" + config.hashlimit_name | d(config.name | d(rule_name))) %} +{% endif %} +{% set ferm__tpl_recent_args = [] %} +{% if ferm__tpl_recent_name %} +{% set _ = ferm__tpl_recent_args.append('name "' + ferm__tpl_recent_name + '"') %} +{% elif ferm__tpl_recent_set_name %} +{% set _ = ferm__tpl_recent_args.append('set name "' + ferm__tpl_recent_set_name + '"') %} +{% endif %} +{% if ferm__tpl_recent_update | bool %} +{% set _ = ferm__tpl_recent_args.append('update') %} +{% endif %} +{% if ferm__tpl_recent_remove | bool %} +{% set _ = ferm__tpl_recent_args.append('remove') %} +{% endif %} +{% if ferm__tpl_recent_seconds %} +{% set _ = ferm__tpl_recent_args.append('seconds ' + ferm__tpl_recent_seconds) %} +{% endif %} +{% if ferm__tpl_recent_hitcount %} +{% set _ = ferm__tpl_recent_args.append('hitcount ' + ferm__tpl_recent_hitcount) %} +{% endif %} +{% set ferm__tpl_arguments = [] %} +{% if ferm__tpl_interface %} +{% if ferm__tpl_interface | length == 1 %} +{% set _ = ferm__tpl_arguments.append("interface " + ferm__tpl_interface | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_arguments.append("interface (" + ferm__tpl_interface | join(" ") + ")") %} +{% endif %} +{% elif ferm__tpl_interface_present %} +{% if ferm__tpl_interface_present | length == 1 %} +{% set _ = ferm__tpl_arguments.append("interface " + ferm__tpl_interface_present | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_arguments.append("interface (" + ferm__tpl_interface_present | join(" ") + ")") %} +{% endif %} +{% endif %} +{% if ferm__tpl_outerface %} +{% if ferm__tpl_outerface | length == 1 %} +{% set _ = ferm__tpl_arguments.append("outerface " + ferm__tpl_outerface | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_arguments.append("outerface (" + ferm__tpl_outerface | join(" ") + ")") %} +{% endif %} +{% elif ferm__tpl_outerface_present %} +{% if ferm__tpl_outerface_present | length == 1 %} +{% set _ = ferm__tpl_arguments.append("outerface " + ferm__tpl_outerface_present | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_arguments.append("outerface (" + ferm__tpl_outerface_present | join(" ") + ")") %} +{% endif %} +{% endif %} +{% if ferm__tpl_protocol %} +{% if ferm__tpl_protocol | length == 1 %} +{% set _ = ferm__tpl_arguments.append("protocol " + ferm__tpl_protocol | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_arguments.append("protocol (" + ferm__tpl_protocol | join(" ") + ")") %} +{% endif %} +{% elif not ferm__tpl_protocol and (ferm__tpl_sport or ferm__tpl_dport) %} +{% set _ = ferm__tpl_arguments.append("protocol tcp") %} +{% endif %} +{% if ferm__tpl_protocol_syn %} +{% set _ = ferm__tpl_arguments.append(ferm__tpl_protocol_syn | join(" ")) %} +{% endif %} +{% if ferm__tpl_dport %} +{% if config.multiport|d() and config.multiport | bool %} +{% if ferm__tpl_dport | length == 1 %} +{% set _ = ferm__tpl_arguments.append("dport " + ferm__tpl_dport | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_arguments.append("mod multiport destination-ports (" + ferm__tpl_dport | join(" ") + ")") %} +{% endif %} +{% else %} +{% if ferm__tpl_dport | length == 1 %} +{% set _ = ferm__tpl_arguments.append("dport " + ferm__tpl_dport | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_arguments.append("dport (" + ferm__tpl_dport | join(" ") + ")") %} +{% endif %} +{% endif %} +{% endif %} +{% if ferm__tpl_state %} +{% if ferm__tpl_state | length == 1 %} +{% set _ = ferm__tpl_arguments.append("mod state state " + ferm__tpl_state | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_arguments.append("mod state state (" + ferm__tpl_state | join(" ") + ")") %} +{% endif %} +{% endif %} +{% if ferm__tpl_arguments and (ferm__tpl_saddr | length > 3 or config.hashlimit|d()) %} +{% if ferm__tpl_subchain %} +{% set _ = ferm__tpl_arguments.append('@subchain "' + ferm__tpl_subchain + '"') %} +{% endif %} +{% endif %} +{% if ferm__tpl_domain_args %}{{ ferm__tpl_domain_args | join(" ") }} {% endif %}{ +{% if config.type|d('accept') in [ 'policy', 'default_policy' ] %} + policy {{ config.policy }}; +{% elif config.type|d('accept') == 'include' %} + @include "{{ config.include }}"; +{% elif config.type|d('accept') == 'connection_tracking' %} + {% if ferm__tpl_arguments %}{{ ferm__tpl_arguments | join(" ") }} {% endif %}{ + {{ ferm__tpl_tracking_module_command }} INVALID {{ ferm__tpl_tracking_invalid_target }}; + {{ ferm__tpl_tracking_module_command }} (ESTABLISHED RELATED) {{ ferm__tpl_tracking_active_target }}; + } +{% elif config.type|d('accept') == 'recent' %} + {% if ferm__tpl_arguments %}{{ ferm__tpl_arguments | join(" ") }} {% endif %}{ + + mod recent {{ ferm__tpl_recent_args | join(" ") }} { +{% if ((config.recent_log is undefined or config.recent_log | bool) and ferm__log | bool) %} + + &log("{{ config.recent_log_prefix | d('ipt-recent-' + config.recent_name | d(config.recent_set_name) + ': ') }}"); +{% endif %} +{% if ferm__tpl_recent_target %} +{% if ferm__tpl_recent_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if config.include|d() %} + + @include "{{ config.include }}"; +{% elif config.realgoto is undefined or not config.realgoto | bool %} + + jump "{{ ferm__tpl_recent_target }}"; +{% elif config.realgoto|d() and config.realgoto | bool %} + + realgoto "{{ ferm__tpl_recent_target }}"; +{% endif %} +{% elif ferm__tpl_recent_target in [ 'REJECT' ] %} + + REJECT reject-with {{ ferm__tpl_reject_with }}; +{% else %} + + {{ ferm__tpl_recent_target }}; +{% endif %} +{% endif %} + } + } +{% elif config.type|d('accept') == 'reject' %} + protocol udp REJECT reject-with icmp-port-unreachable; + protocol tcp REJECT reject-with tcp-reset; + @if @eq($DOMAIN, ip) { + REJECT reject-with icmp-proto-unreachable; + } +{% elif config.type|d('accept') in [ 'custom', 'raw' ] %} +{% if ferm__tpl_domain_args %}{{ ferm__tpl_domain_args | join(" ") + " {" }} +{% endif %} +{% if config.rules|d() %} +{% if ferm__tpl_domain_args %} +{{ config.rules | indent(4,true) }} +{% else %} +{{ config.rules }} +{% endif %} +{% endif %} +{% if ferm__tpl_domain_args %}}{% endif %} +{% else %} + {% if ferm__tpl_arguments %}{{ ferm__tpl_arguments | join(" ") }} {% endif %}{ +{% if config.hashlimit|d() %} + + mod hashlimit hashlimit {{ config.hashlimit }} +{% if config.hashlimit_burst|d() %} + hashlimit-burst {{ config.hashlimit_burst }} +{% endif %} + hashlimit-mode {{ config.hashlimit_mode | d("srcip") }} + hashlimit-name {{ config.hashlimit_name | d(config.name | d(rule_name)) }} +{% if config.hashlimit_expire is undefined or config.hashlimit_expire %} + hashlimit-htable-expire {{ ((config.hashlimit_expire|d("1800")) | int * 1000) }} +{% endif %} +{% if ferm__tpl_hashlimit_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if config.include|d() %} + + @include "{{ config.include }}"; +{% elif config.realgoto is undefined or not config.realgoto | bool %} + + jump "{{ ferm__tpl_target }}"; +{% elif config.realgoto|d() and config.realgoto | bool %} + + realgoto "{{ ferm__tpl_target }}"; +{% endif %} +{% elif ferm__tpl_hashlimit_target in [ 'REJECT' ] %} + + REJECT reject-with {{ ferm__tpl_reject_with }}; +{% else %} + + {{ ferm__tpl_hashlimit_target }}; +{% endif %} +{% if ((config.log is undefined or config.log | bool) and (ferm__log | bool)) %} + + &log("{{ config.log_prefix | d('ipt-hashlimit-' + config.hashlimit_name | d(config.name | d(rule_name)) + ': ') }}"); + +{% endif %} +{% endif %} +{% if ferm__tpl_saddr|d() %} + @def $SITEMS = ( @ipfilter( ({{ ferm__tpl_saddr | unique | join(" ") }}) ) ); + @if @ne($SITEMS,"") { +{% if ferm__tpl_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if config.include|d() %} + @include "{{ config.include }}"; +{% elif config.realgoto is undefined or not config.realgoto | bool %} + saddr $SITEMS jump "{{ ferm__tpl_target }}"; +{% elif config.realgoto|d() and config.realgoto | bool %} + saddr $SITEMS realgoto "{{ ferm__tpl_target }}"; +{% endif %} +{% elif ferm__tpl_target in [ 'REJECT' ] %} + saddr $SITEMS REJECT reject-with {{ ferm__tpl_reject_with }}; +{% else %} + saddr $SITEMS {{ ferm__tpl_target }}; +{% endif %} + } +{% elif ferm__tpl_daddr|d() %} + @def $DITEMS = ( @ipfilter( ({{ ferm__tpl_daddr | unique | join(" ") }}) ) ); + @if @ne($DITEMS,"") { +{% if ferm__tpl_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if config.include|d() %} + @include "{{ config.include }}"; +{% elif config.realgoto is undefined or not config.realgoto | bool %} + daddr $DITEMS jump "{{ ferm__tpl_target }}"; +{% elif config.realgoto|d() and config.realgoto | bool %} + daddr $DITEMS realgoto "{{ ferm__tpl_target }}"; +{% endif %} +{% elif ferm__tpl_target in [ 'REJECT' ] %} + daddr $DITEMS REJECT reject-with {{ ferm__tpl_reject_with }}; +{% else %} + daddr $DITEMS {{ ferm__tpl_target }}; +{% endif %} + } +{% else %} +{% if config.accept_any is defined %} +{% if config.accept_any | bool %} +{% if ferm__tpl_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if config.include|d() %} + @include "{{ config.include }}"; +{% elif config.realgoto is undefined or not config.realgoto | bool %} + jump "{{ ferm__tpl_target }}"; +{% elif config.realgoto|d() and config.realgoto | bool %} + realgoto "{{ ferm__tpl_target }}"; +{% endif %} +{% elif ferm__tpl_target in [ 'REJECT' ] %} + REJECT reject-with {{ ferm__tpl_reject_with }}; +{% else %} + {{ ferm__tpl_target }}; +{% endif %} +{% elif not config.accept_any | bool %} + # Connections from any IP address not allowed +{% endif %} +{% else %} +{% if ferm__tpl_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if config.include|d() %} + @include "{{ config.include }}"; +{% elif config.realgoto is undefined or not config.realgoto | bool %} + jump "{{ ferm__tpl_target }}"; +{% elif config.realgoto|d() and config.realgoto | bool %} + realgoto "{{ ferm__tpl_target }}"; +{% endif %} +{% elif ferm__tpl_target in [ 'REJECT' ] %} + REJECT reject-with {{ ferm__tpl_reject_with }}; +{% else %} +{% if ferm__tpl_arguments %} + {{ ferm__tpl_target }}; +{% else %} + # No rule parameters specified +{% endif %} +{% endif %} +{% endif %} +{% endif %} + } +{% endif %} +} +{% endmacro %} +{% set rule_name = (item.value.name | d(item.key)) %} +{% set rule = item.value %} +{% if rule.comment|d() %} +{{ (rule.comment if rule.comment is string else rule.comment | join('\n')) | regex_replace('\n$', '') | comment(prefix='', postfix='') -}} +{% endif %} +{% if rule.rules|d() %} +{% if rule.rules is string %} +{{ rule.rules -}} +{% elif rule.rules is mapping %} +{{ print_rule(rule.rules) -}} +{% elif rule.rules is not string and rule.rules is not mapping %} +{% for element in rule.rules %}{% if not loop.first %} + +{% endif %} +{% if element is string %} +{{ element -}} +{% elif element is mapping %} +{{ print_rule(element) -}} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} +{% if rule.debug|d() | bool or (ansible_local|d() and ansible_local.tags|d() and 'debug' in ansible_local.tags) %} + +{{ ("rule_name: " + (rule_name | to_nice_json)) | replace('\n$','') | comment(prefix='',postfix='') -}} +{{ ("rule: " + (rule | to_nice_json)) | replace('\n$','') | comment(prefix='',postfix='') -}} +{% endif %} diff --git a/templates/lookup/ferm__combined_rules.j2 b/templates/lookup/ferm__combined_rules.j2 new file mode 100644 index 0000000..d206451 --- /dev/null +++ b/templates/lookup/ferm__combined_rules.j2 @@ -0,0 +1,63 @@ +{% macro merge_dict(current_dict, to_merge_dict, dict_key='name') %} +{% set merged_dict = current_dict %} +{% if to_merge_dict %} +{% if to_merge_dict is mapping %} +{% for dict_name in to_merge_dict.keys() | sort %} +{% if to_merge_dict[dict_name][dict_key]|d() %} +{% set _ = merged_dict.update({to_merge_dict[dict_name][dict_key]:(current_dict[to_merge_dict[dict_name][dict_key]]|d({}) | combine(to_merge_dict[dict_name], recursive=True))}) %} +{% elif to_merge_dict[dict_name][dict_key] is undefined %} +{% set _ = merged_dict.update({dict_name:(current_dict[dict_name]|d({}) | combine(to_merge_dict[dict_name], recursive=True))}) %} +{% endif %} +{% endfor %} +{% elif to_merge_dict is not string and to_merge_dict is not mapping %} +{% set flattened_dict = lookup("flattened", to_merge_dict) %} +{% for element in ([ flattened_dict ] if flattened_dict is mapping else flattened_dict) %} +{% if element[dict_key]|d() %} +{% set _ = merged_dict.update({element[dict_key]:(current_dict[element[dict_key]]|d({}) | combine(element, recursive=True))}) %} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} +{{ merged_dict | to_json }} +{% endmacro %} +{% set ferm__tpl_merge_default = (merge_dict({}, ferm__default_rules, 'name') | from_json) %} +{% set ferm__tpl_merge_dependent = (merge_dict(ferm__tpl_merge_default, ferm__dependent_rules, 'name') | from_json) %} +{% set ferm__tpl_fix_dependent = (merge_dict(ferm__tpl_merge_dependent, ferm__fix_dependent_rules, 'name') | from_json) %} +{% set ferm__tpl_merge_all = (merge_dict(ferm__tpl_fix_dependent, ferm__rules, 'name') | from_json) %} +{% set ferm__tpl_merge_group = (merge_dict(ferm__tpl_merge_all, ferm__group_rules, 'name') | from_json) %} +{% set ferm__tpl_rules = (merge_dict(ferm__tpl_merge_group, ferm__host_rules, 'name') | from_json) %} +{% set ferm__tpl_filtered_rules = {} %} +{% for name, params in ferm__tpl_rules.iteritems() %} +{% set rule_name = (params["name"] | d(name)) %} +{% if params["rules"] is undefined %} +{% set _ = params.update({"rules":[]}) %} +{% set ferm__tpl_rule = {} %} +{% for parameter in params.keys() | sort %} +{% if parameter not in [ 'comment', 'rule_state', 'name', 'rules', 'template', 'weight', 'weight_class', 'role', 'role_weight', 'delete', 'when', 'enabled' ] %} +{% if parameter == 'state' %} +{% if params["state"] not in [ 'present', 'absent', 'ignore' ] %} +{% set _ = ferm__tpl_rule.update({parameter:params[parameter]}) %} +{% endif %} +{% else %} +{% set _ = ferm__tpl_rule.update({parameter:params[parameter]}) %} +{% endif %} +{% endif %} +{% endfor %} +{% set _ = params["rules"].append(ferm__tpl_rule) %} +{% endif %} +{% if params["state"] is undefined or params["state"] not in [ 'present', 'absent', 'ignore' ] %} +{% if params["enabled"]|d() %} +{% if params["enabled"]|bool %} +{% set _ = params.update({"state":"present"}) %} +{% else %} +{% set _ = params.update({"state":"absent"}) %} +{% endif %} +{% elif params["rule_state"]|d() %} +{% set _ = params.update({"state":params["rule_state"]}) %} +{% else %} +{% set _ = params.update({"state":"present"}) %} +{% endif %} +{% endif %} +{% set _ = ferm__tpl_filtered_rules.update({rule_name:params}) %} +{% endfor %} +{{ ferm__tpl_filtered_rules | to_yaml }} diff --git a/templates/lookup/ferm__fix_dependent_rules.j2 b/templates/lookup/ferm__fix_dependent_rules.j2 new file mode 100644 index 0000000..725ca15 --- /dev/null +++ b/templates/lookup/ferm__fix_dependent_rules.j2 @@ -0,0 +1,32 @@ +{% if ferm__dependent_rules %} +{% set ferm__tpl_dependent_rules = lookup('flattened', ferm__dependent_rules) %} +{% else %} +{% set ferm__tpl_dependent_rules = {} %} +{% endif %} +{% set ferm__tpl_fixed_rules = {} %} +{% if ferm__tpl_dependent_rules %} +{% if ferm__tpl_dependent_rules is mapping %} +{% set fixed_dict = ferm__tpl_dependent_rules %} +{% if 'name' not in fixed_dict.keys() %} +{% if 'filename' in fixed_dict.keys() %} +{% set _ = fixed_dict.update({"name": ('filename_' + (fixed_dict.filename if fixed_dict.filename is string else fixed_dict.filename[0]))}) %} +{% elif 'dport' in fixed_dict.keys() %} +{% set _ = fixed_dict.update({"name": ('dport_' + (fixed_dict.dport if fixed_dict.dport is string else fixed_dict.dport[0]))}) %} +{% endif %} +{% endif %} +{% set _ = ferm__tpl_fixed_rules.update({fixed_dict['name']:fixed_dict}) %} +{% elif ferm__tpl_dependent_rules is not string and ferm__tpl_dependent_rules is not mapping %} +{% for element in ferm__tpl_dependent_rules %} +{% set fixed_dict = element %} +{% if 'name' not in fixed_dict.keys() %} +{% if 'filename' in fixed_dict.keys() %} +{% set _ = fixed_dict.update({"name": ('filename_' + (fixed_dict.filename if fixed_dict.filename is string else fixed_dict.filename[0]))}) %} +{% elif 'dport' in fixed_dict.keys() %} +{% set _ = fixed_dict.update({"name": ('dport_' + (fixed_dict.dport if fixed_dict.dport is string else fixed_dict.dport[0]))}) %} +{% endif %} +{% endif %} +{% set _ = ferm__tpl_fixed_rules.update({fixed_dict['name']:fixed_dict}) %} +{% endfor %} +{% endif %} +{% endif %} +{{ ferm__tpl_fixed_rules | to_json }} From d04160120c08fdf413a2f1a3cc20b9c031c5df4b Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Wed, 21 Dec 2016 08:46:10 +0100 Subject: [PATCH 02/30] Convert default firewall rules to YAML dictionary --- defaults/main.yml | 295 +++++++++++++--------------------------------- 1 file changed, 82 insertions(+), 213 deletions(-) diff --git a/defaults/main.yml b/defaults/main.yml index 0b41fb9..1eb77a2 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -376,135 +376,68 @@ ferm__combined_weight_map: '{{ ferm__default_weight_map # ]]] # .. envvar:: ferm__default_rules [[[ # -# List of default firewall rules defined on each host. -ferm__default_rules: '{{ ferm__rules_default_policy + - ferm__rules_log + - ferm__rules_hooks + - ferm__rules_variables + - ferm__rules_filter_loopback + - ferm__rules_filter_ansible_controller + - ferm__rules_filter_icmp + - ferm__rules_filter_conntrack + - ferm__rules_filter_syn + - ferm__rules_filter_recent_badguys + - ferm__rules_accept_dhcpv6_client + - ferm__rules_filter_include_legacy + - ferm__rules_filter_recent_scanners + - ferm__rules_filter_reject_all + - ferm__rules_fail2ban + - ferm__rules_forward }}' - - # ]]] -# .. envvar:: ferm__rules_default_policy [[[ -# -# Configure default policies for built-in :command:`iptables` chains. -ferm__rules_default_policy: - - - type: 'default_policy' +# YAML dictionary with default firewall rules defined on each host. +ferm__default_rules: + + 'policy_filter_input': + type: 'default_policy' chain: 'INPUT' - weight: '00' - name: 'filter_input' policy: '{{ ferm__default_policy_input }}' - - type: 'default_policy' + 'policy_filter_forward': + type: 'default_policy' chain: 'FORWARD' - weight: '00' - name: 'filter_forward' policy: '{{ ferm__default_policy_forward }}' - - type: 'default_policy' + 'policy_filter_output': + type: 'default_policy' chain: 'OUTPUT' - weight: '00' - name: 'filter_output' policy: '{{ ferm__default_policy_output }}' - # ]]] -# .. envvar:: ferm__rules_hooks [[[ -# -# Implement custom firewall hooks. -ferm__rules_hooks: - - - type: 'custom' - name: 'hooks' - weight: '00' + 'firewall_hooks': + type: 'custom' comment: 'Run custom hooks at various firewall stages' rules: | @hook pre "run-parts /etc/ferm/hooks/pre.d"; @hook post "run-parts /etc/ferm/hooks/post.d"; @hook flush "run-parts /etc/ferm/hooks/flush.d"; -# ]]] -# .. envvar:: ferm__rules_variables [[[ -# -# Provide custom variables in the firewall configuration. -ferm__rules_variables: - - - type: 'custom' - name: 'variables' - weight: '00' + 'firewall_variables': + type: 'custom' comment: 'Define custom variables available in the firewall' rules: | @def $domains = ({{ ferm__domains | unique | join(" ") }}); @def $ipv4_enabled = {{ "1" if "ip" in ferm__domains else "0" }}; @def $ipv6_enabled = {{ "1" if "ip6" in ferm__domains else "0" }}; -# ]]] -# .. envvar:: ferm__rules_log [[[ -# -# Create custom log function which is used by other firewall rules to log -# packets. -ferm__rules_log: - - - type: 'custom' - name: 'log' - weight: '00' + 'firewall_log': + type: 'custom' comment: 'Custom log function used by other rules' - rule_state: '{{ "present" if (ferm__log | bool) else "absent" }}' rules: | @def &log($msg) = { mod limit limit {{ ferm__log_limit }} limit-burst {{ ferm__log_burst }} {{ ferm__log_target }}; } + rule_state: '{{ "present" if (ferm__log | bool) else "absent" }}' - # ]]] -# .. envvar:: ferm__rules_filter_loopback [[[ -# -# Allow connections from ``localhost``. -ferm__rules_filter_loopback: - - - type: 'accept' - weight: '00' + 'accept_loopback': + type: 'accept' weight_class: 'loopback' - name: 'loopback' interface: 'lo' - # ]]] -# .. envvar:: ferm__rules_filter_ansible_controller [[[ -# -# Allow connections from Ansible Controllers. -ferm__rules_filter_ansible_controller: - - - type: 'ansible_controller' - weight: '01' + 'accept_ansible_controller': + type: 'ansible_controller' weight_class: 'ansible-controller' comment: 'Accept SSH connections from Ansible Controllers' - name: 'rules' dport: '{{ ferm__ansible_controllers_ports }}' interface: '{{ ferm__ansible_controllers_interfaces }}' multiport: True accept_any: False - # ]]] -# .. envvar:: ferm__rules_filter_icmp [[[ -# -# Filter ICMP packets. -ferm__rules_filter_icmp: - - - type: 'hashlimit' - weight: '03' + 'filter_icmp_flood': + type: 'hashlimit' weight_class: 'filter-icmp' - name: 'icmp-flood' protocol: 'icmp' enabled: '{{ ferm__filter_icmp | bool }}' hashlimit: '{{ ferm__filter_icmp_limit }}' @@ -513,27 +446,14 @@ ferm__rules_filter_icmp: hashlimit_target: 'ACCEPT' target: 'DROP' - # ]]] -# .. envvar:: ferm__rules_filter_conntrack [[[ -# -# Enable connection tracking for incoming, forwarded and outgoing traffic. -ferm__rules_filter_conntrack: - - - type: 'connection_tracking' - weight: '05' - weight_class: 'conntrack' + 'connection_tracking': + type: 'connection_tracking' + weight_class: 'connection-tracking' chain: [ 'INPUT', 'OUTPUT', 'FORWARD' ] - # ]]] -# .. envvar:: ferm__rules_filter_syn [[[ -# -# Filter TCP SYN packets. -ferm__rules_filter_syn: - - - type: 'hashlimit' - weight: '07' + 'filter_syn_flood': + type: 'hashlimit' weight_class: 'filter-syn' - name: 'syn-flood' protocol: 'tcp' protocol_syn: True rule_state: '{{ "present" if (ferm__filter_syn | bool) else "absent" }}' @@ -543,16 +463,9 @@ ferm__rules_filter_syn: hashlimit_target: 'RETURN' target: 'DROP' - # ]]] -# .. envvar:: ferm__rules_filter_recent_badguys [[[ -# -# Block all packets marked as "badguys" later in the firewall. -ferm__rules_filter_recent_badguys: - - - type: 'recent' - weight: '09' + 'block_recent_badguys': + type: 'recent' weight_class: 'any-blacklist' - name: 'block-recent-badguys' comment: 'Reject packets marked as "badguys"' rule_state: '{{ "present" if (ferm__filter_recent | bool) else "absent" }}' recent_name: '{{ ferm__filter_recent_name }}' @@ -560,33 +473,18 @@ ferm__rules_filter_recent_badguys: recent_seconds: '{{ ferm__filter_recent_time }}' recent_target: 'REJECT' - - type: 'recent' - weight: '09' + 'clean_recent_badguys': + type: 'recent' weight_class: 'any-blacklist' - name: 'clean-recent-badguys' comment: 'Reject packets marked as "badguys"' rule_state: '{{ "present" if (ferm__filter_recent | bool) else "absent" }}' recent_name: '{{ ferm__filter_recent_name }}' recent_remove: True recent_log: False - # ]]] -# .. envvar:: ferm__rules_accept_dhcpv6_client [[[ -# -# Allow DHCPv6 responses. -# -# Related to: -# -# * https://serverfault.com/questions/513772/getting-an-ip-from-an-ipv6-dhcp-server -# * https://bugzilla.redhat.com/show_bug.cgi?id=656334 -# * https://bugzilla.redhat.com/show_bug.cgi?id=591630 -# -ferm__rules_accept_dhcpv6_client: - - - type: 'accept' - weight: '50' + 'accept_dhcpv6_client': + type: 'accept' weight_class: 'any-service' - filename: 'dhcpv6-client' comment: 'DHCPv6 responses seem to be neither RELATED nor ESTABLISHED.' domain: [ 'ip6' ] saddr: [ 'fe80::/10' ] @@ -595,113 +493,84 @@ ferm__rules_accept_dhcpv6_client: sport: [ 'dhcpv6-server' ] dport: [ 'dhcpv6-client' ] - # ]]] -# .. envvar:: ferm__rules_filter_include_legacy [[[ -# -# Include rules from legacy ferm directory. -ferm__rules_filter_include_legacy: - - - type: 'accept' - weight: '50' - weight_class: 'any-service' - filename: 'jump_legacy-rules' + 'jump_to_legacy_input_rules': + type: 'accept' + weight: '-10' + weight_class: 'reject' + name: 'jump_to_legacy_rules' comment: 'Jump to legacy firewall rules' target: 'debops-legacy-input-rules' rule_state: '{{ "present" if (ferm__include_legacy | bool) else "absent" }}' - - type: 'include' - weight: 'zz' + 'include_legacy_input_rules': + type: 'include' + weight_class: 'post-hook' chain: 'debops-legacy-input-rules' comment: 'Include legacy firewall rules' - name: 'legacy-input-rules' include: '/etc/ferm/filter-input.d/' rule_state: '{{ "present" if (ferm__include_legacy | bool) else "absent" }}' - # ]]] -# .. envvar:: ferm__rules_filter_recent_scanners [[[ -# -# Block potential port scanners that try to access closed ports. -ferm__rules_filter_recent_scanners: - - - type: 'recent' + 'block_portscans': + type: 'recent' weight: '85' - name: 'block-portscans' comment: 'Mark potential port scanners as bad guys' recent_set_name: '{{ ferm__filter_recent_name }}' rule_state: '{{ "present" if (ferm__mark_portscan | bool) else "absent" }}' - # ]]] -# .. envvar:: ferm__rules_filter_reject_all [[[ -# -# Reject all incoming packets not handled by previous rules. -ferm__rules_filter_reject_all: - - - type: 'reject' - weight: '99' - weight_class: 'any-reject' + 'reject_all': + type: 'reject' - # ]]] -# .. envvar:: ferm__rules_fail2ban [[[ -# -# :program:`fail2ban` support rules for ferm. -ferm__rules_fail2ban: - - type: 'fail2ban' - weight: 'zz' + 'fail2ban-hook': + type: 'fail2ban' comment: 'Reload fail2ban rules' rule_state: '{{ "present" if (ferm__fail2ban | bool) else "absent" }}' + rules: | + @hook post "type fail2ban-server > /dev/null && (fail2ban-client ping > /dev/null && fail2ban-client reload > /dev/null || true) || true"; + @hook flush "type fail2ban-server > /dev/null && (fail2ban-client ping > /dev/null && fail2ban-client reload > /dev/null || true) || true"; + weight_class: 'post-hook' - # ]]] -# .. envvar:: ferm__rules_forward [[[ -# -# Manage packet forwarding to other hosts/containers. -ferm__rules_forward: - - - chain: 'FORWARD' + 'forward_external_in': + chain: 'FORWARD' type: 'accept' + weight: '1' + weight_class: 'any-forward' interface_present: '{{ ferm__external_interfaces | unique }}' - weight: '10' - role: 'forward' - role_weight: '10' - name: 'external_in' comment: 'Forward incoming traffic to other hosts' - rule_state: '{{ "present" if ( - ferm__forward_accept|bool and ( - (ferm__forward|d(ferm_forward) | bool) or - (ansible_local|d() and ansible_local.ferm|d() and - ansible_local.ferm.forward | bool))) - else "absent" }}' - - - chain: 'FORWARD' + rule_state: '{{ "present" + if (ferm__forward_accept|bool and ferm__forward | bool) + else "ignore" }}' + + 'forward_external_out': + chain: 'FORWARD' type: 'accept' + weight: '2' + weight_class: 'any-forward' outerface_present: '{{ ferm__external_interfaces | unique }}' - weight: '10' - role: 'forward' - role_weight: '20' - name: 'external_out' comment: 'Forward outgoing traffic to other hosts' - rule_state: '{{ "present" if ( - ferm__forward_accept|bool and ( - (ferm__forward|d(ferm_forward) | bool) or - (ansible_local|d() and ansible_local.ferm|d() and - ansible_local.ferm.forward | bool))) - else "absent" }}' - - - chain: 'FORWARD' + rule_state: '{{ "present" + if (ferm__forward_accept|bool and ferm__forward | bool) + else "ignore" }}' + + 'forward_internal': + chain: 'FORWARD' type: 'accept' interface_present: '{{ ferm__internal_interfaces }}' outerface_present: '{{ ferm__internal_interfaces }}' - weight: '10' - role: 'forward' - role_weight: '30' - name: 'internal' + weight: '3' + weight_class: 'any-forward' comment: 'Forward internal traffic between hosts' - rule_state: '{{ "present" if ( - ferm__forward_accept|bool and ( - (ferm__forward|d(ferm_forward) | bool) or - (ansible_local|d() and ansible_local.ferm|d() and - ansible_local.ferm.forward | bool))) - else "absent" }}' + rule_state: '{{ "present" + if (ferm__forward_accept|bool and ferm__forward | bool) + else "ignore" }}' + + 'fix_bootpc_checksum': + type: 'custom' + rules: | + # Add checksums to BOOTP packets from virtual machines and containers. + # https://www.redhat.com/archives/libvir-list/2010-August/msg00035.html + @hook post "iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill"; + rule_state: 'ignore' # ]]] # .. envvar:: ferm__custom_files [[[ From 1bfbd2e594287a12e2221d30589753d5b238b1ce Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Wed, 21 Dec 2016 22:17:49 +0100 Subject: [PATCH 03/30] Move ferm 'forward' logic to default variables --- defaults/main.yml | 10 +++++++-- templates/etc/ansible/facts.d/ferm.fact.j2 | 2 +- .../etc/network/if-pre-up.d/ferm-forward.j2 | 21 +++++++------------ templates/etc/sysctl.d/30-ferm.conf.j2 | 9 +------- 4 files changed, 17 insertions(+), 25 deletions(-) diff --git a/defaults/main.yml b/defaults/main.yml index 1eb77a2..95cd1d0 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -606,7 +606,10 @@ ferm__fail2ban: True # Enable forwarding in :command:`ip(6)tables`. This option can be used by other # roles and it's status is saved in Ansible local facts, which will override # variable status from role defaults or inventory. -ferm__forward: False +ferm__forward: '{{ ansible_local.ferm.forward + if (ansible_local|d() and ansible_local.ferm|d() and + ansible_local.ferm.forward|d()) + else "False" }}' # ]]] # .. envvar:: ferm__forward_accept [[[ @@ -614,7 +617,10 @@ ferm__forward: False # Should traffic be forwarded between the other hosts/containers if # :envvar:`ferm__forward` is ``True``? # Refer to :envvar:`ferm__rules_forward` for details. -ferm__forward_accept: False +ferm__forward_accept: '{{ ansible_local.ferm.forward + if (ansible_local|d() and ansible_local.ferm|d() and + ansible_local.ferm.forward|d()) + else "False" }}' # ]]] # .. envvar:: ferm__external_interfaces [[[ diff --git a/templates/etc/ansible/facts.d/ferm.fact.j2 b/templates/etc/ansible/facts.d/ferm.fact.j2 index 6d7c4df..cb71aa6 100644 --- a/templates/etc/ansible/facts.d/ferm.fact.j2 +++ b/templates/etc/ansible/facts.d/ferm.fact.j2 @@ -1,5 +1,5 @@ {% set ferm__tpl_forward = False %} -{% if ((ferm__forward|d(ferm_forward) | bool) or +{% if (ferm__forward | bool or (ansible_local|d() and ansible_local.ferm|d() and (ansible_local.ferm.forward|d() | bool))) %} {% set ferm__tpl_forward = True %} diff --git a/templates/etc/network/if-pre-up.d/ferm-forward.j2 b/templates/etc/network/if-pre-up.d/ferm-forward.j2 index b5b2254..1fd8cb8 100644 --- a/templates/etc/network/if-pre-up.d/ferm-forward.j2 +++ b/templates/etc/network/if-pre-up.d/ferm-forward.j2 @@ -2,25 +2,18 @@ # {{ ansible_managed }} -{% if ferm__enabled | bool %} -{% if (ferm__forward|d(ferm_forward) | bool) or - (ansible_local|d() and ansible_local.ferm|d() and - (ansible_local.ferm.forward|d() | bool)) %} -{% set ferm__tpl_interfaces = (ferm__external_interfaces|d([]) | list) + - (ferm__internal_interfaces|d([]) | list) %} -{% for interface in ferm__tpl_interfaces | unique %} -{% if interface and hostvars[inventory_hostname]["ansible_" + interface] | d() %} +{% if ferm__enabled | bool and ferm__forward | bool %} +{% set ferm__tpl_interfaces = (ferm__external_interfaces|d([]) | list) + + (ferm__internal_interfaces|d([]) | list) %} +{% for interface in ferm__tpl_interfaces | unique %} +{% if interface and hostvars[inventory_hostname]["ansible_" + interface] | d() %} # Force Router Advertisement support on {{ interface }} interface if [ "$IFACE" = "{{ interface }}" ] ; then sysctl -w net/ipv6/conf/{{ interface }}/accept_ra=2 fi -{% endif %} -{% endfor %} -{% else %} -# Network forwarding in ip(6)tables is not enabled -{% endif %} +{% endif %} +{% endfor %} {% else %} # ferm support is disabled {% endif %} - diff --git a/templates/etc/sysctl.d/30-ferm.conf.j2 b/templates/etc/sysctl.d/30-ferm.conf.j2 index 94eac5f..9a7c80b 100644 --- a/templates/etc/sysctl.d/30-ferm.conf.j2 +++ b/templates/etc/sysctl.d/30-ferm.conf.j2 @@ -1,8 +1,6 @@ # {{ ansible_managed }} -{% if ferm__enabled | bool %} -{% if ((ferm__forward|d(ferm_forward) | bool) or - (ansible_local|d() and ansible_local.ferm|d() and (ansible_local.ferm.forward|d() | bool))) %} +{% if ferm__enabled | bool and ferm__forward|bool %} # Enable IPv4 forwarding net.ipv4.ip_forward = 1 @@ -13,11 +11,6 @@ net.ipv6.conf.all.forwarding = 1 # Enable IPv6 autoconfiguration (SLAAC) net.ipv6.conf.default.accept_ra = 1 net.ipv6.conf.all.accept_ra = 1 - -{% else %} -# Forwarding in ip(6)tables is not enabled - -{% endif %} {% else %} # ferm support is disabled {% endif %} From 92e30dee65069f6767f43287ca551ff2b390cc08 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Wed, 21 Dec 2016 22:18:16 +0100 Subject: [PATCH 04/30] Add more rule weight classes --- defaults/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/defaults/main.yml b/defaults/main.yml index 95cd1d0..c56af63 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -351,8 +351,10 @@ ferm__default_weight_map: 'filter-syn': '25' 'any-blacklist': '30' 'sshd-chain': '40' - 'accept': '50' + 'any-forward': '60' 'default': '100' + 'accept': '100' + 'any-service': '100' 'reject': '900' 'any-reject': '900' 'post-hook': '950' From e1e0f48f13536b95a7fd89bb0180d8885ac401a9 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Wed, 21 Dec 2016 23:20:03 +0100 Subject: [PATCH 05/30] Replace remove variable with its value --- tasks/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks/main.yml b/tasks/main.yml index 2fe2214..4b323c7 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -101,7 +101,7 @@ - name: Remove iptables INPUT rules if requested file: - path: '/etc/ferm/filter-input.d/{{ ferm__weight_map[item.weight_class|d()] | d(item.weight | d(ferm__default_weight)) }}_{{ item.filename | d(item.type + "_" + item.name | d((item.dport[0] if item.dport|d() else "rules"))) }}.conf' + path: '/etc/ferm/filter-input.d/{{ ferm__weight_map[item.weight_class|d()] | d(item.weight | d("50")) }}_{{ item.filename | d(item.type + "_" + item.name | d((item.dport[0] if item.dport|d() else "rules"))) }}.conf' state: 'absent' with_flattened: - '{{ ferm_input_list }}' @@ -115,7 +115,7 @@ - name: Configure iptables INPUT rules template: src: 'etc/ferm/filter-input.d/{{ item.type }}.conf.j2' - dest: '/etc/ferm/filter-input.d/{{ ferm__weight_map[item.weight_class|d()] | d(item.weight | d(ferm__default_weight)) }}_{{ item.filename | d(item.type + "_" + item.name | d((item.dport[0] if item.dport|d() else "rules"))) }}.conf' + dest: '/etc/ferm/filter-input.d/{{ ferm__weight_map[item.weight_class|d()] | d(item.weight | d("50")) }}_{{ item.filename | d(item.type + "_" + item.name | d((item.dport[0] if item.dport|d() else "rules"))) }}.conf' owner: 'root' group: 'adm' mode: '0644' From 9dfc2d7149275048f26a3bc855103c3cb9076cf1 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Wed, 21 Dec 2016 23:39:25 +0100 Subject: [PATCH 06/30] Fix removed variable reference in Changelog --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index ef1a9af..6dea254 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -83,7 +83,7 @@ Added - Write missing role documentation. [ganto_, ypid_, drybjed_] -- Allow to disable :envvar:`ferm__rules_forward` using +- Allow to disable ``ferm__rules_forward`` using :envvar:`ferm__forward_accept`. [ypid_] Changed From 92b5a27eed72f0f2633b1d08184ef2e4ea777300 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Wed, 8 Feb 2017 18:39:25 +0100 Subject: [PATCH 07/30] Reorganization of the rule template --- templates/etc/ferm/rules.d/rule.conf.j2 | 482 ++++++++++++------------ 1 file changed, 250 insertions(+), 232 deletions(-) diff --git a/templates/etc/ferm/rules.d/rule.conf.j2 b/templates/etc/ferm/rules.d/rule.conf.j2 index f77996d..5520abd 100644 --- a/templates/etc/ferm/rules.d/rule.conf.j2 +++ b/templates/etc/ferm/rules.d/rule.conf.j2 @@ -1,185 +1,189 @@ # {{ ansible_managed }} +{% macro print_list(elements, prefix='', postfix='') %}{% if elements | length == 1 %}{{ ((prefix + ' ') if prefix else '') + elements | join(' ') + ((' ' + postfix) if postfix else '') }}{% else %}{{ ((prefix + ' ') if prefix else '') + '(' + (elements | join(' ')) + ')' + ((' ' + postfix) if postfix else '') }}{% endif %}{% endmacro %} {% macro print_rule(config) %} {# Domain, table, chain #} {# ==================== #} -{% set ferm__tpl_domain = [] %} -{% set ferm__tpl_table = [] %} -{% set ferm__tpl_chain = [] %} +{% set ferm__tpl_config = { + 'type': 'accept', + 'domain_args': [], + 'target': 'ACCEPT', + 'hashlimit_target': 'RETURN', + 'recent_target': 'NOP', + 'reject_with': 'icmp-admin-prohibited', + 'saddr': [], + 'daddr': [], + 'sport': [], + 'dport': [] +} %} +{% if config.type|d() %} +{% set _ = ferm__tpl_config.update({'type': config.type }) %} +{% endif %} {% if config.domain|d() %} -{% set _ = ferm__tpl_domain.extend(([ config.domain ] if config.domain is string else config.domain)) %} +{% set _ = ferm__tpl_config.update({'domain': ([ config.domain ] if config.domain is string else config.domain) }) %} {% elif config.domains|d() %} -{% set _ = ferm__tpl_domain.extend(([ config.domains ] if config.domains is string else config.domains)) %} -{% endif %} -{% if config.table|d() %} -{% set _ = ferm__tpl_table.extend(([ config.table ] if config.table is string else config.table)) %} -{% elif config.tables|d() %} -{% set _ = ferm__tpl_table.extend(([ config.tables ] if config.tables is string else config.tables)) %} -{% endif %} -{% if config.chain|d() %} -{% set _ = ferm__tpl_chain.extend(([ config.chain ] if config.chain is string else config.chain)) %} -{% elif config.chains|d() %} -{% set _ = ferm__tpl_chain.extend(([ config.chains ] if config.chains is string else config.chains)) %} -{% endif %} -{% if not ferm__tpl_domain %} -{% set ferm__tpl_domain = ferm__domains %} -{% endif %} -{% if not ferm__tpl_table %} -{% set ferm__tpl_table = [ 'filter' ] %} -{% endif %} -{% if not ferm__tpl_chain %} -{% set ferm__tpl_chain = [ 'INPUT' ] %} +{% set _ = ferm__tpl_config.update({'domain': ([ config.domains ] if config.domains is string else config.domains) }) %} +{% else %} +{% set _ = ferm__tpl_config.update({'domain': ([ ferm__domains ] if ferm__domains is string else ferm__domains) }) %} {% endif %} -{% set ferm__tpl_domain_args = [] %} -{% if ferm__tpl_domain %} -{% if ferm__tpl_domain | length == 1 %} -{% set _ = ferm__tpl_domain_args.append("domain " + ferm__tpl_domain | join(" ")) %} +{% if ferm__tpl_config['type'] != 'dmz' %} +{% if config.table|d() %} +{% set _ = ferm__tpl_config.update({'table': ([ config.table ] if config.table is string else config.table) }) %} +{% elif config.tables|d() %} +{% set _ = ferm__tpl_config.update({'table': ([ config.tables ] if config.tables is string else config.tables) }) %} {% else %} -{% set _ = ferm__tpl_domain_args.append("domain (" + ferm__tpl_domain | join(" ") + ")") %} +{% set _ = ferm__tpl_config.update({'table': [ 'filter' ] }) %} {% endif %} -{% endif %} -{% if ferm__tpl_table %} -{% if ferm__tpl_table | length == 1 %} -{% set _ = ferm__tpl_domain_args.append("table " + ferm__tpl_table | join(" ")) %} +{% if config.chain|d() %} +{% set _ = ferm__tpl_config.update({'chain': ([ config.chain ] if config.chain is string else config.chain) }) %} +{% elif config.chains|d() %} +{% set _ = ferm__tpl_config.update({'chain': ([ config.chains ] if config.chains is string else config.chains) }) %} {% else %} -{% set _ = ferm__tpl_domain_args.append("table (" + ferm__tpl_table | join(" ") + ")") %} +{% set _ = ferm__tpl_config.update({'chain': [ 'INPUT' ] }) %} {% endif %} {% endif %} -{% if ferm__tpl_chain %} -{% if ferm__tpl_chain | length == 1 %} -{% set _ = ferm__tpl_domain_args.append("chain " + ferm__tpl_chain | join(" ")) %} -{% else %} -{% set _ = ferm__tpl_domain_args.append("chain (" + ferm__tpl_chain | join(" ") + ")") %} -{% endif %} +{% if ferm__tpl_config['domain']|d() %} +{% set _ = ferm__tpl_config['domain_args'].append(print_list(ferm__tpl_config['domain'], prefix='domain')) %} +{% endif %} +{% if ferm__tpl_config['table']|d() %} +{% set _ = ferm__tpl_config['domain_args'].append(print_list(ferm__tpl_config['table'], prefix='table')) %} +{% endif %} +{% if ferm__tpl_config['chain']|d() %} +{% set _ = ferm__tpl_config['domain_args'].append(print_list(ferm__tpl_config['chain'], prefix='chain')) %} {% endif %} {# Rule arguments #} {# ============== #} -{% set ferm__tpl_target = 'ACCEPT' %} -{% set ferm__tpl_hashlimit_target = 'RETURN' %} -{% set ferm__tpl_recent_target = 'NOP' %} -{% set ferm__tpl_reject_with = 'icmp-admin-prohibited' %} -{% set ferm__tpl_interface = [] %} -{% set ferm__tpl_interface_present = [] %} -{% set ferm__tpl_outerface = [] %} -{% set ferm__tpl_outerface_present = [] %} -{% set ferm__tpl_protocol = [] %} -{% set ferm__tpl_protocol_syn = [] %} -{% set ferm__tpl_saddr = [] %} -{% set ferm__tpl_daddr = [] %} -{% set ferm__tpl_sport = [] %} -{% set ferm__tpl_dport = [] %} -{% set ferm__tpl_state = [] %} -{% set ferm__tpl_tracking_invalid_target = (config.tracking_invalid_target | d('DROP')) %} -{% set ferm__tpl_tracking_active_target = (config.tracking_active_target | d('ACCEPT')) %} -{% set ferm__tpl_tracking_module = (config.tracking_module | d('conntrack')) %} -{% set ferm__tpl_recent_name = '' %} -{% set ferm__tpl_recent_set_name = '' %} -{% set ferm__tpl_recent_update = False %} -{% set ferm__tpl_recent_remove = False %} -{% set ferm__tpl_recent_seconds = '' %} -{% set ferm__tpl_recent_hitcount = '' %} +{% if config.saddr|d() %} +{% set _ = ferm__tpl_config['saddr'].extend(([ config.saddr ] if config.saddr is string else config.saddr)) %} +{% endif %} +{% if config.daddr|d() %} +{% set _ = ferm__tpl_config['daddr'].extend(([ config.daddr ] if config.daddr is string else config.daddr)) %} +{% endif %} +{% if config.sport|d() %} +{% set _ = ferm__tpl_config['sport'].extend(([ config.sport ] if config.sport is string else config.sport)) %} +{% endif %} +{% if config.dport|d() %} +{% set _ = ferm__tpl_config['dport'].extend(([ config.dport ] if config.dport is string else config.dport)) %} +{% endif %} {% if config.recent_name|d() %} -{% set ferm__tpl_recent_name = config.recent_name %} +{% set _ = ferm__tpl_config.update({'recent_name': config.recent_name}) %} {% elif config.recent_set_name|d() %} -{% set ferm__tpl_recent_set_name = config.recent_set_name %} +{% set _ = ferm__tpl_config.update({'recent_set_name': config.recent_set_name}) %} {% endif %} {% if config.recent_update|d() and config.recent_update | bool %} -{% set ferm__tpl_recent_update = True %} +{% set _ = ferm__tpl_config.update({'recent_update': True}) %} {% endif %} {% if config.recent_remove|d() and config.recent_remove | bool %} -{% set ferm__tpl_recent_remove = True %} +{% set _ = ferm__tpl_config.update({'recent_remove': True}) %} {% endif %} {% if config.recent_seconds|d() %} -{% set ferm__tpl_recent_seconds = config.recent_seconds | string %} +{% set _ = ferm__tpl_config.update({'recent_seconds': config.recent_seconds | string}) %} {% endif %} {% if config.recent_hitcount|d() %} -{% set ferm__tpl_recent_hitcount = config.recent_hitcount | string %} +{% set _ = ferm__tpl_config.update({'recent_hitcount': config.recent_hitcount | string}) %} {% endif %} {% if config.recent_target|d() %} -{% set ferm__tpl_recent_target = config.recent_target %} +{% set _ = ferm__tpl_config.update({'recent_target': config.recent_target}) %} +{% endif %} +{% if config.subchain|d() %} +{% set _ = ferm__tpl_config.update({'subchain': (ferm__tpl_config['type'] + "-" + config.name | d((ferm__tpl_config['dport'][0] if ferm__tpl_config['dport']|d() else "rules")))}) %} {% endif %} -{% set ferm__tpl_subchain = (config.type|d('accept') + "-" + config.name | d((config.dport[0] if config.dport|d() else "rules"))) %} {% if config.interface|d() %} -{% set _ = ferm__tpl_interface.extend(([ config.interface ] if config.interface is string else config.interface)) %} +{% set _ = ferm__tpl_config.update({'interface': ([ config.interface ] if config.interface is string else config.interface) }) %} {% elif config.interfaces|d() %} -{% set _ = ferm__tpl_interface.extend(([ config.interfaces ] if config.interfaces is string else config.interfaces)) %} +{% set _ = ferm__tpl_config.update({'interface': ([ config.interfaces ] if config.interfaces is string else config.interfaces) }) %} +{% endif %} +{% if ferm__tpl_config['type'] == 'connection_tracking' %} +{% set _ = ferm__tpl_config.update({'tracking_invalid_target': (config.tracking_invalid_target | d('DROP'))}) %} +{% set _ = ferm__tpl_config.update({'tracking_active_target': (config.tracking_active_target | d('ACCEPT'))}) %} +{% set _ = ferm__tpl_config.update({'tracking_module': (config.tracking_module | d('conntrack'))}) %} +{% if ferm__tpl_config['tracking_module'] == 'state' %} +{% set _ = ferm__tpl_config.update({'tracking_module_command': 'mod state state'}) %} +{% else %} +{% set _ = ferm__tpl_config.update({'tracking_module_command': 'mod conntrack ctstate'}) %} +{% endif %} {% endif %} -{% if ferm__tpl_tracking_module == 'state' %} -{% set ferm__tpl_tracking_module_command = 'mod state state' %} -{% else %} -{% set ferm__tpl_tracking_module_command = 'mod conntrack ctstate' %} +{% if ferm__tpl_config['type'] == 'dmz' %} +{% if config.public_ip|d() %} +{% set _ = ferm__tpl_config.update({'public_ip': ([ config.public_ip ] if config.public_ip is string else config.public_ip) }) %} +{% endif %} +{% if config.private_ip|d() %} +{% set _ = ferm__tpl_config.update({'private_ip': ([ config.private_ip ] if config.private_ip is string else config.private_ip) }) %} +{% endif %} +{% if config.port|d() %} +{% set _ = ferm__tpl_config.update({'dmz_ports': ([ config.port ] if config.port is string else config.port) }) %} +{% elif config.ports|d() %} +{% set _ = ferm__tpl_config.update({'dmz_ports': ([ config.ports ] if config.ports is string else config.ports) }) %} +{% endif %} {% endif %} {% if config.interface_present|d() %} {% if config.interface_present is string %} {% if hostvars[inventory_hostname]["ansible_" + config.interface_present]|d() %} -{% set ferm__tpl_interface_present = [ config.interface_present ] %} +{% set _ = ferm__tpl_config.update({'interface_present': [ config.interface_present ] }) %} {% endif %} {% else %} {% for interface in config.interface_present %} {% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} -{% set _ = ferm__tpl_interface_present.append(interface) %} +{% set _ = ferm__tpl_config.update({'interface_present': [ interface ] }) %} {% endif %} {% endfor %} {% endif %} {% elif config.interfaces_present|d() %} {% if config.interfaces_present is string %} {% if hostvars[inventory_hostname]["ansible_" + config.interfaces_present]|d() %} -{% set ferm__tpl_interface_present = [ config.interfaces_present ] %} +{% set _ = ferm__tpl_config.update({'interface_present': [ config.interface_present ] }) %} {% endif %} {% else %} {% for interface in config.interfaces_present %} {% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} -{% set _ = ferm__tpl_interface_present.append(interface) %} +{% set _ = ferm__tpl_config.update({'interface_present': [ interface ] }) %} {% endif %} {% endfor %} {% endif %} {% endif %} {% if config.outerface|d() %} -{% set _ = ferm__tpl_outerface.extend(([ config.outerface ] if config.outerface is string else config.outerface)) %} +{% set _ = ferm__tpl_config.update({'outerface': ([ config.outerface ] if config.outerface is string else config.outerface) }) %} {% elif config.outerfaces|d() %} -{% set _ = ferm__tpl_outerface.extend(([ config.outerfaces ] if config.outerfaces is string else config.outerfaces)) %} +{% set _ = ferm__tpl_config.update({'outerface': ([ config.outerfaces ] if config.outerfaces is string else config.outerfaces) }) %} {% endif %} {% if config.outerface_present|d() %} {% if config.outerface_present is string %} {% if hostvars[inventory_hostname]["ansible_" + config.outerface_present]|d() %} -{% set ferm__tpl_outerface_present = [ config.outerface_present ] %} +{% set _ = ferm__tpl_config.update({'outerface_present': [ config.outerface_present ] }) %} {% endif %} {% else %} {% for outerface in config.outerface_present %} {% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} -{% set _ = ferm__tpl_outerface_present.append(outerface) %} +{% set _ = ferm__tpl_config.update({'outerface_present': [ outerface ] }) %} {% endif %} {% endfor %} {% endif %} {% elif config.outerfaces_present|d() %} {% if config.outerfaces_present is string %} {% if hostvars[inventory_hostname]["ansible_" + config.outerfaces_present]|d() %} -{% set ferm__tpl_outerface_present = [ config.outerfaces_present ] %} +{% set _ = ferm__tpl_config.update({'outerface_present': [ config.outerfaces_present ] }) %} {% endif %} {% else %} {% for outerface in config.outerfaces_present %} {% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} -{% set _ = ferm__tpl_outerface_present.append(outerface) %} +{% set _ = ferm__tpl_config.update({'outerface_present': [ outerface ] }) %} {% endif %} {% endfor %} {% endif %} {% endif %} {% if config.protocol|d() %} -{% set _ = ferm__tpl_protocol.extend(([ config.protocol ] if config.protocol is string else config.protocol)) %} +{% set _ = ferm__tpl_config.update({'protocol': ([ config.protocol ] if config.protocol is string else config.protocol) }) %} {% elif config.protocols|d() %} -{% set _ = ferm__tpl_protocol.extend(([ config.protocols ] if config.protocols is string else config.protocols)) %} +{% set _ = ferm__tpl_config.update({'protocol': ([ config.protocols ] if config.protocols is string else config.protocols) }) %} {% endif %} -{% if config.protocol_syn is defined %} +{% if config.protocol_syn|d() %} {% if config.protocol_syn | bool %} -{% set ferm__tpl_protocol_syn = [ 'syn' ] %} +{% set _ = ferm__tpl_config.update({'protocol_syn': [ 'syn' ] }) %} {% elif not config.protocol_syn | bool %} -{% set ferm__tpl_protocol_syn = [ '! syn' ] %} +{% set _ = ferm__tpl_config.update({'protocol_syn': [ '! syn' ] }) %} {% endif %} {% endif %} -{% if config.saddr|d() %} -{% set _ = ferm__tpl_saddr.extend(([ config.saddr ] if config.saddr is string else config.saddr)) %} -{% endif %} -{% if config.type|d('accept') == 'ansible_controller' %} +{% if ferm__tpl_config['type'] == 'ansible_controller' %} {% set ferm__tpl_ansible_controllers = [] %} {% if ansible_local|d() and ansible_local.core|d() and ansible_local.core.ansible_controllers|d() %} {% set _ = ferm__tpl_ansible_controllers.extend(ansible_local.core.ansible_controllers) %} @@ -196,139 +200,104 @@ {% set _ = ferm__tpl_ansible_controllers.extend(([ config.ansible_controllers ] if config.ansible_controllers is string else config.ansible_controllers)) %} {% endif %} {% if ferm__tpl_ansible_controllers %} -{% set _ = ferm__tpl_saddr.extend(ferm__tpl_ansible_controllers) %} +{% set _ = ferm__tpl_config['saddr'].extend(ferm__tpl_ansible_controllers) %} {% endif %} {% endif %} -{% if config.daddr|d() %} -{% set _ = ferm__tpl_daddr.extend(([ config.daddr ] if config.daddr is string else config.daddr)) %} -{% endif %} -{% if config.sport|d() %} -{% set _ = ferm__tpl_sport.extend(([ config.sport ] if config.sport is string else config.sport)) %} -{% endif %} -{% if config.dport|d() %} -{% set _ = ferm__tpl_dport.extend(([ config.dport ] if config.dport is string else config.dport)) %} -{% endif %} {% if config.state|d() %} -{% set _ = ferm__tpl_state.extend(([ config.state ] if config.state is string else config.state)) %} +{% set _ = ferm__tpl_config.update({'state': ([ config.state ] if config.state is string else config.state) }) %} {% endif %} {% if config.target|d() %} -{% set ferm__tpl_target = config.target %} +{% set _ = ferm__tpl_config.update({'target': config.target}) %} {% endif %} {% if config.hashlimit_target|d() %} -{% set ferm__tpl_hashlimit_target = config.hashlimit_target %} +{% set _ = ferm__tpl_config.update({'hashlimit_target': config.hashlimit_target}) %} {% endif %} {% if config.reject_with|d() %} -{% set ferm__tpl_reject_with = config.reject_with %} +{% set _ = ferm__tpl_config.update({'reject_with': config.reject_with}) %} {% endif %} {% if config.subchain is defined %} {% if config.subchain | bool %} -{% set ferm__tpl_subchain = config.subchain %} +{% set _ = ferm__tpl_config.update({'subchain': config.subchain}) %} {% else %} {% if config.hashlimit|d() %} -{% set ferm__tpl_subchain = (config.type + "-" + config.hashlimit_name | d(config.name | d(rule_name))) %} -{% else %} -{% set ferm__tpl_subchain = '' %} +{% set _ = ferm__tpl_config.update({'subchain': (ferm__tpl_config['type'] + "-" + config.hashlimit_name | d(config.name | d(rule_name)))}) %} {% endif %} {% endif %} {% endif %} {% if config.hashlimit|d() %} -{% set ferm__tpl_subchain = (config.type + "-" + config.hashlimit_name | d(config.name | d(rule_name))) %} +{% set _ = ferm__tpl_config.update({'subchain': (ferm__tpl_config['type'] + "-" + config.hashlimit_name | d(config.name | d(rule_name)))}) %} {% endif %} {% set ferm__tpl_recent_args = [] %} -{% if ferm__tpl_recent_name %} -{% set _ = ferm__tpl_recent_args.append('name "' + ferm__tpl_recent_name + '"') %} -{% elif ferm__tpl_recent_set_name %} -{% set _ = ferm__tpl_recent_args.append('set name "' + ferm__tpl_recent_set_name + '"') %} +{% if ferm__tpl_config['recent_name']|d() %} +{% set _ = ferm__tpl_recent_args.append('name "' + ferm__tpl_config['recent_name'] + '"') %} +{% elif ferm__tpl_config['recent_set_name']|d() %} +{% set _ = ferm__tpl_recent_args.append('set name "' + ferm__tpl_config['recent_set_name'] + '"') %} {% endif %} -{% if ferm__tpl_recent_update | bool %} +{% if (ferm__tpl_config['recent_update']|d(False)) | bool %} {% set _ = ferm__tpl_recent_args.append('update') %} {% endif %} -{% if ferm__tpl_recent_remove | bool %} +{% if (ferm__tpl_config['recent_remove']|d(False)) | bool %} {% set _ = ferm__tpl_recent_args.append('remove') %} {% endif %} -{% if ferm__tpl_recent_seconds %} -{% set _ = ferm__tpl_recent_args.append('seconds ' + ferm__tpl_recent_seconds) %} +{% if ferm__tpl_config['recent_seconds']|d() %} +{% set _ = ferm__tpl_recent_args.append('seconds ' + ferm__tpl_config['recent_seconds']) %} {% endif %} -{% if ferm__tpl_recent_hitcount %} -{% set _ = ferm__tpl_recent_args.append('hitcount ' + ferm__tpl_recent_hitcount) %} +{% if ferm__tpl_config['recent_hitcount']|d() %} +{% set _ = ferm__tpl_recent_args.append('hitcount ' + ferm__tpl_config['recent_hitcount']) %} {% endif %} {% set ferm__tpl_arguments = [] %} -{% if ferm__tpl_interface %} -{% if ferm__tpl_interface | length == 1 %} -{% set _ = ferm__tpl_arguments.append("interface " + ferm__tpl_interface | join(" ")) %} -{% else %} -{% set _ = ferm__tpl_arguments.append("interface (" + ferm__tpl_interface | join(" ") + ")") %} -{% endif %} -{% elif ferm__tpl_interface_present %} -{% if ferm__tpl_interface_present | length == 1 %} -{% set _ = ferm__tpl_arguments.append("interface " + ferm__tpl_interface_present | join(" ")) %} -{% else %} -{% set _ = ferm__tpl_arguments.append("interface (" + ferm__tpl_interface_present | join(" ") + ")") %} -{% endif %} -{% endif %} -{% if ferm__tpl_outerface %} -{% if ferm__tpl_outerface | length == 1 %} -{% set _ = ferm__tpl_arguments.append("outerface " + ferm__tpl_outerface | join(" ")) %} -{% else %} -{% set _ = ferm__tpl_arguments.append("outerface (" + ferm__tpl_outerface | join(" ") + ")") %} -{% endif %} -{% elif ferm__tpl_outerface_present %} -{% if ferm__tpl_outerface_present | length == 1 %} -{% set _ = ferm__tpl_arguments.append("outerface " + ferm__tpl_outerface_present | join(" ")) %} -{% else %} -{% set _ = ferm__tpl_arguments.append("outerface (" + ferm__tpl_outerface_present | join(" ") + ")") %} -{% endif %} -{% endif %} -{% if ferm__tpl_protocol %} -{% if ferm__tpl_protocol | length == 1 %} -{% set _ = ferm__tpl_arguments.append("protocol " + ferm__tpl_protocol | join(" ")) %} -{% else %} -{% set _ = ferm__tpl_arguments.append("protocol (" + ferm__tpl_protocol | join(" ") + ")") %} -{% endif %} -{% elif not ferm__tpl_protocol and (ferm__tpl_sport or ferm__tpl_dport) %} +{% if ferm__tpl_config['interface']|d() %} +{% set _ = ferm__tpl_arguments.append(print_list(ferm__tpl_config['interface'], prefix='interface')) %} +{% elif ferm__tpl_config['interface_present']|d() %} +{% set _ = ferm__tpl_arguments.append(print_list(ferm__tpl_config['interface_present'], prefix='interface')) %} +{% endif %} +{% if ferm__tpl_config['outerface']|d() %} +{% set _ = ferm__tpl_arguments.append(print_list(ferm__tpl_config['outerface'], prefix='outerface')) %} +{% elif ferm__tpl_config['outerface_present']|d() %} +{% set _ = ferm__tpl_arguments.append(print_list(ferm__tpl_config['outerface_present'], prefix='outerface')) %} +{% endif %} +{% if ferm__tpl_config['protocol']|d() %} +{% set _ = ferm__tpl_arguments.append(print_list(ferm__tpl_config['protocol'], prefix='protocol')) %} +{% elif not ferm__tpl_config['protocol']|d() and (ferm__tpl_config['sport']|d() or ferm__tpl_config['dport']|d()) %} {% set _ = ferm__tpl_arguments.append("protocol tcp") %} {% endif %} -{% if ferm__tpl_protocol_syn %} -{% set _ = ferm__tpl_arguments.append(ferm__tpl_protocol_syn | join(" ")) %} +{% if ferm__tpl_config['protocol_syn']|d() %} +{% set _ = ferm__tpl_arguments.extend(ferm__tpl_config['protocol_syn']) %} {% endif %} -{% if ferm__tpl_dport %} +{% if ferm__tpl_config['dport']|d() %} {% if config.multiport|d() and config.multiport | bool %} -{% if ferm__tpl_dport | length == 1 %} -{% set _ = ferm__tpl_arguments.append("dport " + ferm__tpl_dport | join(" ")) %} +{% if ferm__tpl_config['dport'] | length == 1 %} +{% set _ = ferm__tpl_arguments.append("dport " + ferm__tpl_config['dport'] | join(" ")) %} {% else %} -{% set _ = ferm__tpl_arguments.append("mod multiport destination-ports (" + ferm__tpl_dport | join(" ") + ")") %} +{% set _ = ferm__tpl_arguments.append("mod multiport destination-ports (" + ferm__tpl_config['dport'] | join(" ") + ")") %} {% endif %} {% else %} -{% if ferm__tpl_dport | length == 1 %} -{% set _ = ferm__tpl_arguments.append("dport " + ferm__tpl_dport | join(" ")) %} -{% else %} -{% set _ = ferm__tpl_arguments.append("dport (" + ferm__tpl_dport | join(" ") + ")") %} -{% endif %} +{% set _ = ferm__tpl_arguments.append(print_list(ferm__tpl_config['dport'], prefix='dport')) %} {% endif %} {% endif %} -{% if ferm__tpl_state %} -{% if ferm__tpl_state | length == 1 %} -{% set _ = ferm__tpl_arguments.append("mod state state " + ferm__tpl_state | join(" ")) %} -{% else %} -{% set _ = ferm__tpl_arguments.append("mod state state (" + ferm__tpl_state | join(" ") + ")") %} -{% endif %} +{% if ferm__tpl_config['state']|d() %} +{% set _ = ferm__tpl_arguments.append(print_list(ferm__tpl_config['state'], prefix='mod state state')) %} {% endif %} -{% if ferm__tpl_arguments and (ferm__tpl_saddr | length > 3 or config.hashlimit|d()) %} -{% if ferm__tpl_subchain %} -{% set _ = ferm__tpl_arguments.append('@subchain "' + ferm__tpl_subchain + '"') %} +{% if ferm__tpl_arguments and ((ferm__tpl_config['saddr']|d([])) | length > 3 or config.hashlimit|d()) %} +{% if ferm__tpl_config['subchain']|d() %} +{% set _ = ferm__tpl_arguments.append('@subchain "' + ferm__tpl_config['subchain'] + '"') %} {% endif %} {% endif %} -{% if ferm__tpl_domain_args %}{{ ferm__tpl_domain_args | join(" ") }} {% endif %}{ -{% if config.type|d('accept') in [ 'policy', 'default_policy' ] %} +{# This is where the configuration begins + ====================================== #} +{% if ferm__tpl_config['type'] != 'dmz' %} +{% if ferm__tpl_config['domain_args'] %}{{ ferm__tpl_config['domain_args'] | join(" ") }} {% endif %}{ +{% endif %} +{% if ferm__tpl_config['type'] in [ 'policy', 'default_policy' ] %} policy {{ config.policy }}; -{% elif config.type|d('accept') == 'include' %} +{% elif ferm__tpl_config['type'] == 'include' %} @include "{{ config.include }}"; -{% elif config.type|d('accept') == 'connection_tracking' %} +{% elif ferm__tpl_config['type'] == 'connection_tracking' %} {% if ferm__tpl_arguments %}{{ ferm__tpl_arguments | join(" ") }} {% endif %}{ - {{ ferm__tpl_tracking_module_command }} INVALID {{ ferm__tpl_tracking_invalid_target }}; - {{ ferm__tpl_tracking_module_command }} (ESTABLISHED RELATED) {{ ferm__tpl_tracking_active_target }}; + {{ ferm__tpl_config['tracking_module_command'] }} INVALID {{ ferm__tpl_config['tracking_invalid_target'] }}; + {{ ferm__tpl_config['tracking_module_command'] }} (ESTABLISHED RELATED) {{ ferm__tpl_config['tracking_active_target'] }}; } -{% elif config.type|d('accept') == 'recent' %} +{% elif ferm__tpl_config['type'] == 'recent' %} {% if ferm__tpl_arguments %}{{ ferm__tpl_arguments | join(" ") }} {% endif %}{ mod recent {{ ferm__tpl_recent_args | join(" ") }} { @@ -336,45 +305,92 @@ &log("{{ config.recent_log_prefix | d('ipt-recent-' + config.recent_name | d(config.recent_set_name) + ': ') }}"); {% endif %} -{% if ferm__tpl_recent_target %} -{% if ferm__tpl_recent_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if ferm__tpl_config['recent_target'] %} +{% if ferm__tpl_config['recent_target'] not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} {% if config.include|d() %} @include "{{ config.include }}"; {% elif config.realgoto is undefined or not config.realgoto | bool %} - jump "{{ ferm__tpl_recent_target }}"; + jump "{{ ferm__tpl_config['recent_target'] }}"; {% elif config.realgoto|d() and config.realgoto | bool %} - realgoto "{{ ferm__tpl_recent_target }}"; + realgoto "{{ ferm__tpl_config['recent_target'] }}"; {% endif %} -{% elif ferm__tpl_recent_target in [ 'REJECT' ] %} +{% elif ferm__tpl_config['recent_target'] in [ 'REJECT' ] %} - REJECT reject-with {{ ferm__tpl_reject_with }}; + REJECT reject-with {{ ferm__tpl_config['reject_with'] }}; {% else %} - {{ ferm__tpl_recent_target }}; + {{ ferm__tpl_config['recent_target'] }}; {% endif %} {% endif %} } } -{% elif config.type|d('accept') == 'reject' %} +{% elif ferm__tpl_config['type'] == 'reject' %} protocol udp REJECT reject-with icmp-port-unreachable; protocol tcp REJECT reject-with tcp-reset; @if @eq($DOMAIN, ip) { REJECT reject-with icmp-proto-unreachable; } -{% elif config.type|d('accept') in [ 'custom', 'raw' ] %} -{% if ferm__tpl_domain_args %}{{ ferm__tpl_domain_args | join(" ") + " {" }} +{% elif ferm__tpl_config['type'] in [ 'custom', 'raw' ] %} +{% if ferm__tpl_config['domain_args'] %}{{ ferm__tpl_config['domain_args'] | join(" ") + " {" }} {% endif %} {% if config.rules|d() %} -{% if ferm__tpl_domain_args %} +{% if ferm__tpl_config['domain_args'] %} {{ config.rules | indent(4,true) }} {% else %} {{ config.rules }} {% endif %} {% endif %} -{% if ferm__tpl_domain_args %}}{% endif %} +{% if ferm__tpl_config['domain_args'] %}}{% endif %} +{% elif ferm__tpl_config['type'] == 'dmz' %} +{% if ferm__tpl_config['domain_args'] %}{{ ferm__tpl_config['domain_args'] | join(" ") }} {% endif %}{ + @def $PUBLIC_IP = ( @ipfilter( ({{ ferm__tpl_config['public_ip'] | unique | join(' ') }}) ) ); + @def $PRIVATE_IP = ( @ipfilter( ({{ ferm__tpl_config['private_ip'] | unique | join(' ') }}) ) ); + @if @ne($PUBLIC_IP,"") @if @ne($PRIVATE_IP,"") { + table filter chain FORWARD { +{% if ferm__tpl_config['dmz_ports']|d() %} + protocol ({{ ferm__tpl_config['protocols']|d([ 'tcp' ]) | join(" ") }}) { +{% if ferm__tpl_config['dmz_ports'] | length > 1 %} + mod multiport destination-ports ({{ ferm__tpl_config['dmz_ports'] | join(" ") }}) { +{% else %} + dport ({{ ferm__tpl_config['dmz_ports'] | join(" ") }}) { +{% endif %} + destination $PRIVATE_IP ACCEPT; + } + } +{% else %} + destination $PRIVATE_IP ACCEPT; +{% endif %} + } + + table nat { + chain PREROUTING { +{% if ferm__tpl_config['dmz_ports']|d() %} + protocol ({{ ferm__tpl_config['protocols']|d([ 'tcp' ]) | join(" ") }}) { +{% if ferm__tpl_config['dmz_ports'] | length > 1 %} + mod multiport destination-ports ({{ ferm__tpl_config['dmz_ports'] | join(" ") }}) { +{% else %} + dport ({{ ferm__tpl_config['dmz_ports'] | join(" ") }}) { +{% endif %} +{% if ferm__tpl_config['dport']|d() %} + destination $PUBLIC_IP DNAT to @cat($PRIVATE_IP, ":{{ ferm__tpl_config['dport'][0] }}"); +{% else %} + destination $PUBLIC_IP DNAT to $PRIVATE_IP; +{% endif %} + } + } +{% else %} + destination $PUBLIC_IP DNAT to $PRIVATE_IP; +{% endif %} + } + chain POSTROUTING { + source $PRIVATE_IP SNAT to $PUBLIC_IP; + } + } + } +} {% else %} {% if ferm__tpl_arguments %}{{ ferm__tpl_arguments | join(" ") }} {% endif %}{ {% if config.hashlimit|d() %} @@ -388,23 +404,23 @@ {% if config.hashlimit_expire is undefined or config.hashlimit_expire %} hashlimit-htable-expire {{ ((config.hashlimit_expire|d("1800")) | int * 1000) }} {% endif %} -{% if ferm__tpl_hashlimit_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if ferm__tpl_config['hashlimit_target'] not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} {% if config.include|d() %} @include "{{ config.include }}"; {% elif config.realgoto is undefined or not config.realgoto | bool %} - jump "{{ ferm__tpl_target }}"; + jump "{{ ferm__tpl_config['target'] }}"; {% elif config.realgoto|d() and config.realgoto | bool %} - realgoto "{{ ferm__tpl_target }}"; + realgoto "{{ ferm__tpl_config['target'] }}"; {% endif %} -{% elif ferm__tpl_hashlimit_target in [ 'REJECT' ] %} +{% elif ferm__tpl_config['hashlimit_target'] in [ 'REJECT' ] %} - REJECT reject-with {{ ferm__tpl_reject_with }}; + REJECT reject-with {{ ferm__tpl_config['reject_with'] }}; {% else %} - {{ ferm__tpl_hashlimit_target }}; + {{ ferm__tpl_config['hashlimit_target'] }}; {% endif %} {% if ((config.log is undefined or config.log | bool) and (ferm__log | bool)) %} @@ -412,73 +428,73 @@ {% endif %} {% endif %} -{% if ferm__tpl_saddr|d() %} - @def $SITEMS = ( @ipfilter( ({{ ferm__tpl_saddr | unique | join(" ") }}) ) ); +{% if ferm__tpl_config['saddr']|d() %} + @def $SITEMS = ( @ipfilter( ({{ ferm__tpl_config['saddr'] | unique | join(" ") }}) ) ); @if @ne($SITEMS,"") { -{% if ferm__tpl_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if ferm__tpl_config['target'] not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} {% if config.include|d() %} @include "{{ config.include }}"; {% elif config.realgoto is undefined or not config.realgoto | bool %} - saddr $SITEMS jump "{{ ferm__tpl_target }}"; + saddr $SITEMS jump "{{ ferm__tpl_config['target'] }}"; {% elif config.realgoto|d() and config.realgoto | bool %} - saddr $SITEMS realgoto "{{ ferm__tpl_target }}"; + saddr $SITEMS realgoto "{{ ferm__tpl_config['target'] }}"; {% endif %} -{% elif ferm__tpl_target in [ 'REJECT' ] %} - saddr $SITEMS REJECT reject-with {{ ferm__tpl_reject_with }}; +{% elif ferm__tpl_config['target'] in [ 'REJECT' ] %} + saddr $SITEMS REJECT reject-with {{ ferm__tpl_config['reject_with'] }}; {% else %} - saddr $SITEMS {{ ferm__tpl_target }}; + saddr $SITEMS {{ ferm__tpl_config['target'] }}; {% endif %} } -{% elif ferm__tpl_daddr|d() %} - @def $DITEMS = ( @ipfilter( ({{ ferm__tpl_daddr | unique | join(" ") }}) ) ); +{% elif ferm__tpl_config['daddr']|d() %} + @def $DITEMS = ( @ipfilter( ({{ ferm__tpl_config['daddr'] | unique | join(" ") }}) ) ); @if @ne($DITEMS,"") { -{% if ferm__tpl_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if ferm__tpl_config['target'] not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} {% if config.include|d() %} @include "{{ config.include }}"; {% elif config.realgoto is undefined or not config.realgoto | bool %} - daddr $DITEMS jump "{{ ferm__tpl_target }}"; + daddr $DITEMS jump "{{ ferm__tpl_config['target'] }}"; {% elif config.realgoto|d() and config.realgoto | bool %} - daddr $DITEMS realgoto "{{ ferm__tpl_target }}"; + daddr $DITEMS realgoto "{{ ferm__tpl_config['target'] }}"; {% endif %} -{% elif ferm__tpl_target in [ 'REJECT' ] %} - daddr $DITEMS REJECT reject-with {{ ferm__tpl_reject_with }}; +{% elif ferm__tpl_config['target'] in [ 'REJECT' ] %} + daddr $DITEMS REJECT reject-with {{ ferm__tpl_config['reject_with'] }}; {% else %} - daddr $DITEMS {{ ferm__tpl_target }}; + daddr $DITEMS {{ ferm__tpl_config['target'] }}; {% endif %} } {% else %} {% if config.accept_any is defined %} {% if config.accept_any | bool %} -{% if ferm__tpl_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if ferm__tpl_config['target'] not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} {% if config.include|d() %} @include "{{ config.include }}"; {% elif config.realgoto is undefined or not config.realgoto | bool %} - jump "{{ ferm__tpl_target }}"; + jump "{{ ferm__tpl_config['target'] }}"; {% elif config.realgoto|d() and config.realgoto | bool %} - realgoto "{{ ferm__tpl_target }}"; + realgoto "{{ ferm__tpl_config['target'] }}"; {% endif %} -{% elif ferm__tpl_target in [ 'REJECT' ] %} - REJECT reject-with {{ ferm__tpl_reject_with }}; +{% elif ferm__tpl_config['target'] in [ 'REJECT' ] %} + REJECT reject-with {{ ferm__tpl_config['reject_with'] }}; {% else %} - {{ ferm__tpl_target }}; + {{ ferm__tpl_config['target'] }}; {% endif %} {% elif not config.accept_any | bool %} # Connections from any IP address not allowed {% endif %} {% else %} -{% if ferm__tpl_target not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} +{% if ferm__tpl_config['target'] not in [ 'ACCEPT', 'DROP', 'REJECT', 'RETURN', 'NOP' ] %} {% if config.include|d() %} @include "{{ config.include }}"; {% elif config.realgoto is undefined or not config.realgoto | bool %} - jump "{{ ferm__tpl_target }}"; + jump "{{ ferm__tpl_config['target'] }}"; {% elif config.realgoto|d() and config.realgoto | bool %} - realgoto "{{ ferm__tpl_target }}"; + realgoto "{{ ferm__tpl_config['target'] }}"; {% endif %} -{% elif ferm__tpl_target in [ 'REJECT' ] %} - REJECT reject-with {{ ferm__tpl_reject_with }}; +{% elif ferm__tpl_config['target'] in [ 'REJECT' ] %} + REJECT reject-with {{ ferm__tpl_config['reject_with'] }}; {% else %} {% if ferm__tpl_arguments %} - {{ ferm__tpl_target }}; + {{ ferm__tpl_config['target'] }}; {% else %} # No rule parameters specified {% endif %} @@ -487,7 +503,9 @@ {% endif %} } {% endif %} +{% if ferm__tpl_config['type'] != 'dmz' %} } +{% endif %} {% endmacro %} {% set rule_name = (item.value.name | d(item.key)) %} {% set rule = item.value %} From 6ab9b988bb8b96b1f29dfcaf0949da6278fff935 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 14 Feb 2017 14:42:36 +0100 Subject: [PATCH 08/30] Run 'debconf' task only on APT-based hosts Fixes #104 --- CHANGES.rst | 3 +++ tasks/main.yml | 1 + 2 files changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6dea254..0c1062b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -59,6 +59,9 @@ Changed type defined for a given firewall rule. You can use negative weight values for better control over rule order. [drybjed_] +- Run the ``debconf`` task only when APT is the package manager. This should + allow the role to be used on OSes other than Debian/Ubuntu. [drybjed_] + Removed ~~~~~~~ diff --git a/tasks/main.yml b/tasks/main.yml index 4b323c7..a9b94e3 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -6,6 +6,7 @@ question: 'ferm/enable' vtype: 'boolean' value: '{{ "yes" if ferm__enabled|bool else "no" }}' + when: ansible_pkg_mgr == 'apt' - name: Ensure ferm is installed package: From a179f2e6eddaa0bfbda8501aebffc49cae759bc4 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 14 Feb 2017 14:51:25 +0100 Subject: [PATCH 09/30] Properly reject IPv6 packets in new rule template --- templates/etc/ferm/rules.d/rule.conf.j2 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/etc/ferm/rules.d/rule.conf.j2 b/templates/etc/ferm/rules.d/rule.conf.j2 index 5520abd..aee6a23 100644 --- a/templates/etc/ferm/rules.d/rule.conf.j2 +++ b/templates/etc/ferm/rules.d/rule.conf.j2 @@ -333,6 +333,9 @@ @if @eq($DOMAIN, ip) { REJECT reject-with icmp-proto-unreachable; } + @if @eq($DOMAIN, ip6) { + REJECT; + } {% elif ferm__tpl_config['type'] in [ 'custom', 'raw' ] %} {% if ferm__tpl_config['domain_args'] %}{{ ferm__tpl_config['domain_args'] | join(" ") + " {" }} {% endif %} From 13b793a840df32915a5b0acccc4169624e91f0c3 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 14 Feb 2017 15:18:42 +0100 Subject: [PATCH 10/30] Cosmetic change in default variables --- defaults/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defaults/main.yml b/defaults/main.yml index c56af63..9b16936 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -88,7 +88,7 @@ ferm__ansible_controllers_ports: [ 'ssh' ] # # List of interfaces for the default Ansible Controllers rule. An empty list # means all interfaces. -ferm__ansible_controllers_interfaces: [ ] +ferm__ansible_controllers_interfaces: [] # ]]] # .. envvar:: ferm__default_policy_input [[[ From 91fc64f216c0f9ca1ae528fd68d3970b6760372a Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 28 Mar 2017 12:16:27 +0200 Subject: [PATCH 11/30] Request 'sudo' on Travis-CI --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3e4ab90..123f4db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ --- +sudo: True language: 'python' python: '2.7' From 875e0ada8b3a64075cbfee54c2d48ca9d5ac2ac7 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 28 Mar 2017 12:17:36 +0200 Subject: [PATCH 12/30] Update role documentation --- CHANGES.rst | 4 +- docs/defaults-detailed.rst | 147 ++++++++++++++++++++++ docs/rules.rst | 244 ++++++++++++------------------------- 3 files changed, 229 insertions(+), 166 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0c1062b..c628f0a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -95,7 +95,7 @@ Changed - Use the `Ansible package module`_ which requires Ansible v2.0. [ypid_] - Be more precise about the expected format of ``item.by_role`` in - :ref:`default_rules`. [ypid_] + :ref:`ferm__ref_default_rules`. [ypid_] - Move kernel parameters to enable reverse path filtering to the debops.sysctl_ role. [ypid_] @@ -118,7 +118,7 @@ Deprecated compatibility. [ypid_] - Deprecated ``item.role``, use ``item.by_role`` instead. Applies for: - :ref:`default_rules`. [ypid_] + :ref:`ferm__ref_default_rules`. [ypid_] `debops.ferm v0.2.1`_ - 2016-04-21 diff --git a/docs/defaults-detailed.rst b/docs/defaults-detailed.rst index d311848..7b696a8 100644 --- a/docs/defaults-detailed.rst +++ b/docs/defaults-detailed.rst @@ -11,6 +11,153 @@ them. :local: :depth: 1 +.. _ferm__ref_rules: + +ferm__rules +----------- + +The ``ferm__*_rules`` variables are YAML dictionaries which define what +firewall rules are configured on a host. All dictionaries are recursively +combined together in the order they appear in the :file:`defaults/main.yml` +file. + +Each entry in the ``ferm__*_rules`` dictionaries is a YAML dictionary. The key +of a given entry is a rule name which will be part of the filename generated by +the role. + +You can also use YAML lists of dictionaries, however you cannot combine both +dictionaries and lists in the same ``ferm__*_rules`` variable. YAML +dictionaries specified in a list need to have the ``name`` parameter that +specifies the rule name, otherwise they will be skipped. Use of YAML lists is +usually done in other roles for rules defined via role dependent variables. + +The variables are parsed in a specific order: + +- :envvar:`ferm__default_rules` +- :envvar:`ferm__dependent_rules` +- :envvar:`ferm__rules` +- :envvar:`ferm__group_rules` +- :envvar:`ferm__host_rules` + +The result is stored as :envvar:`ferm__combined_rules` variable. This order +allows modification of the default rules as well as rules defined by other +Ansible roles using Ansible inventory variables. + +The rules are stored in the :file:`/etc/ferm/rules.d/` directory and +the filename format is: + +.. code-block:: none + + /etc/ferm/rules.d/_rule_.conf + +The rule "weight" is determined by a given rule type which can be overriden if +needed, see the ``type``, ``weight`` and ``weight_class`` parameters for more +details. + +Each rule defined in a dictionary uses specific parameters. The parameters +described here are general ones, mostly usable on the main "level" and are +related to management of rule files. The parameters related to specific +:command:`ferm` rules are described in :ref:`ferm__ref_firewall_rules` +documentation. + +``name`` + Name of the firewall rule to configure. If it's not specified, or a YAML + dictionary format is used for the parent variable, the rule will be named + after the dictionary key used. + + Example rule configuration in a YAML dictionary variable: + + .. code-block:: yaml + + ferm__rules: + 'accept_all_connections': + type: 'accept' + accept_any: True + + Example rule configuration in a YAML list variable: + + .. code-block:: yaml + + ferm__rules: + - name: 'accept_all_connections' + type: 'accept' + accept_any: True + +``rules`` + Either a string or a YAML text block that contains raw :command:`ferm` + configuration options, or a list of YAML dictionaries which specify firewall + rules. If this parameter is not specified, role will try and generate rules + automatically based on other parameters specified on the "first level" of + a given rule definition. Most of the other parameters can be specified on the + "second evel" rules and will apply to a given rule in the list. + + Example custom rule definition that restarts :command:`nginx` after firewall is modified: + + .. code-block:: yaml + + ferm__rules: + 'restart_nginx': + type: 'post-hook' + rules: '@hook post "type nginx > /dev/null && systemctl restart nginx || true";' + + Exmple list of rule definitions which will open access to different service + ports; rules will be present in the same file: + + .. code-block:: yaml + + ferm__rules: + - name: 'allow_http_https' + rules: + + - dport: 'http' + accept_any: True + + - dport: 'https' + accept_any: True + +``rule_state`` + Optional. Specify the state the of the firewall rule file, or one of the + rules included in that file. Available states: + + - ``present``: default. The rule file will be created if it doesn't exist, + a rule will be present in the file. + + - ``absent``: The rule file will be removed, a rule in the file will not be + generated. + + - ``ignore``: the role will not change the current state of the configuration + file. This value does not have an effect on the rules inside the file. + +``comment`` + Optional. Add a comment in the rule configuration file, either as a string or + as a YAML text block. + +``template`` + Optional. Name of the template to use to generate the firewall rule file. + Currently only one template is available, ``rule`` so this option is not + useful yet. + +``type`` + Optional. Specify the rule type as a name, for example ``accept`` or + ``reject``. Different rule types can use different rule parameters, the rule + type also affects the "weight" used to order the configuration files. Weight + of the different rules is specified in the :envvar:`ferm__default_weight_map` + variable and can be overriden using the :envvar:`ferm__weight_map` variable. + + List of known rule types can be found in the :ref:`ferm__ref_firewall_rules` + documentation. + +``weight_class`` + Optional. Override the rule type with another type, to change the sort order + of the configuration files. This parameter does not affect the + :command:`ferm` configuration template, only the resulting filename. + +``weight`` + Optional. Additional positive or negative number (for example ``2`` or + ``-2``) which will be added to the rule weight affecting the file sorting + order. + + .. _ferm__ref_input_list: ferm_input_list diff --git a/docs/rules.rst b/docs/rules.rst index 755a949..9f62b5e 100644 --- a/docs/rules.rst +++ b/docs/rules.rst @@ -1,3 +1,5 @@ +.. _ferm__ref_firewall_rules: + Firewall Rule Definitions ========================= @@ -14,7 +16,7 @@ requirements. :local: :depth: 2 -.. _default_rules: +.. _ferm__ref_default_rules: Default rules ------------- @@ -30,7 +32,7 @@ In case a firewall is not required or preferred this behaviour can be disabled by setting :envvar:`ferm__enabled` to ``False`` in the inventory. -.. _custom_rules: +.. _ferm__ref_custom_rules: Custom rules ------------ @@ -46,10 +48,6 @@ the following keys: configuration, required. See `Rule templates`_ for a description of the available rule templates. -``chain`` - Optional. :command:`iptables` chain to which the rule is added or from which it - is removed. Defaults to ``INPUT``. - ``comment`` Optional. Comment which should be added to the generated rule configuration. @@ -61,71 +59,37 @@ the following keys: Optional. :command:`iptables` table to which the rule is added or from which it is removed. Defaults to ``filter``. -``filename`` - Optional. Set custom filename for ferm rule definition instead of generated - one. - -``name`` - Optional. Set rule name in ferm configuration file when ``item.filename`` is - not set and other places where a custom rule name might be useful. +``chain`` + Optional. :command:`iptables` chain to which the rule is added or from which it + is removed. Defaults to ``INPUT``. ``by_role`` Optional. Name of the Ansible role in the format ``ROLE_OWNER.ROLE_NAME`` - which is responsable for the rule. - The sanitized name will be included in the autogenerated filename. - -``role`` - Deprecated. Use ``by_role`` instead. - -``role_weight`` - Optional. This allows to set the same ``item.weight`` for all rules of a - particular Ansible role. - -``rule_state`` - Optional. Specify if rule is to be added or removed. Possible values: - ``present`` or ``absent``. Defaults to ``present``. - -``delete`` - This option is deprecated, see `discussion `_. - Use ``rule_state`` instead. - Delete rule from :program:`ferm` configuration. Possible values ``True`` - or ``False``. Defaults to ``False``. - -``weight`` - Optional. Helps with file sorting in rule directory. - -``weight_class`` - Optional. Helps to manage order of firewall rules. The ``item.weight_class`` - will be checked in the :envvar:`ferm__weight_map` dictionary. If a corresponding - entry is found, its weight will be used for that rule, if not, the - ``item.weight`` specified in the rule will be used instead. + which is responsable for the rule. This will be included as a comment in the + generated rule file. Depending on the chosen type, many additional variables are supported. -Please check the individual template description below. +Please check the individual rule type description below. -.. _rule_templates: +.. _ferm__ref_rule_types: -Rule templates --------------- +Rule types +---------- -There exist a number of predefined rule templates for generating firewall -rules through ferm. Each rule definition is referencing the used template -through its ``item.type`` key. The templates are located in the -:file:`templates/etc/ferm/ferm.d/` directory. +There exist a number of predefined rule types for generating firewall rules +through :command:`ferm`. Following a list of the available rule types which can +be used to create custom rules. -Following a list of the available rule templates which can be used to -create custom rules. +.. _ferm__ref_type_accept: -.. _accept_template: +The 'accept' type +~~~~~~~~~~~~~~~~~ -accept -^^^^^^ - -Template to create rules that match interfaces, ports, remote IP -addresses/subnets and can accept the packets, reject, or redirect to a -different chain. The following template-specific YAML keys are supported: +This rule type can be used to create rules that match interfaces, ports, remote +IP addresses/subnets and can accept the packets, reject, or redirect to +a different chain. The following type-specific YAML keys are supported: ``accept_any`` Optional. Match all source addresses by default. Possible values: ``True`` @@ -141,13 +105,9 @@ different chain. The following template-specific YAML keys are supported: ``dport`` Optional. List of destination ports to which the rule is applied. -``enabled`` - Optional. Enable rule definition. Possible values: ``True`` or ``False``. - Defaults to ``True``. - ``include`` - Optional. Custom ferm configuration file to include. See `ferm include`_ - for more details. + Optional. Custom :command:`ferm` configuration file to include. + See `ferm include`_ for more details. ``interface`` Optional. List of network interfaces for incoming packets to which the @@ -206,25 +166,20 @@ different chain. The following template-specific YAML keys are supported: Optional. :command:`iptables` jump target. Possible values: ``ACCEPT``, ``DROP``, ``REJECT``, ``RETURN``, ``NOP`` or a custom target. Defaults to ``ACCEPT``. -``when`` - This option is deprecated, see `discussion `_. - Use ``rule_state`` instead. - Optional. Define condition for the rule to be disabled. - .. _ferm include: http://ferm.foo-projects.org/download/2.1/ferm.html#includes .. _ferm realgoto: http://ferm.foo-projects.org/download/2.1/ferm.html#realgoto_custom_chain_name .. _ferm subchain: http://ferm.foo-projects.org/download/2.1/ferm.html#_subchain -.. _ansible_controller_template: +.. _ferm__ref_type_ansible_controller: -ansible_controller -^^^^^^^^^^^^^^^^^^ +The 'ansible_controller' type +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Similar to the `accept_template`_ template but defaults to the SSH target -port and sets the source address to the host running Ansible if not -overwritten through the ``item.ansible_controllers`` key. The following -template-specific YAML keys are supported: +Similar to the ``accept`` type but defaults to the SSH target port and sets the +source address to the host running Ansible if not overwritten through the +``item.ansible_controllers`` key. The following type-specific YAML keys are +supported: ``ansible_controllers`` Optional. List of source IP address which are added to ``item.saddr``. @@ -238,10 +193,6 @@ template-specific YAML keys are supported: Optional. List of destination ports to which the rule is applied. Defaults to :command:`ssh`. -``enabled`` - Optional. Enable rule definition. Possible values: ``True`` or ``False``. - Defaults to ``True``. - ``include`` Optional. Custom ferm configuration file to include. See `ferm include`_ for more details. @@ -295,19 +246,16 @@ template-specific YAML keys are supported: Optional. :command:`iptables` jump target. Possible values: ``ACCEPT``, ``DROP``, ``REJECT``, ``RETURN``, ``NOP`` or a custom target. Defaults to ``ACCEPT``. -This template is used in the default rule :envvar:`ferm__rules_filter_ansible_controller` -which enables SSH connections from the Ansible controller host. - .. _iptables multiport: http://ipset.netfilter.org/iptables-extensions.man.html#lbBM -.. connection_tracking_template: +.. _ferm__ref_type_connection_tracking: -connection_tracking -^^^^^^^^^^^^^^^^^^^ +The 'connection_tracking' type +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Template to enable connection tracking using the `iptables conntrack`_ or -`iptables state`_ extension. The following template-specific YAML keys are +This type is used to enable connection tracking using the `iptables conntrack`_ +or `iptables state`_ extension. The following type-specific YAML keys are supported: ``active_target`` @@ -338,21 +286,17 @@ supported: Optional. List of network interfaces for outgoing packets which are excluded from the rule. -This template is used in the default rule :envvar:`ferm__rules_filter_conntrack` -which enables connection tracking in the ``INPUT``, ``OUTPUT`` and ``FORWARD`` -chain. - .. _iptables conntrack: http://ipset.netfilter.org/iptables-extensions.man.html#lbAO .. _iptables state: http://ipset.netfilter.org/iptables-extensions.man.html#lbCC -.. _custom_template: +.. _ferm__ref_type_custom: -custom -^^^^^^ +The 'custom' type +~~~~~~~~~~~~~~~~~ -Template to define custom ferm rules. The following additional YAML keys are -supported: +The type used to define custom :command:`ferm` rules. The following additional +YAML keys are supported: ``rules`` ferm rule definition, required. @@ -364,31 +308,26 @@ supported: This template is used among others in a debops.libvirtd_ custom ferm rule. -.. _default_policy_template: +.. _ferm__ref_type_default_policy: -default_policy -^^^^^^^^^^^^^^ +The 'default_policy' type +~~~~~~~~~~~~~~~~~~~~~~~~~ -Template to define :command:`iptables` default policies. The following -template-specific YAML keys are supported: +This type is used to define :command:`iptables` default policies. The following +type-specific YAML keys are supported: ``policy`` :command:`iptables` chain policy, required. -This template is used in the default rule :envvar:`ferm__rules_default_policy` -which sets the ``INPUT``, ``FORWARD`` and ``OUTPUT`` chain policies according -to :envvar:`ferm__default_policy_input`, :envvar:`ferm__default_policy_forward` -and :envvar:`ferm__default_policy_output`. +.. _ferm__ref_type_dmz: -.. _dmz_template: +The 'dmz' type +~~~~~~~~~~~~~~ -dmz -^^^ - -Template to enable connection forwarding to another host. If ``item.port`` -is not specified, all traffic is forwarded. The following template-specific -YAML keys are supported: +This type can be used to enable connection forwarding to another host. If +``item.port`` is not specified, all traffic is forwarded. The following +type-specific YAML keys are supported: ``multiport`` Optional. Use `iptables multiport`_ extension. Possible values: ``True`` @@ -411,25 +350,13 @@ YAML keys are supported: internal destination port is different from the original destination port. -.. _fail2ban_template: +.. _ferm__ref_type_hashlimit: -fail2ban -^^^^^^^^ +The 'hashlimit' type +~~~~~~~~~~~~~~~~~~~~ -Template to integrate fail2ban with :program:`ferm`. As the fail2ban service is -defining its own :command:`iptables` chains the template will make sure that they -are properly refreshed if the :program:`ferm` configuration changes. - -This template is used in the default rule :envvar:`ferm__rules_fail2ban`. - - -.. _hashlimit_template: - -hashlimit -^^^^^^^^^ - -Template to define rate limit rules using the `iptables hashlimit`_ extension. -The following template-specific YAML keys are supported: +This type is used to define rate limit rules using the `iptables hashlimit`_ +extension. The following type-specific YAML keys are supported: ``daddr`` Optional. List of destination IP addresses or networks to which the @@ -438,10 +365,6 @@ The following template-specific YAML keys are supported: ``dport`` Optional. List of destination ports to which the rule is applied. -``enabled`` - Optional. Enable rule definition. Possible values: ``True`` and ``False``. - Defaults to ``True``. - ``hashlimit_burst`` Optional. Number of packets to match within the expiration time. Defaults to ``5``. @@ -508,36 +431,32 @@ The following template-specific YAML keys are supported: Optional. :command:`iptables` jump target in case the rate limit is reached. Defaults to ``REJECT``. -This template is used in the default rules :envvar:`ferm__rules_filter_icmp` and -:envvar:`ferm__rules_filter_syn` which limits the packet rate for ICMP packets -and new connection attempts. - .. _iptables hashlimit: http://ipset.netfilter.org/iptables-extensions.man.html#lbAY -.. _include_template: +.. _ferm__ref_type_include: -include -^^^^^^^ +The 'include' type +~~~~~~~~~~~~~~~~~~ -Template to include custom ferm configuration files. The following -template-specific YAML keys are supported: +This type can be used to include custom :command:`ferm` configuration files. +The following type-specific YAML keys are supported: ``include`` Optional. Custom ferm configuration file to include. See `ferm include`_ for more details. -.. _log_template: +.. _ferm__ref_type_log: -log -^^^ +The 'log' type +~~~~~~~~~~~~~~ -Template to specify logging rules using the `iptables log`_ extension. -The following template-specific YAML keys are supported: +This type can be used to specify logging rules using the `iptables log`_ +extension. The following type-specific YAML keys are supported: ``include`` - Optional. Custom ferm configuration file to include. See + Optional. Custom :command:`ferm` configuration file to include. See `ferm include`_ for more details. ``log_burst`` @@ -587,13 +506,13 @@ The following template-specific YAML keys are supported: .. _iptables log: http://ipset.netfilter.org/iptables-extensions.man.html#lbDD -.. _recent_template: +.. _ferm__ref_type_recent: -recent -^^^^^^ +The 'recent' type +~~~~~~~~~~~~~~~~~ -Template to track connections and respond accordingly by using the -`iptables recent`_ extension. The following template-specific YAML keys are +This type can be used to track connections and respond accordingly by using the +`iptables recent`_ extension. The following type-specific YAML keys are supported: ``dport`` @@ -667,7 +586,7 @@ supported: separate subchain with the name given. See `ferm subchain`_ for more details. -When using the `recent_template`_ template make sure to always define two +When using the ``recent`` type make sure to always define two rules: * One for matching the packet against the address list using the @@ -677,22 +596,19 @@ rules: * To clear the source address from the list again in case the connection restrictions are not met, add a second role using ``item.recent_remove``. -This template is used in the default role :envvar:`ferm__rules_filter_recent_badguys` -which will block IP addresses which are doing excessive connection attempts. - .. _iptables recent: http://ipset.netfilter.org/iptables-extensions.man.html#lbBW -.. _reject_template: +.. _ferm__ref_type_reject: -reject -^^^^^^ +The 'reject' type +~~~~~~~~~~~~~~~~~ -Template to reject all traffic. It can be added for example as a final rule -in a custom chain. +This type is used to reject all traffic. It can be added for example as a final +rule in a custom chain. -.. _legacy_rules: +.. _ferm__ref_legacy_rules: Legacy rules ------------ From bc6ba126f7f130eb46caa1d86dac4770ec8b3657 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 28 Mar 2017 12:18:23 +0200 Subject: [PATCH 13/30] Better comments in rule files --- templates/etc/ferm/rules.d/rule.conf.j2 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/templates/etc/ferm/rules.d/rule.conf.j2 b/templates/etc/ferm/rules.d/rule.conf.j2 index aee6a23..8d0eb2b 100644 --- a/templates/etc/ferm/rules.d/rule.conf.j2 +++ b/templates/etc/ferm/rules.d/rule.conf.j2 @@ -16,6 +16,9 @@ 'sport': [], 'dport': [] } %} +{% if config.comment|d() %} +{% set _ = ferm__tpl_config.update({'comment': config.comment }) %} +{% endif %} {% if config.type|d() %} {% set _ = ferm__tpl_config.update({'type': config.type }) %} {% endif %} @@ -285,6 +288,9 @@ {% endif %} {# This is where the configuration begins ====================================== #} +{% if ferm__tpl_config['comment']|d() %} +{{ (ferm__tpl_config['comment'] if ferm__tpl_config['comment'] is string else ferm__tpl_config['comment'] | join('\n')) | regex_replace('\n$', '') | comment(prefix='', postfix='') -}} +{% endif %} {% if ferm__tpl_config['type'] != 'dmz' %} {% if ferm__tpl_config['domain_args'] %}{{ ferm__tpl_config['domain_args'] | join(" ") }} {% endif %}{ {% endif %} @@ -512,6 +518,9 @@ {% endmacro %} {% set rule_name = (item.value.name | d(item.key)) %} {% set rule = item.value %} +{% if rule.by_role|d() %} +{{ ("This firewall rule was generated by: " + (rule.by_role if rule.by_role is string else rule.by_role | join('\n'))) | regex_replace('\n$', '') | comment(prefix='', postfix='') -}} +{% endif %} {% if rule.comment|d() %} {{ (rule.comment if rule.comment is string else rule.comment | join('\n')) | regex_replace('\n$', '') | comment(prefix='', postfix='') -}} {% endif %} From 38ad0b0bafb61f6a02da30d97945f2b76803514f Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 28 Mar 2017 12:19:48 +0200 Subject: [PATCH 14/30] Divert the original 'ferm.conf' configuration file --- CHANGES.rst | 3 +++ tasks/main.yml | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c628f0a..7dcbd33 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -62,6 +62,9 @@ Changed - Run the ``debconf`` task only when APT is the package manager. This should allow the role to be used on OSes other than Debian/Ubuntu. [drybjed_] +- The :file:`/etc/ferm/ferm.conf` configuration file will be now properly + diverted to preserve the original. [drybjed_] + Removed ~~~~~~~ diff --git a/tasks/main.yml b/tasks/main.yml index a9b94e3..9722867 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -60,6 +60,12 @@ mode: '0644' notify: [ 'Restart ferm' ] +- name: Divert the original ferm configuration file + command: dpkg-divert --quiet --local --divert /etc/ferm/ferm.conf.dpkg-divert --rename /etc/ferm/ferm.conf + args: + creates: '/etc/ferm/ferm.conf.dpkg-divert' + when: ferm__enabled|bool + - name: Configure main ferm config file template: src: 'etc/ferm/ferm.conf.j2' @@ -68,6 +74,14 @@ group: 'adm' mode: '0644' notify: [ 'Restart ferm' ] + when: ferm__enabled|bool + +- name: Revert the original configuration file + shell: rm -f /etc/ferm/ferm.conf ; dpkg-divert --quiet --local --rename --remove /etc/ferm/ferm.conf + args: + removes: '/etc/ferm/ferm.conf.dpkg-divert' + warn: False + when: not ferm__enabled|bool - name: Remove firewall rules file: From 65e34894873f435294e7c3b57cb0560a600c24df Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 28 Mar 2017 12:27:42 +0200 Subject: [PATCH 15/30] Remove self-referential documentation --- docs/rules.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/rules.rst b/docs/rules.rst index 9f62b5e..3a6c39b 100644 --- a/docs/rules.rst +++ b/docs/rules.rst @@ -43,14 +43,6 @@ predefined rule lists (:envvar:`ferm__rules`, :envvar:`ferm__group_rules`, inventory. Each rule has to be defined as a YAML dict using some of the following keys: -``type`` - Type of the rule template used for creating the corresponding ferm - configuration, required. See `Rule templates`_ for a description of - the available rule templates. - -``comment`` - Optional. Comment which should be added to the generated rule configuration. - ``domain`` Optional. :command:`iptables` domain used for the firewall rule. Possible values: :command:`ip`, ``ip6``. Defaults to :envvar:`ferm__domains`. From 3628cbc6a80cad33ca3ca0234a3f168197a41a68 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 28 Mar 2017 12:33:32 +0200 Subject: [PATCH 16/30] Fix missing reference issue in documentation --- defaults/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/defaults/main.yml b/defaults/main.yml index 9b16936..4c7ace1 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -618,7 +618,6 @@ ferm__forward: '{{ ansible_local.ferm.forward # # Should traffic be forwarded between the other hosts/containers if # :envvar:`ferm__forward` is ``True``? -# Refer to :envvar:`ferm__rules_forward` for details. ferm__forward_accept: '{{ ansible_local.ferm.forward if (ansible_local|d() and ansible_local.ferm|d() and ansible_local.ferm.forward|d()) From e8658bc3b432558805194d2430e00273655b7367 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 28 Mar 2017 12:48:44 +0200 Subject: [PATCH 17/30] Update firewall guides documentation --- docs/guides.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/guides.rst b/docs/guides.rst index 9fd109d..a547299 100644 --- a/docs/guides.rst +++ b/docs/guides.rst @@ -53,7 +53,7 @@ variables. ferm__default_policy_forward: DROP -* As soon as :envvar:`ferm__forward` is enabled, :envvar:`ferm__rules_forward` +* As soon as :envvar:`ferm__forward` is enabled, the default role configuration will create a default rule list which will accept every incoming or outgoing packet with a valid forward target. This can make sense if forwarding should be enabled on a virtualization host to allow packets in and out a separate @@ -89,7 +89,7 @@ variables. * Once a packet was accepted by the firewall all related packets belonging to the same connection are accepted too. This is defined in the - :envvar:`ferm__rules_filter_conntrack` rule which is loaded as part of the + ``connection_tracking`` rule which is loaded as part of the :envvar:`ferm__default_rules` rule list. @@ -122,7 +122,7 @@ address of a network packet is rewritten to the internal host address. .. topic:: Note - The :ref:`dmz_template` rule template won't modify the source address of a + The :ref:`ferm__ref_type_dmz` rule template won't modify the source address of a forwarded packet. This means that the original source address can still be identified at the internal receiver, however the route leading back to the source address must traverse the gateway again in order to successfully @@ -198,7 +198,7 @@ any other purpose. * First create an Ansible list with an individually chosen name which will hold the custom output rules. For every outgoing connection which should be allowed to the internal or external network a rule needs to be added. Every - template described in the :ref:`rule_templates` chapter can be used for the + template described in the :ref:`ferm__ref_rule_types` chapter can be used for the custom rules. The definition below is just a minimal example to show the procedure:: @@ -248,7 +248,7 @@ any other purpose. name: 'reject_out' comment: 'Reject remaining outgoing traffic' - The last rule is using the :ref:`reject_template` template which will reject + The last rule is using the :ref:`ferm__ref_type_reject` which will reject every packet not explicitly allowed. This will make it easier to figure out missing rules than if the packets would simply be dropped. @@ -270,8 +270,8 @@ any other purpose. Block Port Scans ~~~~~~~~~~~~~~~~ -To block port scans there is a predefined rule list -:envvar:`ferm__rules_filter_recent_scanners` which is not enabled by default. +To block port scans there is a predefined rule ``block_portscans`` which is not +enabled by default. It will remember source addresses which try to reach closed ports and completely blocks access from those addresses for a while. This behaviour can be enabled by setting :envvar:`ferm__mark_portscan`:: From 919e31e38b1cd8f3a6fe5d8d79ae214343b004ee Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 28 Mar 2017 12:56:45 +0200 Subject: [PATCH 18/30] Fix documentation reference --- docs/guides.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides.rst b/docs/guides.rst index a547299..9b564c5 100644 --- a/docs/guides.rst +++ b/docs/guides.rst @@ -84,8 +84,8 @@ variables. else "absent" }}' If there are multiple internal interfaces additional rules permitting packet - forwarding between those might be necessary. Check the ``internal`` rule of - the default :envvar:`ferm__rules_forward` for an example. + forwarding between those might be necessary. Check the ``forward_internal`` rule of + the default :envvar:`ferm__default_rules` for an example. * Once a packet was accepted by the firewall all related packets belonging to the same connection are accepted too. This is defined in the From 3876c679b1f62851796e9a9e666321825b9886b2 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Wed, 29 Mar 2017 10:42:49 +0200 Subject: [PATCH 19/30] Add missing 'log' and 'limit' firewall rules --- templates/etc/ferm/rules.d/rule.conf.j2 | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/templates/etc/ferm/rules.d/rule.conf.j2 b/templates/etc/ferm/rules.d/rule.conf.j2 index 8d0eb2b..9d2a71a 100644 --- a/templates/etc/ferm/rules.d/rule.conf.j2 +++ b/templates/etc/ferm/rules.d/rule.conf.j2 @@ -230,6 +230,59 @@ {% if config.hashlimit|d() %} {% set _ = ferm__tpl_config.update({'subchain': (ferm__tpl_config['type'] + "-" + config.hashlimit_name | d(config.name | d(rule_name)))}) %} {% endif %} +{% if config.limit|d() %} +{% set _ = ferm__tpl_config.update({'limit': config.limit}) %} +{% if config.limit_burst|d() %} +{% set _ = ferm__tpl_config.update({'limit_burst': config.limit_burst}) %} +{% endif %} +{% endif %} +{% if ferm__tpl_config['type'] == 'log' %} +{% set _ = ferm__tpl_config.update({'log_target': 'LOG'}) %} +{% endif %} +{% if config.log_target|d() %} +{% set _ = ferm__tpl_config.update({'log_target': config.log_target}) %} +{% endif %} +{% if config.log_limit|d() %} +{% set _ = ferm__tpl_config.update({'limit': config.log_limit}) %} +{% endif %} +{% if config.log_burst|d() %} +{% set _ = ferm__tpl_config.update({'limit_burst': config.log_burst}) %} +{% endif %} +{% if config.log_ip_options|d() %} +{% set _ = ferm__tpl_config.update({'log_ip_options': config.log_ip_options | bool}) %} +{% endif %} +{% if config.log_tcp_options|d() %} +{% set _ = ferm__tpl_config.update({'log_tcp_options': config.log_tcp_options | bool}) %} +{% endif %} +{% if config.log_tcp_sequence|d() %} +{% set _ = ferm__tpl_config.update({'log_tcp_sequence': config.log_tcp_sequence | bool}) %} +{% endif %} +{% if config.log_prefix|d() %} +{% set _ = ferm__tpl_config.update({'log_prefix': config.log_prefix}) %} +{% endif %} +{% if config.log_level|d() %} +{% set _ = ferm__tpl_config.update({'log_level': config.log_level}) %} +{% endif %} +{% set ferm__tpl_log_args = [] %} +{% if ferm__tpl_config['type'] == 'log' %} +{% if ferm__tpl_config['log_target'] == 'LOG' %} +{% if ferm__tpl_config['log_ip_options']|d() and ferm__tpl_config['log_ip_options']|bool %} +{% set _ = ferm__tpl_log_args.append('log-ip-options') %} +{% endif %} +{% if ferm__tpl_config['log_tcp_options']|d() and ferm__tpl_config['log_tcp_options']|bool %} +{% set _ = ferm__tpl_log_args.append('log-tcp-options') %} +{% endif %} +{% if ferm__tpl_config['log_tcp_sequence']|d() and ferm__tpl_config['log_tcp_sequence']|bool %} +{% set _ = ferm__tpl_log_args.append('log-tcp-sequence') %} +{% endif %} +{% if ferm__tpl_config['log_level']|d() %} +{% set _ = ferm__tpl_log_args.append('log-level ' + ferm__tpl_config['log_level']) %} +{% endif %} +{% if ferm__tpl_config['log_prefix']|d() %} +{% set _ = ferm__tpl_log_args.append('log-prefix "' + ferm__tpl_config['log_prefix'] + '"') %} +{% endif %} +{% endif %} +{% endif %} {% set ferm__tpl_recent_args = [] %} {% if ferm__tpl_config['recent_name']|d() %} {% set _ = ferm__tpl_recent_args.append('name "' + ferm__tpl_config['recent_name'] + '"') %} @@ -286,6 +339,12 @@ {% set _ = ferm__tpl_arguments.append('@subchain "' + ferm__tpl_config['subchain'] + '"') %} {% endif %} {% endif %} +{% if ferm__tpl_config['limit']|d() %} +{% set _ = ferm__tpl_arguments.append("mod limit limit " + ferm__tpl_config['limit']) %} +{% if ferm__tpl_config['limit_burst']|d() %} +{% set _ = ferm__tpl_arguments.append("limit-burst " + ferm__tpl_config['limit_burst']) %} +{% endif %} +{% endif %} {# This is where the configuration begins ====================================== #} {% if ferm__tpl_config['comment']|d() %} @@ -303,6 +362,14 @@ {{ ferm__tpl_config['tracking_module_command'] }} INVALID {{ ferm__tpl_config['tracking_invalid_target'] }}; {{ ferm__tpl_config['tracking_module_command'] }} (ESTABLISHED RELATED) {{ ferm__tpl_config['tracking_active_target'] }}; } +{% elif ferm__tpl_config['type'] == 'log' %} + {% if ferm__tpl_arguments %}{{ ferm__tpl_arguments | join(" ") }} {% endif %}{ +{% if ferm__tpl_log_args %} + {{ ferm__tpl_config['log_target'] }} {{ ferm__tpl_log_args | join(' ') }}; +{% else %} + {{ ferm__tpl_config['log_target'] }}; +{% endif %} + } {% elif ferm__tpl_config['type'] == 'recent' %} {% if ferm__tpl_arguments %}{{ ferm__tpl_arguments | join(" ") }} {% endif %}{ From cf0592fb8305bc774036d1173ad523539b2406ac Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Sat, 8 Apr 2017 12:01:56 +0200 Subject: [PATCH 20/30] Bring back rules for source ports --- templates/etc/ferm/rules.d/rule.conf.j2 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/templates/etc/ferm/rules.d/rule.conf.j2 b/templates/etc/ferm/rules.d/rule.conf.j2 index 9d2a71a..98331cb 100644 --- a/templates/etc/ferm/rules.d/rule.conf.j2 +++ b/templates/etc/ferm/rules.d/rule.conf.j2 @@ -320,6 +320,17 @@ {% if ferm__tpl_config['protocol_syn']|d() %} {% set _ = ferm__tpl_arguments.extend(ferm__tpl_config['protocol_syn']) %} {% endif %} +{% if ferm__tpl_config['sport']|d() %} +{% if config.multiport|d() and config.multiport | bool %} +{% if ferm__tpl_config['sport'] | length == 1 %} +{% set _ = ferm__tpl_arguments.append("sport " + ferm__tpl_config['sport'] | join(" ")) %} +{% else %} +{% set _ = ferm__tpl_arguments.append("mod multiport source-ports (" + ferm__tpl_config['sport'] | join(" ") + ")") %} +{% endif %} +{% else %} +{% set _ = ferm__tpl_arguments.append(print_list(ferm__tpl_config['sport'], prefix='sport')) %} +{% endif %} +{% endif %} {% if ferm__tpl_config['dport']|d() %} {% if config.multiport|d() and config.multiport | bool %} {% if ferm__tpl_config['dport'] | length == 1 %} From 23ae6d608634073f7a0cd3687a540955e8c5c247 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 9 May 2017 11:41:36 +0200 Subject: [PATCH 21/30] Update Ansible facts if they have been changed --- CHANGES.rst | 2 ++ tasks/main.yml | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7dcbd33..9cd14d1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -24,6 +24,8 @@ Added - Add a variable which can be used to restrict what network interfaces can be used for connections from Ansible Controller. [gaudenz] +- Update the Ansible facts automatically if they have been changed. [drybjed_] + Changed ~~~~~~~ diff --git a/tasks/main.yml b/tasks/main.yml index 9722867..8b97b54 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -213,3 +213,8 @@ group: 'root' mode: '0644' tags: [ 'role::ferm:rules' ] + register: ferm__register_facts + +- name: Update Ansible facts if they were modified + action: setup + when: ferm__register_facts|changed From 43bf3db8a2f85da23678fee02d670d4e5785afb7 Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Thu, 25 May 2017 23:37:49 +0200 Subject: [PATCH 22/30] debops-optimize --- CHANGES.rst | 2 +- COPYRIGHT | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9cd14d1..014a28b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,7 +10,7 @@ Changelog This project adheres to `Semantic Versioning `__ and `human-readable changelog `__. -The current role maintainer_ is drybjed_ +The current role maintainer_ is drybjed_. `debops.ferm master`_ - unreleased diff --git a/COPYRIGHT b/COPYRIGHT index d5f50a8..0f44145 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,9 +1,9 @@ debops.ferm - Manage iptables firewall using ferm -Copyright (C) 2013-2016 Maciej Delmanowski -Copyright (C) 2015-2016 Robin Schneider +Copyright (C) 2013-2017 Maciej Delmanowski +Copyright (C) 2015-2017 Robin Schneider Copyright (C) 2016 Reto Gantenbein -Copyright (C) 2014-2016 DebOps https://debops.org/ +Copyright (C) 2014-2017 DebOps https://debops.org/ This Ansible role is part of DebOps. From c712d5d53ec424889210b9eb51a8cf4a1706f31c Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Thu, 25 May 2017 23:47:14 +0200 Subject: [PATCH 23/30] Fix spelling and typos --- docs/defaults-detailed.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/defaults-detailed.rst b/docs/defaults-detailed.rst index 7b696a8..a56e971 100644 --- a/docs/defaults-detailed.rst +++ b/docs/defaults-detailed.rst @@ -50,7 +50,7 @@ the filename format is: /etc/ferm/rules.d/_rule_.conf -The rule "weight" is determined by a given rule type which can be overriden if +The rule "weight" is determined by a given rule type which can be overridden if needed, see the ``type``, ``weight`` and ``weight_class`` parameters for more details. @@ -89,7 +89,7 @@ documentation. rules. If this parameter is not specified, role will try and generate rules automatically based on other parameters specified on the "first level" of a given rule definition. Most of the other parameters can be specified on the - "second evel" rules and will apply to a given rule in the list. + "second level" rules and will apply to a given rule in the list. Example custom rule definition that restarts :command:`nginx` after firewall is modified: @@ -100,7 +100,7 @@ documentation. type: 'post-hook' rules: '@hook post "type nginx > /dev/null && systemctl restart nginx || true";' - Exmple list of rule definitions which will open access to different service + Example list of rule definitions which will open access to different service ports; rules will be present in the same file: .. code-block:: yaml @@ -116,8 +116,8 @@ documentation. accept_any: True ``rule_state`` - Optional. Specify the state the of the firewall rule file, or one of the - rules included in that file. Available states: + Optional. Specify the state of the firewall rule file, or one of the + rules included in that file. Supported states: - ``present``: default. The rule file will be created if it doesn't exist, a rule will be present in the file. @@ -142,7 +142,7 @@ documentation. ``reject``. Different rule types can use different rule parameters, the rule type also affects the "weight" used to order the configuration files. Weight of the different rules is specified in the :envvar:`ferm__default_weight_map` - variable and can be overriden using the :envvar:`ferm__weight_map` variable. + variable and can be overridden using the :envvar:`ferm__weight_map` variable. List of known rule types can be found in the :ref:`ferm__ref_firewall_rules` documentation. From ecfd66bcbbfcae1181126da579196ef331061fdc Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 20 Jun 2017 11:20:08 +0200 Subject: [PATCH 24/30] Redesign rule parser and drop dictionary support --- CHANGES.rst | 3 + defaults/main.yml | 90 ++++++++++--------- docs/defaults-detailed.rst | 54 +++-------- docs/playbooks/ferm.yml | 5 +- tasks/main.yml | 4 +- templates/lookup/ferm__combined_rules.j2 | 63 ------------- templates/lookup/ferm__fix_dependent_rules.j2 | 8 +- templates/lookup/ferm__parsed_rules.j2 | 44 +++++++++ 8 files changed, 123 insertions(+), 148 deletions(-) delete mode 100644 templates/lookup/ferm__combined_rules.j2 create mode 100644 templates/lookup/ferm__parsed_rules.j2 diff --git a/CHANGES.rst b/CHANGES.rst index 014a28b..023c6a3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -67,6 +67,9 @@ Changed - The :file:`/etc/ferm/ferm.conf` configuration file will be now properly diverted to preserve the original. [drybjed_] +- The rule parser is redesigned again and support for YAML dictionaries is + dropped, only lists can be used. [drybjed_] + Removed ~~~~~~~ diff --git a/defaults/main.yml b/defaults/main.yml index 4c7ace1..48eca91 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -257,9 +257,9 @@ ferm__include_legacy: True # ]]] # .. envvar:: ferm__dependent_rules [[[ # -# YAML dictionary which contains :command:`ferm` rules to manage defined by -# other Ansible roles using dependent variables. -ferm__dependent_rules: {} +# YAML list which contains :command:`ferm` rules to manage defined by other +# Ansible roles using dependent variables. +ferm__dependent_rules: [] # ]]] # .. envvar:: ferm__fix_dependent_rules [[[ @@ -275,33 +275,45 @@ ferm__fix_dependent_rules: '{{ lookup("template", # ]]] # .. envvar:: ferm__rules [[[ # -# YAML dictionary which contains :command:`ferm` rules which should be defined -# on all hosts in the Ansible inventory. -ferm__rules: {} +# YAML list which contains :command:`ferm` rules which should be defined on all +# hosts in the Ansible inventory. +ferm__rules: [] # ]]] # .. envvar:: ferm__group_rules [[[ # -# YAML dictionary which contains :command:`ferm` rules which should be defined -# on a group of hosts in the Ansible inventory. -ferm__group_rules: {} +# YAML list which contains :command:`ferm` rules which should be defined on +# a group of hosts in the Ansible inventory. +ferm__group_rules: [] # ]]] # .. envvar:: ferm__host_rules [[[ # -# YAML dictionary which contains :command:`ferm` rules which should be defined -# on specific hosts in the Ansible inventory. -ferm__host_rules: {} +# YAML list which contains :command:`ferm` rules which should be defined on +# specific hosts in the Ansible inventory. +ferm__host_rules: [] # ]]] # .. envvar:: ferm__combined_rules [[[ # +# YAML list which defines the order in which firewall rules are defined and +# affect each other. This list is then passed to the parser template to +# generate final dictionary with rules for :command:`ferm`. +ferm__combined_rules: '{{ ferm__default_rules + + ferm__fix_dependent_rules + + ferm__rules + + ferm__group_rules + + ferm__host_rules }}' + + # ]]] +# .. envvar:: ferm__parsed_rules [[[ +# # YAML dictionary which contains all of the defined :command:`ferm` rules # combined together. This variable is used in the Ansible tasks that manage the # rules on remote hosts. -ferm__combined_rules: '{{ lookup("template", - "lookup/ferm__combined_rules.j2", - convert_data=False) | from_yaml }}' +ferm__parsed_rules: '{{ lookup("template", + "lookup/ferm__parsed_rules.j2", + convert_data=False) | from_yaml }}' # ]]] # .. envvar:: ferm_input_list [[[ @@ -381,22 +393,22 @@ ferm__combined_weight_map: '{{ ferm__default_weight_map # YAML dictionary with default firewall rules defined on each host. ferm__default_rules: - 'policy_filter_input': + - name: 'policy_filter_input' type: 'default_policy' chain: 'INPUT' policy: '{{ ferm__default_policy_input }}' - 'policy_filter_forward': + - name: 'policy_filter_forward' type: 'default_policy' chain: 'FORWARD' policy: '{{ ferm__default_policy_forward }}' - 'policy_filter_output': + - name: 'policy_filter_output' type: 'default_policy' chain: 'OUTPUT' policy: '{{ ferm__default_policy_output }}' - 'firewall_hooks': + - name: 'firewall_hooks' type: 'custom' comment: 'Run custom hooks at various firewall stages' rules: | @@ -404,7 +416,7 @@ ferm__default_rules: @hook post "run-parts /etc/ferm/hooks/post.d"; @hook flush "run-parts /etc/ferm/hooks/flush.d"; - 'firewall_variables': + - name: 'firewall_variables' type: 'custom' comment: 'Define custom variables available in the firewall' rules: | @@ -412,7 +424,7 @@ ferm__default_rules: @def $ipv4_enabled = {{ "1" if "ip" in ferm__domains else "0" }}; @def $ipv6_enabled = {{ "1" if "ip6" in ferm__domains else "0" }}; - 'firewall_log': + - name: 'firewall_log' type: 'custom' comment: 'Custom log function used by other rules' rules: | @@ -423,12 +435,12 @@ ferm__default_rules: } rule_state: '{{ "present" if (ferm__log | bool) else "absent" }}' - 'accept_loopback': + - name: 'accept_loopback' type: 'accept' weight_class: 'loopback' interface: 'lo' - 'accept_ansible_controller': + - name: 'accept_ansible_controller' type: 'ansible_controller' weight_class: 'ansible-controller' comment: 'Accept SSH connections from Ansible Controllers' @@ -437,7 +449,7 @@ ferm__default_rules: multiport: True accept_any: False - 'filter_icmp_flood': + - name: 'filter_icmp_flood' type: 'hashlimit' weight_class: 'filter-icmp' protocol: 'icmp' @@ -448,12 +460,12 @@ ferm__default_rules: hashlimit_target: 'ACCEPT' target: 'DROP' - 'connection_tracking': + - name: 'connection_tracking' type: 'connection_tracking' weight_class: 'connection-tracking' chain: [ 'INPUT', 'OUTPUT', 'FORWARD' ] - 'filter_syn_flood': + - name: 'filter_syn_flood' type: 'hashlimit' weight_class: 'filter-syn' protocol: 'tcp' @@ -465,7 +477,7 @@ ferm__default_rules: hashlimit_target: 'RETURN' target: 'DROP' - 'block_recent_badguys': + - name: 'block_recent_badguys' type: 'recent' weight_class: 'any-blacklist' comment: 'Reject packets marked as "badguys"' @@ -475,7 +487,7 @@ ferm__default_rules: recent_seconds: '{{ ferm__filter_recent_time }}' recent_target: 'REJECT' - 'clean_recent_badguys': + - name: 'clean_recent_badguys' type: 'recent' weight_class: 'any-blacklist' comment: 'Reject packets marked as "badguys"' @@ -484,7 +496,7 @@ ferm__default_rules: recent_remove: True recent_log: False - 'accept_dhcpv6_client': + - name: 'accept_dhcpv6_client' type: 'accept' weight_class: 'any-service' comment: 'DHCPv6 responses seem to be neither RELATED nor ESTABLISHED.' @@ -495,16 +507,15 @@ ferm__default_rules: sport: [ 'dhcpv6-server' ] dport: [ 'dhcpv6-client' ] - 'jump_to_legacy_input_rules': + - name: 'jump_to_legacy_input_rules' type: 'accept' weight: '-10' weight_class: 'reject' - name: 'jump_to_legacy_rules' comment: 'Jump to legacy firewall rules' target: 'debops-legacy-input-rules' rule_state: '{{ "present" if (ferm__include_legacy | bool) else "absent" }}' - 'include_legacy_input_rules': + - name: 'include_legacy_input_rules' type: 'include' weight_class: 'post-hook' chain: 'debops-legacy-input-rules' @@ -512,18 +523,17 @@ ferm__default_rules: include: '/etc/ferm/filter-input.d/' rule_state: '{{ "present" if (ferm__include_legacy | bool) else "absent" }}' - 'block_portscans': + - name: 'block_portscans' type: 'recent' weight: '85' comment: 'Mark potential port scanners as bad guys' recent_set_name: '{{ ferm__filter_recent_name }}' rule_state: '{{ "present" if (ferm__mark_portscan | bool) else "absent" }}' - 'reject_all': + - name: 'reject_all' type: 'reject' - - 'fail2ban-hook': + - name: 'fail2ban-hook' type: 'fail2ban' comment: 'Reload fail2ban rules' rule_state: '{{ "present" if (ferm__fail2ban | bool) else "absent" }}' @@ -532,7 +542,7 @@ ferm__default_rules: @hook flush "type fail2ban-server > /dev/null && (fail2ban-client ping > /dev/null && fail2ban-client reload > /dev/null || true) || true"; weight_class: 'post-hook' - 'forward_external_in': + - name: 'forward_external_in' chain: 'FORWARD' type: 'accept' weight: '1' @@ -543,7 +553,7 @@ ferm__default_rules: if (ferm__forward_accept|bool and ferm__forward | bool) else "ignore" }}' - 'forward_external_out': + - name: 'forward_external_out' chain: 'FORWARD' type: 'accept' weight: '2' @@ -554,7 +564,7 @@ ferm__default_rules: if (ferm__forward_accept|bool and ferm__forward | bool) else "ignore" }}' - 'forward_internal': + - name: 'forward_internal' chain: 'FORWARD' type: 'accept' interface_present: '{{ ferm__internal_interfaces }}' @@ -566,7 +576,7 @@ ferm__default_rules: if (ferm__forward_accept|bool and ferm__forward | bool) else "ignore" }}' - 'fix_bootpc_checksum': + - name: 'fix_bootpc_checksum' type: 'custom' rules: | # Add checksums to BOOTP packets from virtual machines and containers. diff --git a/docs/defaults-detailed.rst b/docs/defaults-detailed.rst index a56e971..bd6a1d0 100644 --- a/docs/defaults-detailed.rst +++ b/docs/defaults-detailed.rst @@ -16,30 +16,16 @@ them. ferm__rules ----------- -The ``ferm__*_rules`` variables are YAML dictionaries which define what -firewall rules are configured on a host. All dictionaries are recursively -combined together in the order they appear in the :file:`defaults/main.yml` -file. - -Each entry in the ``ferm__*_rules`` dictionaries is a YAML dictionary. The key -of a given entry is a rule name which will be part of the filename generated by -the role. - -You can also use YAML lists of dictionaries, however you cannot combine both -dictionaries and lists in the same ``ferm__*_rules`` variable. YAML -dictionaries specified in a list need to have the ``name`` parameter that -specifies the rule name, otherwise they will be skipped. Use of YAML lists is -usually done in other roles for rules defined via role dependent variables. - -The variables are parsed in a specific order: - -- :envvar:`ferm__default_rules` -- :envvar:`ferm__dependent_rules` -- :envvar:`ferm__rules` -- :envvar:`ferm__group_rules` -- :envvar:`ferm__host_rules` - -The result is stored as :envvar:`ferm__combined_rules` variable. This order +The ``ferm__*_rules`` variables are YAML lists which define what +firewall rules are configured on a host. The rules are combined together in the +:envvar:`ferm__combined_rules` variable which defines the order of the rule +variables and therefore how they will affect each other. + +Each entry in the ``ferm__*_rules`` lists is a YAML dictionary. The entry needs +to have the ``name`` parameter that specifies the rule name, otherwise it will +be skipped. + +The result is stored as :envvar:`ferm__parsed_rules` variable. This order allows modification of the default rules as well as rules defined by other Ansible roles using Ansible inventory variables. @@ -61,20 +47,7 @@ related to management of rule files. The parameters related to specific documentation. ``name`` - Name of the firewall rule to configure. If it's not specified, or a YAML - dictionary format is used for the parent variable, the rule will be named - after the dictionary key used. - - Example rule configuration in a YAML dictionary variable: - - .. code-block:: yaml - - ferm__rules: - 'accept_all_connections': - type: 'accept' - accept_any: True - - Example rule configuration in a YAML list variable: + Name of the firewall rule to configure. An example rule definition: .. code-block:: yaml @@ -91,12 +64,13 @@ documentation. a given rule definition. Most of the other parameters can be specified on the "second level" rules and will apply to a given rule in the list. - Example custom rule definition that restarts :command:`nginx` after firewall is modified: + Example custom rule definition that restarts :command:`nginx` after firewall + is modified: .. code-block:: yaml ferm__rules: - 'restart_nginx': + - name: 'restart_nginx': type: 'post-hook' rules: '@hook post "type nginx > /dev/null && systemctl restart nginx || true";' diff --git a/docs/playbooks/ferm.yml b/docs/playbooks/ferm.yml index cae7cb7..822a644 100644 --- a/docs/playbooks/ferm.yml +++ b/docs/playbooks/ferm.yml @@ -4,8 +4,11 @@ hosts: [ 'debops_all_hosts', 'debops_service_ferm' ] become: True + environment: '{{ inventory__environment | d({}) + | combine(inventory__group_environment | d({})) + | combine(inventory__host_environment | d({})) }}' + roles: - role: debops.ferm tags: [ 'role::ferm' ] - diff --git a/tasks/main.yml b/tasks/main.yml index 8b97b54..186e643 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -87,7 +87,7 @@ file: dest: '/etc/ferm/rules.d/{{ "%03d" | format((ferm__combined_weight_map[item.value.weight_class | d(item.value.type | d("default"))] | d("80"))|int + (item.value.weight | d("0"))|int) }}_rule_{{ item.value.name | d(item.key) }}.conf' state: 'absent' - with_dict: '{{ ferm__combined_rules }}' + with_dict: '{{ ferm__parsed_rules }}' register: ferm__register_rules_removed when: (item.value.rule_state|d(item.value.state|d('present')) == 'absent') tags: [ 'role::ferm:rules' ] @@ -99,7 +99,7 @@ owner: 'root' group: 'adm' mode: '0644' - with_dict: '{{ ferm__combined_rules }}' + with_dict: '{{ ferm__parsed_rules }}' register: ferm__register_rules_created when: (item.value.rule_state|d(item.value.state|d('present')) not in [ 'absent', 'ignore' ]) tags: [ 'role::ferm:rules' ] diff --git a/templates/lookup/ferm__combined_rules.j2 b/templates/lookup/ferm__combined_rules.j2 deleted file mode 100644 index d206451..0000000 --- a/templates/lookup/ferm__combined_rules.j2 +++ /dev/null @@ -1,63 +0,0 @@ -{% macro merge_dict(current_dict, to_merge_dict, dict_key='name') %} -{% set merged_dict = current_dict %} -{% if to_merge_dict %} -{% if to_merge_dict is mapping %} -{% for dict_name in to_merge_dict.keys() | sort %} -{% if to_merge_dict[dict_name][dict_key]|d() %} -{% set _ = merged_dict.update({to_merge_dict[dict_name][dict_key]:(current_dict[to_merge_dict[dict_name][dict_key]]|d({}) | combine(to_merge_dict[dict_name], recursive=True))}) %} -{% elif to_merge_dict[dict_name][dict_key] is undefined %} -{% set _ = merged_dict.update({dict_name:(current_dict[dict_name]|d({}) | combine(to_merge_dict[dict_name], recursive=True))}) %} -{% endif %} -{% endfor %} -{% elif to_merge_dict is not string and to_merge_dict is not mapping %} -{% set flattened_dict = lookup("flattened", to_merge_dict) %} -{% for element in ([ flattened_dict ] if flattened_dict is mapping else flattened_dict) %} -{% if element[dict_key]|d() %} -{% set _ = merged_dict.update({element[dict_key]:(current_dict[element[dict_key]]|d({}) | combine(element, recursive=True))}) %} -{% endif %} -{% endfor %} -{% endif %} -{% endif %} -{{ merged_dict | to_json }} -{% endmacro %} -{% set ferm__tpl_merge_default = (merge_dict({}, ferm__default_rules, 'name') | from_json) %} -{% set ferm__tpl_merge_dependent = (merge_dict(ferm__tpl_merge_default, ferm__dependent_rules, 'name') | from_json) %} -{% set ferm__tpl_fix_dependent = (merge_dict(ferm__tpl_merge_dependent, ferm__fix_dependent_rules, 'name') | from_json) %} -{% set ferm__tpl_merge_all = (merge_dict(ferm__tpl_fix_dependent, ferm__rules, 'name') | from_json) %} -{% set ferm__tpl_merge_group = (merge_dict(ferm__tpl_merge_all, ferm__group_rules, 'name') | from_json) %} -{% set ferm__tpl_rules = (merge_dict(ferm__tpl_merge_group, ferm__host_rules, 'name') | from_json) %} -{% set ferm__tpl_filtered_rules = {} %} -{% for name, params in ferm__tpl_rules.iteritems() %} -{% set rule_name = (params["name"] | d(name)) %} -{% if params["rules"] is undefined %} -{% set _ = params.update({"rules":[]}) %} -{% set ferm__tpl_rule = {} %} -{% for parameter in params.keys() | sort %} -{% if parameter not in [ 'comment', 'rule_state', 'name', 'rules', 'template', 'weight', 'weight_class', 'role', 'role_weight', 'delete', 'when', 'enabled' ] %} -{% if parameter == 'state' %} -{% if params["state"] not in [ 'present', 'absent', 'ignore' ] %} -{% set _ = ferm__tpl_rule.update({parameter:params[parameter]}) %} -{% endif %} -{% else %} -{% set _ = ferm__tpl_rule.update({parameter:params[parameter]}) %} -{% endif %} -{% endif %} -{% endfor %} -{% set _ = params["rules"].append(ferm__tpl_rule) %} -{% endif %} -{% if params["state"] is undefined or params["state"] not in [ 'present', 'absent', 'ignore' ] %} -{% if params["enabled"]|d() %} -{% if params["enabled"]|bool %} -{% set _ = params.update({"state":"present"}) %} -{% else %} -{% set _ = params.update({"state":"absent"}) %} -{% endif %} -{% elif params["rule_state"]|d() %} -{% set _ = params.update({"state":params["rule_state"]}) %} -{% else %} -{% set _ = params.update({"state":"present"}) %} -{% endif %} -{% endif %} -{% set _ = ferm__tpl_filtered_rules.update({rule_name:params}) %} -{% endfor %} -{{ ferm__tpl_filtered_rules | to_yaml }} diff --git a/templates/lookup/ferm__fix_dependent_rules.j2 b/templates/lookup/ferm__fix_dependent_rules.j2 index 725ca15..0a01dcf 100644 --- a/templates/lookup/ferm__fix_dependent_rules.j2 +++ b/templates/lookup/ferm__fix_dependent_rules.j2 @@ -1,9 +1,10 @@ {% if ferm__dependent_rules %} {% set ferm__tpl_dependent_rules = lookup('flattened', ferm__dependent_rules) %} {% else %} -{% set ferm__tpl_dependent_rules = {} %} +{% set ferm__tpl_dependent_rules = [] %} {% endif %} {% set ferm__tpl_fixed_rules = {} %} +{% set ferm__tpl_flattened_rules = [] %} {% if ferm__tpl_dependent_rules %} {% if ferm__tpl_dependent_rules is mapping %} {% set fixed_dict = ferm__tpl_dependent_rules %} @@ -29,4 +30,7 @@ {% endfor %} {% endif %} {% endif %} -{{ ferm__tpl_fixed_rules | to_json }} +{% for key, value in ferm__tpl_fixed_rules.iteritems() %} +{% set _ = ferm__tpl_flattened_rules.append(value) %} +{% endfor %} +{{ ferm__tpl_flattened_rules | to_json }} diff --git a/templates/lookup/ferm__parsed_rules.j2 b/templates/lookup/ferm__parsed_rules.j2 new file mode 100644 index 0000000..ce4f359 --- /dev/null +++ b/templates/lookup/ferm__parsed_rules.j2 @@ -0,0 +1,44 @@ +{% set ferm__tpl_filtered_rules = {} %} +{% for element in ferm__combined_rules %} +{% if element.name|d() and element.rule_state|d(element.state|d('present')) != 'ignore' %} +{% set rule_parameters = (ferm__tpl_filtered_rules[element.name].copy() if ferm__tpl_filtered_rules[element.name] is defined else {}) %} +{% if rule_parameters.rules is undefined and element.rules is undefined %} +{% set ferm__tpl_rule = {} %} +{% for parameter in element.keys() | sort %} +{% if parameter not in [ 'comment', 'rule_state', 'name', 'rules', 'template', 'weight', 'weight_class', 'role', 'role_weight', 'delete', 'when', 'enabled' ] %} +{% if parameter == 'state' %} +{% if element["state"] not in [ 'present', 'absent', 'ignore' ] %} +{% set _ = ferm__tpl_rule.update({parameter: element[parameter]}) %} +{% endif %} +{% else %} +{% set _ = ferm__tpl_rule.update({parameter: element[parameter]}) %} +{% endif %} +{% endif %} +{% endfor %} +{% if ferm__tpl_rule %} +{% set _ = rule_parameters.update({"rules":[]}) %} +{% set _ = rule_parameters["rules"].append(ferm__tpl_rule) %} +{% endif %} +{% endif %} +{% if element["state"] is undefined or element["state"] not in [ 'present', 'absent', 'ignore' ] %} +{% if element["enabled"]|d() %} +{% if element["enabled"]|bool %} +{% set _ = rule_parameters.update({"rule_state":"present"}) %} +{% else %} +{% set _ = rule_parameters.update({"rule_state":"absent"}) %} +{% endif %} +{% elif element["rule_state"]|d() %} +{% set _ = rule_parameters.update({"rule_state":element["rule_state"]}) %} +{% else %} +{% set _ = rule_parameters.update({"rule_state":"present"}) %} +{% endif %} +{% endif %} +{% for param_key in element.keys() %} +{% set _ = rule_parameters.update({ param_key: element[param_key] }) %} +{% endfor %} +{% if element.name|d() %} +{% set _ = ferm__tpl_filtered_rules.update({element.name: rule_parameters}) %} +{% endif %} +{% endif %} +{% endfor %} +{{ ferm__tpl_filtered_rules | to_yaml }} From d178cbb1e4f946e4ceab4bee694a5e682c9e73b7 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 20 Jun 2017 11:45:34 +0200 Subject: [PATCH 25/30] Remove code redundancy --- templates/etc/ferm/rules.d/rule.conf.j2 | 56 +++++++------------------ 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/templates/etc/ferm/rules.d/rule.conf.j2 b/templates/etc/ferm/rules.d/rule.conf.j2 index 98331cb..e5ae1fd 100644 --- a/templates/etc/ferm/rules.d/rule.conf.j2 +++ b/templates/etc/ferm/rules.d/rule.conf.j2 @@ -120,29 +120,17 @@ {% endif %} {% endif %} {% if config.interface_present|d() %} -{% if config.interface_present is string %} -{% if hostvars[inventory_hostname]["ansible_" + config.interface_present]|d() %} -{% set _ = ferm__tpl_config.update({'interface_present': [ config.interface_present ] }) %} +{% for interface in ([ config.interface_present ] if config.interface_present is string else config.interface_present) %} +{% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} +{% set _ = ferm__tpl_config.update({'interface_present': [ interface ] }) %} {% endif %} -{% else %} -{% for interface in config.interface_present %} -{% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} -{% set _ = ferm__tpl_config.update({'interface_present': [ interface ] }) %} -{% endif %} -{% endfor %} -{% endif %} +{% endfor %} {% elif config.interfaces_present|d() %} -{% if config.interfaces_present is string %} -{% if hostvars[inventory_hostname]["ansible_" + config.interfaces_present]|d() %} -{% set _ = ferm__tpl_config.update({'interface_present': [ config.interface_present ] }) %} +{% for interface in ([ config.interfaces_present ] if config.interfaces_present is string else config.interfaces_present) %} +{% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} +{% set _ = ferm__tpl_config.update({'interface_present': [ interface ] }) %} {% endif %} -{% else %} -{% for interface in config.interfaces_present %} -{% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} -{% set _ = ferm__tpl_config.update({'interface_present': [ interface ] }) %} -{% endif %} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} {% if config.outerface|d() %} {% set _ = ferm__tpl_config.update({'outerface': ([ config.outerface ] if config.outerface is string else config.outerface) }) %} @@ -150,29 +138,17 @@ {% set _ = ferm__tpl_config.update({'outerface': ([ config.outerfaces ] if config.outerfaces is string else config.outerfaces) }) %} {% endif %} {% if config.outerface_present|d() %} -{% if config.outerface_present is string %} -{% if hostvars[inventory_hostname]["ansible_" + config.outerface_present]|d() %} -{% set _ = ferm__tpl_config.update({'outerface_present': [ config.outerface_present ] }) %} +{% for outerface in ([ config.outerface_present ] if config.outerface_present is string else config.outerface_present) %} +{% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} +{% set _ = ferm__tpl_config.update({'outerface_present': [ outerface ] }) %} {% endif %} -{% else %} -{% for outerface in config.outerface_present %} -{% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} -{% set _ = ferm__tpl_config.update({'outerface_present': [ outerface ] }) %} -{% endif %} -{% endfor %} -{% endif %} +{% endfor %} {% elif config.outerfaces_present|d() %} -{% if config.outerfaces_present is string %} -{% if hostvars[inventory_hostname]["ansible_" + config.outerfaces_present]|d() %} -{% set _ = ferm__tpl_config.update({'outerface_present': [ config.outerfaces_present ] }) %} +{% for outerface in ([ config.outerfaces_present ] if config.outerfaces_present is string else config.outerfaces_present) %} +{% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} +{% set _ = ferm__tpl_config.update({'outerface_present': [ outerface ] }) %} {% endif %} -{% else %} -{% for outerface in config.outerfaces_present %} -{% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} -{% set _ = ferm__tpl_config.update({'outerface_present': [ outerface ] }) %} -{% endif %} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} {% if config.protocol|d() %} {% set _ = ferm__tpl_config.update({'protocol': ([ config.protocol ] if config.protocol is string else config.protocol) }) %} From 3f0647e6029935d1fb27b34ce7fd25c9192cd895 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Tue, 20 Jun 2017 21:33:36 +0200 Subject: [PATCH 26/30] Changelog cleanup --- CHANGES.rst | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 023c6a3..a221bfa 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -36,11 +36,9 @@ Changed rejected by default. [gaudenz] - The data format of the firewall rules has been redesigned. Rules can now be - defined as nested YAML dictionaries, existing default or dependent rules can + defined as nested YAML lists, existing default or dependent rules can be easily modified through the Ansible inventory, multiple firewall rules can - be included in one configuration file. The role is compatible with the old, - list-based data format, however dictionary-based format should be preferred. - [drybjed_] + be included in one configuration file. [drybjed_] - The firewall rules are now read from the :file:`/etc/ferm/rules.d/` directory to help with transition to the new data format and avoid tab-completion @@ -67,9 +65,6 @@ Changed - The :file:`/etc/ferm/ferm.conf` configuration file will be now properly diverted to preserve the original. [drybjed_] -- The rule parser is redesigned again and support for YAML dictionaries is - dropped, only lists can be used. [drybjed_] - Removed ~~~~~~~ From 988706c38a12a5a94e190d4526be424593696bdf Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Sat, 8 Jul 2017 00:02:14 +0200 Subject: [PATCH 27/30] Remove 40 lines of redundant code from Jinja2 templates (By adding 400 lines of macro code :) --- templates/etc/default/ferm.j2 | 5 +- templates/etc/ferm/rules.d/rule.conf.j2 | 85 +++------ templates/import/debops__tpl_macros.j2 | 236 ++++++++++++++++++++++++ 3 files changed, 259 insertions(+), 67 deletions(-) create mode 100644 templates/import/debops__tpl_macros.j2 diff --git a/templates/etc/default/ferm.j2 b/templates/etc/default/ferm.j2 index c909f3e..9a0a15f 100644 --- a/templates/etc/default/ferm.j2 +++ b/templates/etc/default/ferm.j2 @@ -8,18 +8,17 @@ FAST=no # cache the output of ferm --lines in /var/cache/ferm? CACHE=no -# additional paramaters for ferm (like --def '=bar') +# additional parameters for ferm (like --def '=bar') OPTIONS= # Enable the ferm init script? (i.e. run on bootup) {% if ferm__enabled | bool %} ENABLED="yes" {% else %} -{% if ((ansible_local|d() and ansible_local.ferm|d() and ansible_local.ferm.enabled|d() and ansible_local.ferm.enabled | bool) and +{% if ((ansible_local|d() and ansible_local.ferm|d() and ansible_local.ferm.enabled|d() | bool) and not ferm__enabled | bool) %} ENABLED="yes" {% else %} ENABLED="no" {% endif %} {% endif %} - diff --git a/templates/etc/ferm/rules.d/rule.conf.j2 b/templates/etc/ferm/rules.d/rule.conf.j2 index e5ae1fd..730e697 100644 --- a/templates/etc/ferm/rules.d/rule.conf.j2 +++ b/templates/etc/ferm/rules.d/rule.conf.j2 @@ -1,4 +1,5 @@ # {{ ansible_managed }} +{% import 'templates/import/debops__tpl_macros.j2' as debops__tpl_macros with context %} {% macro print_list(elements, prefix='', postfix='') %}{% if elements | length == 1 %}{{ ((prefix + ' ') if prefix else '') + elements | join(' ') + ((' ' + postfix) if postfix else '') }}{% else %}{{ ((prefix + ' ') if prefix else '') + '(' + (elements | join(' ')) + ')' + ((' ' + postfix) if postfix else '') }}{% endif %}{% endmacro %} {% macro print_rule(config) %} @@ -22,28 +23,10 @@ {% if config.type|d() %} {% set _ = ferm__tpl_config.update({'type': config.type }) %} {% endif %} -{% if config.domain|d() %} -{% set _ = ferm__tpl_config.update({'domain': ([ config.domain ] if config.domain is string else config.domain) }) %} -{% elif config.domains|d() %} -{% set _ = ferm__tpl_config.update({'domain': ([ config.domains ] if config.domains is string else config.domains) }) %} -{% else %} -{% set _ = ferm__tpl_config.update({'domain': ([ ferm__domains ] if ferm__domains is string else ferm__domains) }) %} -{% endif %} +{% set _ = ferm__tpl_config.update({'domain': (debops__tpl_macros.flattened(config.domain|d(config.domains|d(ferm__domains))) | from_json) }) %} {% if ferm__tpl_config['type'] != 'dmz' %} -{% if config.table|d() %} -{% set _ = ferm__tpl_config.update({'table': ([ config.table ] if config.table is string else config.table) }) %} -{% elif config.tables|d() %} -{% set _ = ferm__tpl_config.update({'table': ([ config.tables ] if config.tables is string else config.tables) }) %} -{% else %} -{% set _ = ferm__tpl_config.update({'table': [ 'filter' ] }) %} -{% endif %} -{% if config.chain|d() %} -{% set _ = ferm__tpl_config.update({'chain': ([ config.chain ] if config.chain is string else config.chain) }) %} -{% elif config.chains|d() %} -{% set _ = ferm__tpl_config.update({'chain': ([ config.chains ] if config.chains is string else config.chains) }) %} -{% else %} -{% set _ = ferm__tpl_config.update({'chain': [ 'INPUT' ] }) %} -{% endif %} +{% set _ = ferm__tpl_config.update({'table': (debops__tpl_macros.flattened(config.table|d(config.tables|d('filter'))) | from_json) }) %} +{% set _ = ferm__tpl_config.update({'chain': (debops__tpl_macros.flattened(config.chain|d(config.chains|d('INPUT'))) | from_json) }) %} {% endif %} {% if ferm__tpl_config['domain']|d() %} {% set _ = ferm__tpl_config['domain_args'].append(print_list(ferm__tpl_config['domain'], prefix='domain')) %} @@ -91,11 +74,7 @@ {% if config.subchain|d() %} {% set _ = ferm__tpl_config.update({'subchain': (ferm__tpl_config['type'] + "-" + config.name | d((ferm__tpl_config['dport'][0] if ferm__tpl_config['dport']|d() else "rules")))}) %} {% endif %} -{% if config.interface|d() %} -{% set _ = ferm__tpl_config.update({'interface': ([ config.interface ] if config.interface is string else config.interface) }) %} -{% elif config.interfaces|d() %} -{% set _ = ferm__tpl_config.update({'interface': ([ config.interfaces ] if config.interfaces is string else config.interfaces) }) %} -{% endif %} +{% set _ = ferm__tpl_config.update({'interface': (debops__tpl_macros.flattened(config.interface|d(config.interfaces)) | from_json) }) %} {% if ferm__tpl_config['type'] == 'connection_tracking' %} {% set _ = ferm__tpl_config.update({'tracking_invalid_target': (config.tracking_invalid_target | d('DROP'))}) %} {% set _ = ferm__tpl_config.update({'tracking_active_target': (config.tracking_active_target | d('ACCEPT'))}) %} @@ -113,47 +92,25 @@ {% if config.private_ip|d() %} {% set _ = ferm__tpl_config.update({'private_ip': ([ config.private_ip ] if config.private_ip is string else config.private_ip) }) %} {% endif %} -{% if config.port|d() %} -{% set _ = ferm__tpl_config.update({'dmz_ports': ([ config.port ] if config.port is string else config.port) }) %} -{% elif config.ports|d() %} -{% set _ = ferm__tpl_config.update({'dmz_ports': ([ config.ports ] if config.ports is string else config.ports) }) %} +{% if config.port|d() or config.ports|d() %} +{% set _ = ferm__tpl_config.update({'dmz_ports': (debops__tpl_macros.flattened(config.port|d(config.ports))) | from_json }) %} {% endif %} {% endif %} -{% if config.interface_present|d() %} -{% for interface in ([ config.interface_present ] if config.interface_present is string else config.interface_present) %} -{% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} -{% set _ = ferm__tpl_config.update({'interface_present': [ interface ] }) %} -{% endif %} -{% endfor %} -{% elif config.interfaces_present|d() %} -{% for interface in ([ config.interfaces_present ] if config.interfaces_present is string else config.interfaces_present) %} -{% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} -{% set _ = ferm__tpl_config.update({'interface_present': [ interface ] }) %} -{% endif %} -{% endfor %} -{% endif %} -{% if config.outerface|d() %} -{% set _ = ferm__tpl_config.update({'outerface': ([ config.outerface ] if config.outerface is string else config.outerface) }) %} -{% elif config.outerfaces|d() %} -{% set _ = ferm__tpl_config.update({'outerface': ([ config.outerfaces ] if config.outerfaces is string else config.outerfaces) }) %} -{% endif %} -{% if config.outerface_present|d() %} -{% for outerface in ([ config.outerface_present ] if config.outerface_present is string else config.outerface_present) %} -{% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} -{% set _ = ferm__tpl_config.update({'outerface_present': [ outerface ] }) %} -{% endif %} -{% endfor %} -{% elif config.outerfaces_present|d() %} -{% for outerface in ([ config.outerfaces_present ] if config.outerfaces_present is string else config.outerfaces_present) %} -{% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} -{% set _ = ferm__tpl_config.update({'outerface_present': [ outerface ] }) %} -{% endif %} -{% endfor %} +{% for interface in (debops__tpl_macros.flattened(config.interface_present|d(config.interfaces_present)) | from_json) %} +{% if hostvars[inventory_hostname]["ansible_" + interface]|d() %} +{% set _ = ferm__tpl_config.update({'interface_present': [ interface ] }) %} +{% endif %} +{% endfor %} +{% if config.outerface|d() or config.outerfaces|d() %} +{% set _ = ferm__tpl_config.update({'outerface': (debops__tpl_macros.flattened(config.outerface|d(config.outerfaces)) | from_json) }) %} {% endif %} -{% if config.protocol|d() %} -{% set _ = ferm__tpl_config.update({'protocol': ([ config.protocol ] if config.protocol is string else config.protocol) }) %} -{% elif config.protocols|d() %} -{% set _ = ferm__tpl_config.update({'protocol': ([ config.protocols ] if config.protocols is string else config.protocols) }) %} +{% for outerface in (debops__tpl_macros.flattened(config.outerface_present|d(config.outerfaces_present)) | from_json) %} +{% if hostvars[inventory_hostname]["ansible_" + outerface]|d() %} +{% set _ = ferm__tpl_config.update({'outerface_present': [ outerface ] }) %} +{% endif %} +{% endfor %} +{% if config.protocol|d() or config.protocols|d() %} +{% set _ = ferm__tpl_config.update({'protocol': (debops__tpl_macros.flattened(config.protocol|d(config.protocols)) | from_json) }) %} {% endif %} {% if config.protocol_syn|d() %} {% if config.protocol_syn | bool %} diff --git a/templates/import/debops__tpl_macros.j2 b/templates/import/debops__tpl_macros.j2 new file mode 100644 index 0000000..5a1cc15 --- /dev/null +++ b/templates/import/debops__tpl_macros.j2 @@ -0,0 +1,236 @@ +{# vim: foldmarker=[[[,]]]:foldmethod=marker +# Commonly used set of macros in DebOps. +# It can be imported in repositories as needed. +# Changes to this file should go upstream: https://github.com/debops/debops-playbooks/blob/master/templates/debops__tpl_macros.j2 +# +# Copyright [[[ +# ============= +# +# Copyright (C) 2014-2017 Maciej Delmanowski +# Copyright (C) 2015-2017 Robin Schneider +# Copyright (C) 2014-2017 DebOps https://debops.org/ +# +# This file is part of DebOps. +# +# DebOps is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# DebOps is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with DebOps. If not, see https://www.gnu.org/licenses/. +# +# ]]] +# +# Usage [[[ +# ========= +# +# Copy the template file to `./templates/import/debops__tpl_macros.j2` of your +# role and import it from there into various other templates or even use it +# in templates which are called by {{ lookup("template", ...) }}. +# +# Make sure to retain the filename of this file so that automatic updates of +# this file can be implemented. +# +# To use the macros in your own template, this file needs to be imported like so: +# +# {% import 'templates/import/debops__tpl_macros.j2' as debops__tpl_macros with context %} +# +# Then you can start using the macros like this: +# +# {{ debops__tpl_macros.indent(some_content, 4) }} +# +# ]]] #} + +{% macro get_realm_yaml_list(domains, fallback_realm) %}{# [[[ #} +{% set custom_realm_list = [] %} +{% if domains and (ansible_local|d() and ansible_local.pki|d() and ansible_local.pki.known_realms|d()) %} +{% for domain in (get_yaml_list_for_elem(domains) | from_yaml) %} +{% if domain in ansible_local.pki.known_realms %} +{% set _ = custom_realm_list.append(domain) %} +{% elif (domain.split('.')[1:] | join('.')) in ansible_local.pki.known_realms %} +{% set _ = custom_realm_list.append(domain.split('.')[1:] | join('.')) %} +{% endif %} +{% endfor %} +{% endif %} +{% if custom_realm_list|length == 0 %} +{% set _ = custom_realm_list.append(fallback_realm) %} +{% endif %} +{{ custom_realm_list | to_nice_yaml }} +{% endmacro %}{# ]]] #} + +{% macro get_apache_version() %}{# [[[ #} +{{ ansible_local.apache.version + if (ansible_local|d() and ansible_local.apache|d() and + ansible_local.apache.version|d()) + else "2.4.0" -}} +{% endmacro %}{# ]]] #} + +{% macro get_apache_min_version() %}{# [[[ #} +{{ ansible_local.apache.min_version + if (ansible_local|d() and ansible_local.apache|d() and + ansible_local.apache.min_version|d()) + else "2.4.0" -}} +{% endmacro %}{# ]]] #} + +{% macro get_openssl_version() %}{# [[[ #} +{{ ansible_local.pki.openssl_version + if (ansible_local|d() and ansible_local.pki|d() and + ansible_local.pki.openssl_version|d()) + else "0.0.0" }} +{% endmacro %}{# ]]] #} + +{% macro get_gnutls_version() %}{# [[[ #} +{{ ansible_local.pki.gnutls_version + if (ansible_local|d() and ansible_local.pki|d() and + ansible_local.pki.gnutls_version|d()) + else "0.0.0" }} +{% endmacro %}{# ]]] #} + +{% macro indent(content, width=4, indentfirst=False) %}{# [[[ #} +{# +## Fixed version of the `indent` filter which does not insert trailing spaces on empty lines. +## Note that you can not use this macro like a filter but have to use it like a regular macro. +## Example: {{ debops__tpl_macros.indent(some_content, 4) }} +## +## Python re.sub seems to default to re.MULTILINE in Ansible. +#} +{{ content | indent(width, indentfirst) | regex_replace("[ \\t\\r\\f\\v]+(\\n|$)", "\\1") -}} +{% endmacro %}{# ]]] #} + +{% macro merge_dict(current_dict, to_merge_dict, dict_key='name') %}{# [[[ #} +{# +## Recursively merges nested dictionaries or a nested dictionary with a list of +## dictionaries. In the second case, a key name can be given whose value will +## be used as top-level key for the dictionary item. Note that for merging simple +## dictionaries you should use the regular `combined` Jinja filter. +## +## This can be used to define default variables as YAML dictionaries and then +## use YAML lists of dictionaries either in the inventory or in role dependent +## variables which lets you add multiple YAML lists together. +## +## Arguments: +## current_dict: Nested dictionary whose values might get overwritten if +## defined in `to_merge_dict`. +## to_merge_dict: Dictionary or list of dictionaries being merged into `current_dict`. +## dict_key: Key in the `to_merge_dict` item which is used to match the +## dictionary item being merged in `current_dict` in case `to_merge_dict` +## is a list of dictionaries. +## +## Return: JSON-formatted dictionary +## +## Examples: +## +## 1. Merge single-nested dictionaries: +## {{ debops__tmpl_macros.merge_dict({'item1': {'key1': 'val1', 'key2': 'val2'}, +## 'item2': {'key3': 'val3', 'key4': 'val4'}}, +## {'item1': {'key2': 'new_val2'}, +## 'item2': {'key5': 'val5' }}) }} +## +## Will output: {"item1": {"key1": "val1", "key2": "new_val2"}, +## "item2": {"key3": "val3", "key4": "val4", "key5": "val5"}} +## +## 2. Merge double-nested dictionaries: +## {{ debops__tmpl_macros.merge_dict({'item1': {'prop1': {'key1': 'val1'}}}, +## {'item1': {'prop1': {'key3': 'val3'}, +## 'prop2': {'key2': 'val2'}}}) }} +## +## Will output: {"item1": {"prop1": {"key1": "val1", "key3": "val3"}, +## "prop2": {"key2": "val2"}}} +## +## 3. Merge nested dictionary with list of dictionaries: +## {{ debops__tmpl_macros.merge_dict({'item1': {'key1': 'val1', 'key2': 'val2'}}, +## [{'name': 'item2', 'key3': 'val3'}, +## {'name': 'item3', 'key4': 'val4'}]) }} +## +## Will output: {"item1: {"key1": "val1", "key2": "val2"}, +## "item2: {"name": "item2", "key3": "val3"}, +## "item3: {"name": "item3", "key4": "val4"}} +#} +{% set merged_dict = current_dict %} +{% if to_merge_dict %} +{% if to_merge_dict is mapping %} +{% for dict_name in to_merge_dict.keys() | sort %} +{% if to_merge_dict[dict_name][dict_key]|d() %} +{% set _ = merged_dict.update({to_merge_dict[dict_name][dict_key]:(current_dict[to_merge_dict[dict_name][dict_key]]|d({}) | combine(to_merge_dict[dict_name], recursive=True))}) %} +{% elif to_merge_dict[dict_name][dict_key] is undefined %} +{% set _ = merged_dict.update({dict_name:(current_dict[dict_name]|d({}) | combine(to_merge_dict[dict_name], recursive=True))}) %} +{% endif %} +{% endfor %} +{% elif to_merge_dict is not string and to_merge_dict is not mapping %} +{% set flattened_dict = lookup("flattened", to_merge_dict) %} +{% for element in ([ flattened_dict ] if flattened_dict is mapping else flattened_dict) %} +{% if element[dict_key]|d() %} +{% set _ = merged_dict.update({element[dict_key]:(current_dict[element[dict_key]]|d({}) | combine(element, recursive=True))}) %} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} +{{ merged_dict | to_json }} +{% endmacro %}{# ]]] #} + +{% macro flattened() %}{# [[[ #} +{# This macro does what the flattened lookup from Ansible fails to do in Jinja templates as of Ansible 2.2. +## All macro arguments are flattened into one "flat" list. +## Uses a less known feature of Jinja for using the *args and *kwargs syntax as known from +## Python. Even if the macro does not declare any arguments, it will happyily +## flatten any non-key-value arguments you provide. +## Additional key-value arguments can be used to influence the behavoir of the macro. +## The macro uses recursion to flatten nested lists. +## Ref: https://stackoverflow.com/questions/13944751/args-kwargs-in-jinja2-macros +## Retruns flattened object as JSON which you will need to deserialize using `from_json`. +## Usage: +## +## {{ debops__tpl_macros.flattened(['list1', 55, ["deeplist1", "deeplist elem", True, False, undefined], {'mapping': True}], 'raw1', 22, True, False) }} +## → ["list1", 55, "deeplist1", "deeplist elem", true, false, "raw1", 22, true, false] +## +## {{ debops__tpl_macros.flattened(['list1', 55, ["deeplist1", "deeplist elem", True, False, undefined], {'mapping': True}], 'raw1', 22, True, False, filter_undef=False) }} +## → ["list1", 55, "deeplist1", "deeplist elem", true, false, null, "raw1", 22, true, false] +## +## {{ debops__tpl_macros.flattened(['list1', 55, ["deeplist1", "deeplist elem", True, False, undefined], {'mapping': True}], 'raw1', 22, True, False, filter_mapping=False) }} +## → ["list1", 55, "deeplist1", "deeplist elem", true, false, {"mapping": true}, "raw1", 22, true, false] +## +## Ansible versions tested with: 2.1, 2.2 +## Jinja versions tested with: 2.8.1 +#} +{% set filter_undef = kwargs.filter_undef|d(True) | bool %} +{% set filter_mapping = kwargs.filter_mapping|d(True) | bool %} +{# The following options are planned but currently don’t work: #} +{% set append_mapping_keys = kwargs.append_mapping_keys|d(False) | bool %} +{% set append_mapping_values = kwargs.append_mapping_values|d(False) | bool %} +{# +{{ "filter_undef:" + (filter_undef | string) }} +{{ "filter_mapping:" + (filter_mapping | string) }} +{{ "varargs:" + (varargs | string) }} +#} +{% set elem_flattened = [] %} +{% for arg in varargs %} +{# +{{ "arg: " + (arg | string) }} +#} +{% if filter_undef and (arg is undefined) %} +{# Filter out. #} +{% elif append_mapping_keys and (arg is mapping) %} +{# Does not work as of Jinja 2.8? #} +{% set _ = elem_flattened.extend(flattened(arg.keys(), filter_undef=filter_undef, filter_mapping=filter_mapping) | from_json) %} +{% elif append_mapping_values and (arg is mapping) %} +{# Does not work as of Jinja 2.8? #} +{% set _ = elem_flattened.extend(flattened(arg.values(), filter_undef=filter_undef, filter_mapping=filter_mapping) | from_json) %} +{% elif filter_mapping and (arg is mapping) %} +{# Filter out. #} +{% elif (arg is undefined) %} +{% set _ = elem_flattened.append(None) %} +{% elif (arg is string) or (arg is number) or (arg is sameas True) or (arg is sameas False) or (arg is mapping) %} +{% set _ = elem_flattened.append(arg) %} +{% elif (arg is iterable) %} +{% for element in arg %} +{% set _ = elem_flattened.extend(flattened(element, filter_undef=filter_undef, filter_mapping=filter_mapping) | from_json) %} +{% endfor %} +{% endif %} +{% endfor %} +{{ elem_flattened | to_json }} +{% endmacro %}{# ]]] #} From 763eef72543f9c0a5b33353e7c31dd7af2bce456 Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Sat, 8 Jul 2017 00:09:22 +0200 Subject: [PATCH 28/30] Regen README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 930a63d..1ba09a0 100644 --- a/README.md +++ b/README.md @@ -51,4 +51,4 @@ License: [GPL-3.0](https://tldrlegal.com/license/gnu-general-public-license-v3-% *** -This role is part of the [DebOps](https://debops.org/) project. README generated by [ansigenome](https://github.com/nickjj/ansigenome/). +This role is part of [DebOps](https://debops.org/). README generated by [ansigenome](https://github.com/nickjj/ansigenome/). From 1bc976f056d92e60c2fdc3d4baaa2cf17045da8a Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Sat, 8 Jul 2017 00:10:12 +0200 Subject: [PATCH 29/30] debops-optimize --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index a221bfa..10ccbaf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,7 +8,7 @@ Changelog **debops.ferm** This project adheres to `Semantic Versioning `__ -and `human-readable changelog `__. +and `human-readable changelog `__. The current role maintainer_ is drybjed_. From f8031b93d004c5089ed03bdf6d9ff8ac7706fa02 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Wed, 12 Jul 2017 08:26:42 +0200 Subject: [PATCH 30/30] Prepare next release --- CHANGES.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 10ccbaf..156f9eb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -16,7 +16,13 @@ The current role maintainer_ is drybjed_. `debops.ferm master`_ - unreleased ---------------------------------- -.. _debops.ferm master: https://github.com/debops/ansible-ferm/compare/v0.2.2...master +.. _debops.ferm master: https://github.com/debops/ansible-ferm/compare/v0.3.0...master + + +`debops.ferm v0.3.0`_ - 2017-07-12 +---------------------------------- + +.. _debops.ferm v0.3.0: https://github.com/debops/ansible-ferm/compare/v0.2.2...v0.3.0 Added ~~~~~