@@ -0,0 +1,86 @@ | |||
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. | |||
@@ -0,0 +1,19 @@ | |||
""" | |||
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 |
@@ -0,0 +1,104 @@ | |||
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)) | |||
@@ -0,0 +1,39 @@ | |||
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 |
@@ -0,0 +1,58 @@ | |||
# 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 %} |