diff --git a/firewalld/files/policy.xml b/firewalld/files/policy.xml
new file mode 100644
index 0000000..0ae4bab
--- /dev/null
+++ b/firewalld/files/policy.xml
@@ -0,0 +1,179 @@
+
+
+{%- macro rich_rule(rule) %}
+
+ {%- if 'ipset' in rule %}
+
+ {%- endif %}
+ {%- if 'source' in rule %}
+
+ {%- endif %}
+ {%- if 'destination' in rule %}
+
+ {%- endif %}
+ {%- if 'service' in rule %}
+
+ {%- endif %}
+ {%- if 'port' in rule %}
+
+ {%- endif %}
+ {%- if 'protocol' in rule %}
+
+ {%- endif %}
+ {%- if 'icmp_block' in rule %}
+
+ {%- endif %}
+ {%- if 'icmp_type' in rule %}
+
+ {%- endif %}
+ {%- if 'masquerade' in rule %}
+ {%- if rule.masquerade %}{%- endif %}
+ {%- endif %}
+ {%- if 'forward_port' in rule %}
+ {%- if 'comment' in rule.forward_port %}
+
+ {%- endif %}
+
+ {%- endif %}
+ {%- if 'source_port' in rule %}
+ {%- if 'comment' in rule.source_port %}
+
+ {%- endif %}
+
+ {%- endif %}
+ {%- if 'log' in rule %}
+
+ {%- if 'limit' in rule.log %}
+
+ {%- endif %}
+
+ {%- endif %}
+ {%- if 'audit' in rule %}
+ {%- if 'limit' in rule.audit %} {%- endif %}
+ {%- endif %}
+ {%- if 'accept' in rule %}
+ {%- if rule.accept is mapping and 'limit' in rule.accept %} {%- endif %}
+ {%- endif %}
+ {%- if 'reject' in rule %}
+
+ {%- endif %}
+ {%- if 'drop' in rule %}
+
+ {%- endif %}
+
+{%- endmacro %}
+
+ {% if 'short' in policy %}{{ policy.short }}{% else %}{{ name }}{% endif %}
+ {% if 'description' in policy %}{{ policy.description }}{% endif %}
+ {% if 'ingress_zone' in policy %}{% endif %}
+ {% if 'egress_zone' in policy %}{% endif %}
+
+{%- if 'sources' in policy %}
+ {%- for v in policy.sources %}
+ {%- if 'comment' in v %}
+
+
+ {%- else %}
+
+ {%- endif %}
+ {%- endfor %}
+{%- endif %}
+{%- if 'ipsets' in policy %}
+ {%- for v in policy.ipsets %}
+ {%- if 'comment' in v %}
+
+
+ {%- else %}
+
+ {%- endif %}
+ {%- endfor %}
+{%- endif %}
+{%- for k,val in policy.items() %}
+ {%- if k.endswith("services") %}
+ {%- for v in val %}
+
+ {%- endfor %}
+ {%- endif %}
+{%- endfor %}
+{%- if 'ports' in policy %}
+ {%- for v in policy.ports %}
+ {%- if 'comment' in v %}
+
+ {%- endif %}
+
+ {%- endfor %}
+{%- endif %}
+{%- if 'protocols' in policy %}
+ {%- for v in policy.protocols %}
+
+ {%- endfor %}
+{%- endif %}
+{%- if 'icmp_blocks' in policy %}
+ {%- for v in policy.icmp_blocks %}
+
+ {%- endfor %}
+{%- endif %}
+{%- if 'icmp_block_inversion' in policy and policy.icmp_block_inversion %}
+
+{%- endif %}
+{%- if 'masquerade' in policy %}
+ {%- if policy.masquerade %}
+
+ {%- endif %}
+{%- endif %}
+{%- if 'forward_ports' in policy %}
+ {%- for v in policy.forward_ports %}
+ {%- if 'comment' in v %}
+
+ {%- endif %}
+
+ {%- endfor %}
+{%- endif %}
+{%- if 'source_ports' in policy %}
+ {%- for v in policy.source_ports %}
+ {%- if 'comment' in v %}
+
+ {%- endif %}
+
+ {%- endfor %}
+{%- endif %}
+{%- if 'rich_rules' in policy %}
+ {%- if policy.rich_rules is list %}
+ {%- set rich_rules = policy.rich_rules %}
+ {%- else %}
+ {%- set expanded_ipset_rules = [] %}
+ {%- for name,rule in policy.rich_rules|dictsort %}
+ {%- if 'ipsets' in rule %}
+ {%- for ipset in rule.ipsets %}
+ {%- set tmp_rule = {} %}
+ {%- set _dummy = tmp_rule.update(rule) %}
+ {%- set _dummy = tmp_rule.update({'ipset':{'name':ipset}}) %}
+ {%- set _dummy = expanded_ipset_rules.append(tmp_rule) %}
+ {%- endfor %}
+ {%- else %}
+ {%- set _dummy = expanded_ipset_rules.append(rule) %}
+ {%- endif %}
+ {%- endfor %}
+ {%- set rich_rules = [] %}
+ {%- for rule in expanded_ipset_rules %}
+ {%- if 'services' in rule %}
+ {%- for service in rule.services %}
+ {%- set tmp_rule = {} %}
+ {%- set _dummy = tmp_rule.update(rule) %}
+ {%- set _dummy = tmp_rule.update({'service':service}) %}
+ {%- set _dummy = rich_rules.append(tmp_rule) %}
+ {%- endfor %}
+ {%- else %}
+ {%- set _dummy = rich_rules.append(rule) %}
+ {%- endif %}
+ {%- endfor %}
+ {%- endif %}
+ {%- for rule in rich_rules %}
+{{- rich_rule(rule) }}
+ {%- endfor %}
+{%- endif %}
+
diff --git a/firewalld/init.sls b/firewalld/init.sls
index 8e4aec8..c1bec7c 100644
--- a/firewalld/init.sls
+++ b/firewalld/init.sls
@@ -24,6 +24,7 @@ include:
- firewalld.backend
- firewalld.services
- firewalld.zones
+ - firewalld.policies
- firewalld.direct
# iptables service that comes with rhel/centos
diff --git a/firewalld/policies.sls b/firewalld/policies.sls
new file mode 100644
index 0000000..b03ed5b
--- /dev/null
+++ b/firewalld/policies.sls
@@ -0,0 +1,46 @@
+# == State: firewalld.policies
+#
+# This state ensures that /etc/firewalld/policies/ exists.
+#
+{% from "firewalld/map.jinja" import firewalld with context %}
+
+directory_firewalld_policies:
+ file.directory: # make sure this is a directory
+ - name: /etc/firewalld/policies
+ - user: root
+ - group: root
+ - mode: 750
+ - require:
+ - pkg: package_firewalld # make sure package is installed
+ - require_in:
+ - service: service_firewalld
+ - watch_in:
+ - cmd: reload_firewalld # reload firewalld config
+
+# == Define: firewalld.policies
+#
+# This defines a policy configuration, see firewalld.policy (5) man page.
+#
+{% for k, v in salt['pillar.get']('firewalld:policies', {}).items() %}
+{% set z_name = v.name|default(k) %}
+
+/etc/firewalld/policies/{{ z_name }}.xml:
+ file.managed:
+ - name: /etc/firewalld/policies/{{ z_name }}.xml
+ - user: root
+ - group: root
+ - mode: 644
+ - source: salt://firewalld/files/policy.xml
+ - template: jinja
+ - require:
+ - pkg: package_firewalld # make sure package is installed
+ - file: directory_firewalld_policies
+ - require_in:
+ - service: service_firewalld
+ - watch_in:
+ - cmd: reload_firewalld # reload firewalld config
+ - context:
+ name: {{ z_name }}
+ policy: {{ v|json }}
+
+{% endfor %}
diff --git a/pillar.example b/pillar.example
index 87d4690..2ee0aab 100644
--- a/pillar.example
+++ b/pillar.example
@@ -182,6 +182,36 @@ firewalld:
services:
- ssh
+ policies:
+ myOutputPolicy:
+ short: myOutputPolicy
+ target: DROP
+ ingress-zones: ANY
+ egress-zones: HOST
+ description: >-
+ This example, creates a policy that applies to traffic originating from the host
+ running firewalld and is destined to any zone. Or said differently traffic
+ in the OUTPUT chain.
+ services:
+ - http
+ - https
+ - ssh
+ rich_rules:
+ - family: ipv4
+ destination:
+ address: 8.8.8.8
+ port:
+ portid: 53
+ protocol: udp
+ accept: true
+ ports:
+ - comment: salt-master
+ port: 4505
+ protocol: tcp
+ - comment: salt-master
+ port: 4506
+ protocol: tcp
+
direct:
chain:
MYCHAIN: