diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aaed21e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +pkg/ +metadata.json +*.idea +*.swp +*.tmp diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..323b4df --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ + Copyright (c) 2014 David Bezuidenhout + + NOTICE THE LICENSE EXCEPTIONS BELOW. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + + EXCEPTIONS: + You MAY NOT upload parts or the whole work of this product on Github or + any other platform again UNLESS you are in possession of an agreement + by the author OR use the function called "FORK" provided on Github itself. + Other parts of the specified license above are not affected. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..34c1f0e --- /dev/null +++ b/README.rst @@ -0,0 +1,68 @@ +=========== +firewalld-formula +=========== + +Salt Stack Formula to set up and configure Firewalld, dynamically managed firewall with support for network/firewall zones to define the trust level of network connections or interfaces + +NOTICE BEFORE YOU USE +===================== + +* This formula aims to follow the conventions and recommendations described at http://docs.saltstack.com/topics/conventions/formulas.html + +TODO +==== + +* configure local pre-commit hooks (code syntax check based on file extension, check for ugly *utf-8 mac os white space*) + +Instructions +============ + +1. Add this repository as a `GitFS `_ backend in your Salt master config. + +2. Configure your Pillar top file (``/srv/pillar/top.sls``), see pillar.example + +3. Include this Formula within another Formula or simply define your needed states within the Salt top file (``/srv/salt/top.sls``). + +Available states +================ + +.. contents:: + :local: + +``firewalld`` +------- +Manage firewalld + +Additional resources +==================== + +None + +Formula Dependencies +==================== + +None + +Contributions +============= + +Contributions are always welcome. All development guidelines you have to know are + +* write clean code (proper YAML+Jinja syntax, no trailing whitespaces, no empty lines with whitespaces, LF only) +* set sane default settings +* test your code +* update README.rst doc + +Salt Compatibility +================== + +Tested with: + +* 2014.1.x + +OS Compatibility +================ + +Tested with: + +* CentOS 7 diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/firewalld/_config.sls b/firewalld/_config.sls new file mode 100644 index 0000000..86ff910 --- /dev/null +++ b/firewalld/_config.sls @@ -0,0 +1,29 @@ +# == State: firewalld._config +# +# This state configures firewalld. +# + +/etc/firewalld/: + file.directory: # make sure this is a directory + - user: root + - group: root + - mode: 750 + - require: + - pkg: firewalld # make sure package is installed + - watch_in: + - service: firewalld # restart service + +/etc/firewalld/firewalld.conf: + file: + - managed + - name: /etc/firewalld/firewalld.conf + - user: root + - group: root + - mode: 640 + - source: salt://firewalld/files/firewalld.conf + - template: jinja + - require: + - pkg: firewalld # make sure package is installed + - watch_in: + - service: firewalld # restart service + diff --git a/firewalld/_service.sls b/firewalld/_service.sls new file mode 100644 index 0000000..8f97906 --- /dev/null +++ b/firewalld/_service.sls @@ -0,0 +1,41 @@ +# == State: firewalld._service +# +# This state ensures that /etc/firewalld/services/ exists. +# +/etc/firewalld/services: + file.directory: # make sure this is a directory + - user: root + - group: root + - mode: 750 + - require: + - pkg: firewalld # make sure package is installed + - watch_in: + - service: firewalld # restart service + + +# == Define: firewalld._service +# +# This defines a service configuration, see firewalld.service (5) man page. +# You usually don't need this, you can simply add ports to zone. + +{% for k, v in salt['pillar.get']('firewalld:services', {}).items() %} +{% set s_name = v.name|default(k) %} + +/etc/firewalld/services/{{ s_name }}.xml: + file: + - managed + - name: /etc/firewalld/services/{{ s_name }}.xml + - user: root + - group: root + - mode: 644 + - source: salt://firewalld/files/service.xml + - template: jinja + - require: + - pkg: firewalld # make sure package is installed + - watch_in: + - service: firewalld # restart service + - context: + name: {{ s_name }} + service: {{ v }} + +{% endfor %} diff --git a/firewalld/_zone.sls b/firewalld/_zone.sls new file mode 100644 index 0000000..4ee8d8f --- /dev/null +++ b/firewalld/_zone.sls @@ -0,0 +1,140 @@ +# == State: firewalld._zone +# +# This state ensures that /etc/firewalld/zones/ exists. +# +/etc/firewalld/zones: + file.directory: # make sure this is a directory + - user: root + - group: root + - mode: 750 + - require: + - pkg: firewalld # make sure package is installed + - watch_in: + - service: firewalld # restart service + + +# == Define: firewalld._zone +# +# This defines a zone configuration, see firewalld.zone (5) man page. +# +{% for k, v in salt['pillar.get']('firewalld:zones', {}).items() %} +{% set z_name = v.name|default(k) %} + +/etc/firewalld/zones/{{ z_name }}.xml: + file: + - managed + - name: /etc/firewalld/zones/{{ z_name }}.xml + - user: root + - group: root + - mode: 644 + - source: salt://firewalld/files/zone.xml + - template: jinja + - require: + - pkg: firewalld # make sure package is installed + - watch_in: + - service: firewalld # restart service + - context: + name: {{ z_name }} + zone: {{ v }} + +{% endfor %} + + + +# === Parameters +# +# [*target*] can be one of {'ACCEPT', '%%REJECT%%', 'DROP'}. +# Used to accept, reject or drop every packet that +# doesn't match any rule (port, service, etc.). +# Default (when target is not specified) is reject. +# [*short*] short readable name +# [*description*] long description of zone +# [*interfaces*] list of interfaces to bind to a zone +# [*sources*] list of source addresses or source address +# ranges ("address/mask") to bind to a zone +# [*ports*] +# list of ports to open +# ports => [{ +# comment => optional, string +# port => mandatory, string, e.g. '1234' +# protocol => mandatory, string, e.g. 'tcp' },...] +# [*services*] list of predefined firewalld services +# [*icmp_blocks*] list of predefined icmp-types to block +# [*masquerade*] enable masquerading ? +# [*forward_ports*] +# list of ports to forward to other port and/or machine +# forward_ports => [{ +# comment => optional, string +# portid => mandatory, string, e.g. '123' +# protocol => mandatory, string, e.g. 'tcp' +# to_port => mandatory to specify either to_port or/and to_addr +# to_addr => mandatory to specify either to_port or/and to_addr },...] +# [*rich_rules*] +# list of rich language rules (firewalld.richlanguage(5)) +# You have to specify one (and only one) +# of {service, port, protocol, icmp_block, masquerade, forward_port} +# and one (and only one) of {accept, reject, drop} +# family - 'ipv4' or 'ipv6', optional, see Rule in firewalld.richlanguage(5) +# source => { optional, see Source in firewalld.richlanguage(5) +# address => mandatory, string, e.g. '192.168.1.0/24' +# invert => optional, bool, e.g. true } +# destination => { optional, see Destination in firewalld.richlanguage(5) +# address => mandatory, string +# invert => optional, bool, e.g. true } +# service - string, see Service in firewalld.richlanguage(5) +# port => { see Port in firewalld.richlanguage(5) +# portid => mandatory +# protocol => mandatory } +# protocol - string, see Protocol in firewalld.richlanguage(5) +# icmp_block - string, see ICMP-Block in firewalld.richlanguage(5) +# masquerade - bool, see Masquerade in firewalld.richlanguage(5) +# forward_port => { see Forward-Port in firewalld.richlanguage(5) +# portid => mandatory +# protocol => mandatory +# to_port => mandatory to specify either to_port or/and to_addr +# to_addr => mandatory to specify either to_port or/and to_addr } +# log => { see Log in firewalld.richlanguage(5) +# prefix => string, optional +# level => string, optional +# limit => string, optional } +# audit => { see Audit in firewalld.richlanguage(5) +# limit => string, optional } +# accept - any value, e.g. true, see Action in firewalld.richlanguage(5) +# reject => { see Action in firewalld.richlanguage(5) +# type => string, optional } +# drop - any value, e.g. true, see Action in firewalld.richlanguage(5) +# +# === Examples +# +# firewalld::zone { "custom": +# description => "This is an example zone", +# services => ["ssh", "dhcpv6-client"], +# ports => [{ +# comment => "for our dummy service", +# port => "1234", +# protocol => "tcp",},], +# masquerade => true, +# forward_ports => [{ +# comment => 'forward 123 to other machine', +# portid => '123', +# protocol => 'tcp', +# to_port => '321', +# to_addr => '1.2.3.4',},], +# rich_rules => [{ +# family => 'ipv4', +# source => { +# address => '192.168.1.0/24', +# invert => true,}, +# port => { +# portid => '123-321', +# protocol => 'udp',}, +# log => { +# prefix => 'local', +# level => 'notice', +# limit => '3/s',}, +# audit => { +# limit => '2/h',}, +# reject => { +# type => 'icmp-host-prohibited',}, +# },],} +# diff --git a/firewalld/files/firewalld.conf b/firewalld/files/firewalld.conf new file mode 100644 index 0000000..20910cb --- /dev/null +++ b/firewalld/files/firewalld.conf @@ -0,0 +1,35 @@ +{{pillar['headers']['salt']['file']}} +{% set firewalld = pillar.get('firewalld', {}) -%} +# firewalld config file + +# default zone +# The default zone used if an empty zone string is used. +# Default: public +DefaultZone={{ firewalld.default_zone|default('public') }} + +# Minimal mark +# Marks up to this minimum are free for use for example in the direct +# interface. If more free marks are needed, increase the minimum +# Default: 100 +MinimalMark={{ firewalld.minimal_mark|default('100') }} + +# Clean up on exit +# If set to no or false the firewall configuration will not get cleaned up +# on exit or stop of firewalld +# Default: yes +CleanupOnExit={{ firewalld.cleanup_on_exit|default('yes') }} + +# Lockdown +# If set to enabled, firewall changes with the D-Bus interface will be limited +# to applications that are listed in the lockdown whitelist. +# The lockdown whitelist file is lockdown-whitelist.xml +# Default: no +Lockdown={{ firewalld.lockdown|default('no') }} + +# IPv6_rpfilter +# Performs a reverse path filter test on a packet for IPv6. If a reply to the +# packet would be sent via the same interface that the packet arrived on, the +# packet will match and be accepted, otherwise dropped. +# The rp_filter for IPv4 is controlled using sysctl. +# Default: yes +IPv6_rpfilter={{ firewalld.IPv6_rpfilter|default('yes') }} diff --git a/firewalld/files/service.xml b/firewalld/files/service.xml new file mode 100644 index 0000000..bf2b424 --- /dev/null +++ b/firewalld/files/service.xml @@ -0,0 +1,25 @@ + +{{pillar['headers']['salt']['xml']}} + +{% if 'short' in service %}{{ service.short }}{% else %}{{ name }}{% endif %} +{% if 'description' in service %}{{ service.description }}{% endif %} +{% if 'ports' in service %} + {% if 'tcp' in service.ports %} + {% for v in service.ports.tcp %}{% endfor %} + {% endif %} + {% if 'udp' in service.ports %} + {% for v in service.ports.udp %}{% endfor %} + {% endif %} + {% if 'modules' in service %} + {% for v in service.modules %}{% endfor %} + {% endif %} +{% endif %} +{% if 'destinations' in service %} + {% if 'ipv4' in service.destinations %} + {% for v in service.destinations.ipv4 %}{% endfor %} + {% endif %} + {% if 'ipv6' in service.destinations %} + {% for v in service.destinations.ipv6 %}{% endfor %} + {% endif %} +{% endif %} + diff --git a/firewalld/files/zone.xml b/firewalld/files/zone.xml new file mode 100644 index 0000000..799bdee --- /dev/null +++ b/firewalld/files/zone.xml @@ -0,0 +1,97 @@ + +{{pillar['headers']['salt']['xml']}} + +{% if 'short' in zone %}{{ zone.short }}{% else %}{{ name }}{% endif %} +{% if 'description' in zone %}{{ zone.description }}{% endif %} + +{% if 'interfaces' in zone %} + {% for v in zone.interfaces %}{% endfor %} +{% endif %} +{% if 'sources' in zone %} + {% for v in zone.sources %}{% endfor %} +{% endif %} +{% if 'services' in zone %} + {% for v in zone.services %}{% endfor %} +{% endif %} +{% if 'ports' in zone %} + {% for v in zone.ports %} + {% if 'comment' in v %} + + {% endif %} + + {% endfor %} +{% endif %} +{% if 'icmp_blocks' in zone %} + {% for v in zone.icmp_blocks %}{% endfor %} +{% endif %} +{% if 'masquerade' in zone %}{% if zone.masquerade %}{% endif %}{% endif %} +{% if 'forward_ports' in zone %} + {% for v in zone.forward_ports %} + {% if 'comment' in v %} + + {% endif %} + + {% endfor %} +{% endif %} + +{% if 'rich_rules' in zone %} + {% for rule in zone.rich_rules %} + {% if 'family' in rule %} + + {% else %} + + {% 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 'masquerade' in rule %} + {% if rule.masquerade %}{% endif %} + {% endif %} + {% if 'forward_port' in rule %} + {% if 'comment' in rule.forward_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 %} + + {% endif %} + {% if 'reject' in rule %} + + {% endif %} + {% if 'drop' in rule %} + + {% endif %} + + {% endfor %} +{% endif %} + + + + + diff --git a/firewalld/init.sls b/firewalld/init.sls new file mode 100644 index 0000000..c59d059 --- /dev/null +++ b/firewalld/init.sls @@ -0,0 +1,41 @@ + +# == State: firewalld +# +# This state installs/runs firewalld. +# + + +{% if salt['pillar.get']('firewalld:enabled') %} +include: + - firewalld._config + - firewalld._service + - firewalld._zone + +# iptables service that comes with rhel/centos +iptables: + service: + - disabled + - enable: False + +ip6tables: + service: + - disabled + - enable: False + +firewalld: + pkg: + - installed + service: + - running # ensure it's running + - enable: True # start on boot + - require: + - pkg: firewalld + - file: /etc/firewalld/firewalld.conf # require this file + - service: iptables # ensure it's stopped + - service: ip6tables # ensure it's stopped +{% else %} +firewalld: + service: + - dead # ensure it's not running + - enable: False # don't start on boot +{% endif %} \ No newline at end of file diff --git a/pillar.example.sls b/pillar.example.sls new file mode 100644 index 0000000..61e4915 --- /dev/null +++ b/pillar.example.sls @@ -0,0 +1,56 @@ +# CentOS7 FirewallD firewall +firewalld: + enabled: True + default_zone: public + services: + sshcustom: + short: sshcustom + description: SSH on port 3232 and 5252. Secure Shell (SSH) is a protocol for logging into and executing commands on remote machines. It provides secure encrypted communications. If you plan on accessing your machine remotely via SSH over a firewalled interface, enable this option. You need the openssh-server package installed for this option to be useful. + ports: + tcp: + - 3232 + - 5252 + modules: + - some_module_to_load + destinations: + ipv4: + - 224.0.0.251 + - 224.0.0.252 + ipv6: + - ff02::fb + - ff02::fc + zones: + public: + short: Public + description: "For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted." + services: + - http + - https + - ssh + - dhcpv6-client +# ports: +# - comment: For our dummy service +# port: 1234 +# protocol: tcp +# forward_ports: +# - comment: forward 123 to other machine +# portid: 123 +# protocol: tcp +# to_port: 321 +# to_addr: 1.2.3.4 +# rich_rules: +# - family: ipv4 +# source: +# address: 192.168.1.0/24 +# invert: true +# port: +# portid: 123-321 +# protocol: udp +# log: +# prefix: local +# level: notice +# limit: 3/s +# audit: +# limit: 2/h +# reject: +# type: icmp-host-prohibited