Browse Source

Adds ability to limit or deny services and applications.

The "limit" and "deny" parameters have been added to both the services and
applications sections. Setting "limit: True" will use the "ufw limit" command
instead of "ufw allow". Likewise, setting "deny: True" will use the "ufw deny"
command.
tags/v0.2.0
Rob Ruana 6 years ago
parent
commit
db977aaf6d
5 changed files with 182 additions and 42 deletions
  1. +26
    -1
      .kitchen.yml
  2. +61
    -30
      _states/ufw.py
  3. +35
    -2
      pillar.example
  4. +40
    -0
      test/integration/ufw/controls/ufw.rb
  5. +20
    -9
      ufw/init.sls

+ 26
- 1
.kitchen.yml View File

@@ -37,10 +37,35 @@ provisioner:
enabled: True
settings:
loglevel: 'low'
applications:
MySQL:
comment: Allow MySQL
Postgresql:
limit: True
comment: Limit Postgresql
SSH223:
deny: True
comment: Deny Webscale SSH
'*':
deny: True
from_addr: 10.0.0.0/8
services:
'*':
deny: True
from_addr:
- 10.0.0.1
- 10.0.0.2
'22':
protocol: tcp
comment: Allow SSH
limit: True
comment: Limit SSH
'80':
protocol: tcp
deny: True
comment: Deny HTTP
'443':
protocol: tcp
comment: Allow HTTPS

platforms:
- name: <%= distrib %>-<%= codename %>

+ 61
- 30
_states/ufw.py View File

@@ -80,6 +80,47 @@ def _as_rule(method, app, interface, protocol, from_addr, from_port, to_addr, to
return real_cmd


def _add_rule(method, name, app=None, interface=None, protocol=None,
from_addr=None, from_port=None, to_addr=None, to_port=None, comment=None):

if app and app.strip('"\' ') == '*':
app = None
if to_port and to_port.strip('"\' ') == '*':
to_port = None

rule = _as_rule(method, app=app, interface=interface, protocol=protocol,
from_addr=from_addr, from_port=from_port, to_addr=to_addr, to_port=to_port, comment=comment)

try:
out = __salt__['ufw.add_rule'](rule)
except (CommandExecutionError, CommandNotFoundError) as e:
return _error(name, e.message)

adds = False
updates = False
for line in out.split('\n'):
if re.match('^Skipping', line):
return _unchanged(name, "{0} is already configured".format(name))
break
if re.match('^Rule(s)? added', line):
adds = True
break
if re.match('^Rule(s)? updated', line):
updates = True
break
if __opts__['test']:
return _test(name, "{0} would have been configured".format(name))
break
return _error(name, line)

if adds:
return _changed(name, "{0} added".format(name), rule=rule)
elif updates:
return _changed(name, "{0} updated".format(name), rule=rule)
else:
return _unchanged(name, "{0} was already configured".format(name))


def enabled(name, **kwargs):
if __salt__['ufw.is_enabled']():
return _unchanged(name, "UFW is already enabled")
@@ -137,38 +178,28 @@ def default_outgoing(name, default):
return _unchanged(name, "{0} was already set to {1}".format(name, default))


def allowed(name, app=None, interface=None, protocol=None,
from_addr=None, from_port=None, to_addr=None, to_port=None, comment=None):
def deny(name, app=None, interface=None, protocol=None,
from_addr=None, from_port=None, to_addr=None, to_port=None, comment=None):

rule = _as_rule("allow", app=app, interface=interface, protocol=protocol,
from_addr=from_addr, from_port=from_port, to_addr=to_addr, to_port=to_port, comment=comment)
return _add_rule('deny', name, app, interface, protocol, from_addr, from_port, to_addr, to_port, comment)

try:
out = __salt__['ufw.add_rule'](rule)
except (CommandExecutionError, CommandNotFoundError) as e:
return _error(name, e.message)

adds = False
updates = False
for line in out.split('\n'):
if re.match('^Skipping', line):
return _unchanged(name, "{0} is already configured".format(name))
break
if re.match('^Rule(s)? added', line):
adds = True
break
if re.match('^Rule(s)? updated', line):
updates = True
break
if __opts__['test']:
return _test(name, "{0} would have been configured".format(name))
break
return _error(name, line)
def limit(name, app=None, interface=None, protocol=None,
from_addr=None, from_port=None, to_addr=None, to_port=None, comment=None):

if adds:
return _changed(name, "{0} added".format(name), rule=rule)
elif updates:
return _changed(name, "{0} updated".format(name), rule=rule)
else:
return _unchanged(name, "{0} was already configured".format(name))
return _add_rule('limit', name, app, interface, protocol, from_addr, from_port, to_addr, to_port, comment)


def allow(name, app=None, interface=None, protocol=None,
from_addr=None, from_port=None, to_addr=None, to_port=None, comment=None):

return _add_rule('allow', name, app, interface, protocol, from_addr, from_port, to_addr, to_port, comment)


def allowed(name, app=None, interface=None, protocol=None,
from_addr=None, from_port=None, to_addr=None, to_port=None, comment=None):

"""
allow() is aliased to allowed() to maintain backwards compatibility.
"""
return allow(name, app, interface, protocol, from_addr, from_port, to_addr, to_port, comment)

+ 35
- 2
pillar.example View File

@@ -53,11 +53,36 @@ ufw:
protocol: tcp
comment: Mail relay

# Allow from an specific port, by number.
# Allow from a specific port, by number.
139:
protocol: tcp
comment: Netbios

# Deny from a specific port, by number.
140:
protocol: tcp
deny: True

# Deny everything from a specific ip address
'*':
protocol: tcp
deny: True
from_addr: 10.0.0.1

# Deny everything from a multiple ip addresses
'*':
protocol: tcp
deny: True
from_addr:
- 10.0.0.2
- 10.0.0.3

# Limit a specific port, by number.
170:
limit: True
protocol: tcp
comment: Print service

# Allow from a range of ports, udp.
"10000:20000":
protocol: udp
@@ -68,12 +93,20 @@ ufw:
protocol: udp
comment: Game server and admin

# Allow an application defined at /etc/ufw/applications.d/
# Allow applications defined at /etc/ufw/applications.d/
applications:
OpenSSH:
enabled: True
comment: We are using fail2ban anyway

# Limit access to salt master
Saltmaster:
limit: True

# Deny access to Postgresql
Postgresql:
deny: True

# Allow all traffic in on the specified interface
interfaces:
eth1:

+ 40
- 0
test/integration/ufw/controls/ufw.rb View File

@@ -25,7 +25,47 @@ describe command('ufw status verbose | grep Logging') do
its('stdout') { should match /low/ }
end

describe command('ufw status | grep MySQL') do
its('exit_status') { should eq 0 }
its('stdout') { should match /ALLOW/ }
end

describe command('ufw status | grep Postgresql') do
its('exit_status') { should eq 0 }
its('stdout') { should match /LIMIT/ }
end

describe command('ufw status | grep SSH223') do
its('exit_status') { should eq 0 }
its('stdout') { should match /DENY/ }
end

describe command('ufw status | grep 10.0.0.0') do
its('exit_status') { should eq 0 }
its('stdout') { should match /DENY/ }
end

describe command('ufw status | grep 22/tcp') do
its('exit_status') { should eq 0 }
its('stdout') { should match /LIMIT/ }
end

describe command('ufw status | grep 80/tcp') do
its('exit_status') { should eq 0 }
its('stdout') { should match /DENY/ }
end

describe command('ufw status | grep 443/tcp') do
its('exit_status') { should eq 0 }
its('stdout') { should match /ALLOW/ }
end

describe command('ufw status | grep 10.0.0.1') do
its('exit_status') { should eq 0 }
its('stdout') { should match /DENY/ }
end

describe command('ufw status | grep 10.0.0.2') do
its('exit_status') { should eq 0 }
its('stdout') { should match /DENY/ }
end

+ 20
- 9
ufw/init.sls View File

@@ -43,15 +43,20 @@ ufw:
# services
{%- for service_name, service_details in ufw.get('services', {}).items() %}

{%- for from_addr in service_details.get('from_addr', [None]) %}
{%- set from_addr_raw = service_details.get('from_addr', [None]) -%}
{%- set from_addrs = [from_addr_raw] if from_addr_raw is string else from_addr_raw -%}

{%- for from_addr in from_addrs %}
{%- set protocol = service_details.get('protocol', None) %}
{%- set deny = service_details.get('deny', None) %}
{%- set limit = service_details.get('limit', None) %}
{%- set method = 'deny' if deny else ('limit' if limit else 'allow') -%}
{%- set from_port = service_details.get('from_port', None) %}
{%- set to_addr = service_details.get('to_addr', None) %}
{%- set comment = service_details.get('comment', None) %}

ufw-svc-{{service_name}}-{{from_addr}}:
ufw.allowed:
ufw-svc-{{method}}-{{service_name}}-{{from_addr}}:
ufw.{{method}}:
{%- if protocol != None %}
- protocol: {{protocol}}
{%- endif %}
@@ -79,17 +84,23 @@ ufw-svc-{{service_name}}-{{from_addr}}:

# Applications
{%- for app_name, app_details in ufw.get('applications', {}).items() %}
{%- for from_addr in app_details.get('from_addr', [None]) %}

{%- set from_addr_raw = app_details.get('from_addr', [None]) -%}
{%- set from_addrs = [from_addr_raw] if from_addr_raw is string else from_addr_raw -%}

{%- for from_addr in from_addrs %}
{%- set deny = app_details.get('deny', None) %}
{%- set limit = app_details.get('limit', None) %}
{%- set method = 'deny' if deny else ('limit' if limit else 'allow') -%}
{%- set to_addr = app_details.get('to_addr', None) %}
{%- set comment = app_details.get('comment', None) %}

{%- if from_addr != None%}
ufw-app-{{app_name}}-{{from_addr}}:
ufw-app-{{method}}-{{app_name}}-{{from_addr}}:
{%- else %}
ufw-app-{{app_name}}:
ufw-app-{{method}}-{{app_name}}:
{%- endif %}
ufw.allowed:
ufw.{{method}}:
- app: '"{{app_name}}"'
{%- if from_addr != None %}
- from_addr: {{from_addr}}
@@ -107,7 +118,7 @@ ufw-app-{{app_name}}:

{%- endfor %}
{%- endfor %}
# Interfaces
{%- for interface_name, interface_details in ufw.get('interfaces', {}).items() %}
{%- set comment = interface_details.get('comment', None) %}

Loading…
Cancel
Save