ufw-formula | |||||
=========== | |||||
This module manages your firewall using ufw with pillar configured rules. | |||||
See the full [Salt Formulas installation and usage instructions](http://docs.saltstack.com/topics/development/conventions/formulas.html). | |||||
Usage | |||||
----- | |||||
All the configuration for the firewall is done via pillar (pillar.example). | |||||
Enable firewall, applying default configuration: | |||||
```javascript | |||||
ufw: | |||||
enabled: True | |||||
``` | |||||
Allow 80/tcp (http) traffic from only two remote addresses: | |||||
``` | |||||
ufw: | |||||
services: | |||||
http: | |||||
protocol: tcp | |||||
from_addr: | |||||
- 10.0.2.15 | |||||
- 10.0.2.16 | |||||
``` | |||||
Allow 443/tcp (https) traffic from network 10.0.0.0/8 to an specific local ip: | |||||
``` | |||||
ufw: | |||||
services: | |||||
https: | |||||
protocol: tcp | |||||
from_addr: | |||||
- 10.0.0.0/8 | |||||
to_addr: 10.0.2.1 | |||||
``` | |||||
Allow from a service port: | |||||
``` | |||||
ufw: | |||||
services: | |||||
smtp: | |||||
protocol: tcp | |||||
``` | |||||
Allow from an specific port, by number: | |||||
``` | |||||
ufw: | |||||
services: | |||||
139: | |||||
protocol: tcp | |||||
``` | |||||
Allow from a range of ports, udp: | |||||
``` | |||||
ufw: | |||||
services: | |||||
"10000:20000": | |||||
protocol: udp | |||||
``` | |||||
Allow from two specific ports, udp: | |||||
``` | |||||
ufw: | |||||
services: | |||||
"30000,40000": | |||||
protocol: udp | |||||
``` | |||||
Allow an application defined at /etc/ufw/applications.d/: | |||||
``` | |||||
ufw: | |||||
applications: | |||||
- OpenSSH | |||||
``` | |||||
Authors | |||||
------- | |||||
Original state and module based on the work from [Yigal Duppen](https://github.com/publysher/infra-example-nginx/tree/develop). | |||||
Salt formula developed by Mario del Pozo. | |||||
""" | |||||
Execution module for UFW. | |||||
""" | |||||
def is_enabled(): | |||||
cmd = 'ufw status | grep "Status: active"' | |||||
out = __salt__['cmd.run'](cmd) | |||||
return True if out else False | |||||
def set_enabled(enabled): | |||||
cmd = 'ufw --force enable' if enabled else 'ufw disable' | |||||
__salt__['cmd.run'](cmd) | |||||
def add_rule(rule): | |||||
cmd = "ufw " + rule | |||||
out = __salt__['cmd.run'](cmd) | |||||
__salt__['cmd.run']("ufw reload") | |||||
return out |
from salt.exceptions import CommandExecutionError, CommandNotFoundError | |||||
import re | |||||
import socket | |||||
def _unchanged(name, msg): | |||||
return {'name': name, 'result': True, 'comment': msg, 'changes': {}} | |||||
def _test(name, msg): | |||||
return {'name': name, 'result': None, 'comment': msg, 'changes': {}} | |||||
def _error(name, msg): | |||||
return {'name': name, 'result': False, 'comment': msg, 'changes': {}} | |||||
def _changed(name, msg, **changes): | |||||
return {'name': name, 'result': True, 'comment': msg, 'changes': changes} | |||||
def _resolve(host): | |||||
# pure IP address / netmask IPv4 or IPv6 ? | |||||
if re.match(r'^([0-9\.](::))+(/[0-9]+)?$', host): | |||||
return | |||||
return socket.gethostbyname(host) | |||||
def _as_rule(method, app, protocol, from_addr, from_port, to_addr, to_port): | |||||
cmd = [method] | |||||
if app is not None: | |||||
cmd.append(app) | |||||
else: | |||||
if protocol is not None: | |||||
cmd.append("proto") | |||||
cmd.append(protocol) | |||||
cmd.append("from") | |||||
if from_addr is not None: | |||||
cmd.append(_resolve(from_addr)) | |||||
else: | |||||
cmd.append("any") | |||||
if from_port is not None: | |||||
cmd.append("port") | |||||
cmd.append(_resolve(from_port)) | |||||
cmd.append("to") | |||||
if to_addr is not None: | |||||
cmd.append(to_addr) | |||||
else: | |||||
cmd.append("any") | |||||
if to_port is not None: | |||||
cmd.append("port") | |||||
cmd.append(to_port) | |||||
real_cmd = ' '.join(cmd) | |||||
return real_cmd | |||||
def enabled(name, **kwargs): | |||||
if __salt__['ufw.is_enabled'](): | |||||
return _unchanged(name, "UFW is already enabled") | |||||
if __opts__['test']: | |||||
return _test(name, "UFW will be enabled") | |||||
try: | |||||
__salt__['ufw.set_enabled'](True) | |||||
except (CommandExecutionError, CommandNotFoundError) as e: | |||||
return _error(name, e.message) | |||||
return _changed(name, "UFW is enabled", enabled=True) | |||||
def allowed(name, app=None, protocol=None, | |||||
from_addr=None, from_port=None, to_addr=None, to_port=None): | |||||
rule = _as_rule("allow", app=app, protocol=protocol, | |||||
from_addr=from_addr, from_port=from_port, to_addr=to_addr, to_port=to_port) | |||||
if __opts__['test']: | |||||
return _test(name, "{0}: {1}".format(name, rule)) | |||||
try: | |||||
out = __salt__['ufw.add_rule'](rule) | |||||
except (CommandExecutionError, CommandNotFoundError) as e: | |||||
return _error(name, e.message) | |||||
changes = False | |||||
for line in out.split('\n'): | |||||
if line.startswith("Skipping"): | |||||
continue | |||||
if line.startswith("Rule added") or line.startswith("Rules updated"): | |||||
changes = True | |||||
break | |||||
return _error(name, line) | |||||
if changes: | |||||
return _changed(name, "{0} allowed".format(name), rule=rule) | |||||
else: | |||||
return _unchanged(name, "{0} was already allowed".format(name)) | |||||
ufw: | |||||
enabled: True | |||||
services: | |||||
# Allow 80/tcp (http) traffic from only two remote addresses. | |||||
http: | |||||
protocol: tcp | |||||
from_addr: | |||||
- 10.0.2.15 | |||||
- 10.0.2.16 | |||||
# Allow 443/tcp (https) traffic from network 10.0.0.0/8 to an specific local ip. | |||||
https: | |||||
protocol: tcp | |||||
from_addr: | |||||
- 10.0.0.0/8 | |||||
to_addr: 10.0.2.1 | |||||
# Allow from a service port. | |||||
smtp: | |||||
protocol: tcp | |||||
# Allow from an specific port, by number. | |||||
139: | |||||
protocol: tcp | |||||
# Allow from a range of ports, udp. | |||||
"10000:20000": | |||||
protocol: udp | |||||
# Allow from two specific ports, udp. | |||||
"30000,40000": | |||||
protocol: udp | |||||
# Allow an application defined at /etc/ufw/applications.d/ | |||||
applications: | |||||
- OpenSSH |
# UFW management module | |||||
{%- set ufw = pillar.get('ufw', {}) %} | |||||
{%- if ufw.get('enabled', False) %} | |||||
ufw: | |||||
pkg: | |||||
- installed | |||||
service.running: | |||||
- enable: True | |||||
ufw: | |||||
- enabled | |||||
- require: | |||||
- pkg: ufw | |||||
{%- for service_name, service_details in ufw.get('services', {}).items() %} | |||||
{%- for from_addr in service_details.get('from_addr', [None]) %} | |||||
{%- set protocol = service_details.get('protocol', None) %} | |||||
{%- set from_port = service_details.get('from_port', None) %} | |||||
{%- set to_addr = service_details.get('to_addr', None) %} | |||||
ufw-svc-{{service_name}}-{{from_addr}}: | |||||
ufw.allowed: | |||||
- protocol: {{protocol}} | |||||
{%- if from_addr != None %} | |||||
- from_addr: {{from_addr}} | |||||
{%- endif %} | |||||
{%- if from_port != None %} | |||||
- from_port: "{{from_port}}" | |||||
{%- endif %} | |||||
{%- if to_addr != None %} | |||||
- to_addr: {{to_addr}} | |||||
{%- endif %} | |||||
- to_port: "{{service_name}}" | |||||
- require: | |||||
- pkg: ufw | |||||
{%- endfor %} | |||||
{%- endfor %} | |||||
# Applications | |||||
{%- for app_name in ufw.get('applications', []) %} | |||||
ufw-app-{{app_name}}: | |||||
ufw.allowed: | |||||
- app: {{app_name}} | |||||
- require: | |||||
- pkg: ufw | |||||
{%- endfor %} | |||||
{% else %} | |||||
#ufw: | |||||
#ufw: | |||||
#- disabled | |||||
{% endif %} |