Skip to content

Commit

Permalink
Clean up ufw implementation
Browse files Browse the repository at this point in the history
- Merge from https://github.com/paulczar/firewall into ufw_defaults. Fixes sous-chefs#14.
- Clean up ufw implementation & ports syntax to be more obvious
  • Loading branch information
martinb3 committed Mar 17, 2015
2 parents 6836568 + 0beb84a commit f15b20d
Show file tree
Hide file tree
Showing 18 changed files with 192 additions and 105 deletions.
3 changes: 3 additions & 0 deletions .kitchen.yml
Expand Up @@ -14,3 +14,6 @@ suites:
- recipe[apt]
- recipe[firewall::default]
- recipe[firewall-test::default]
attributes:
firewall:
allow_ssh: true
31 changes: 21 additions & 10 deletions README.md
Expand Up @@ -12,34 +12,45 @@ Requirements
### Platform
* Ubuntu
* Debian
* Redhat
* CentOS

Tested on:
* Ubuntu 10.04
* Ubuntu 11.04
* Ubuntu 11.10
* Debian 7.0
* Ubuntu 12.04
* Ubuntu 14.04
* Debian 7.8
* CentOS 6.5


Recipes
-------
### default
The default recipe installs the `ufw` package, which this cookbook requires. Make sure that the firewall recipe is in the node or role run_list before any resources from this cookbook is used.
The default recipe creates a firewall resource with action install, and if `node['firewall']['allow_ssh']`, opens port 22 from the world.


Attributes
----------

* `default['firewall']['ufw']['defaults']` hash for template `/etc/default/ufw`

Resources/Providers
-------------------
### firewall
#### Actions
- :enable: *Default action* enable the firewall. this will make any rules that have been defined 'active'.
- :disable: disable the firewall. drop any rules and put the node in an unprotected state.
- :flush: Runs `iptables -F`. Only supported by the iptables firewall provider.

#### Attribute Parameters
- name: name attribute. arbitrary name to uniquely identify this resource
- log_level: level of verbosity the firewall should log at. valid values are: :low, :medium, :high, :full. default is :low.

#### Providers
- `Chef::Provider::FirewallUfw`
- platform default: Ubuntu
- platform_family default: debian

- `Chef::Provider::FirewallIptables`
- platform_family default: rhel

#### Examples

Expand All @@ -66,7 +77,7 @@ end
#### Attribute Parameters
- name: name attribute. arbitrary name to uniquely identify this firewall rule
- protocol: valid values are: :udp, :tcp. default is all protocols
- port: incoming port number (ie. 22 to allow inbound SSH), or an array of incoming port numbers (ie. [80,443] to allow inbound HTTP & HTTPS). NOTE: `protocol` attribute is required with `ports`, or a range of incoming port numbers (ie. 60000..61000 to allow inbound mobile-shell. NOTE: `protocol`, or an attribute is required with `port_range`
- port: incoming port number (ie. 22 to allow inbound SSH), or an array of incoming port numbers (ie. [80,443] to allow inbound HTTP & HTTPS). NOTE: `protocol` attribute is required with multiple ports, or a range of incoming port numbers (ie. 60000..61000 to allow inbound mobile-shell. NOTE: `protocol`, or an attribute is required with a range of ports.
- source: ip address or subnet to filter on incoming traffic. default is `0.0.0.0/0` (ie Anywhere)
- destination: ip address or subnet to filter on outgoing traffic.
- dest_port: outgoing port number.
Expand Down Expand Up @@ -111,15 +122,15 @@ end
# that the protocol attribute is required when using port_range
firewall_rule 'mosh' do
protocol :udp
port_range 60000..61000
port 60000..61000
action :allow
end

# open multiple ports for http/https, note that the protocol
# attribute is required when using ports
firewall_rule 'http/https' do
protocol :tcp
ports [80, 443]
port [80, 443]
action :allow
end

Expand Down Expand Up @@ -165,7 +176,7 @@ License & Authors
- Author:: Seth Chisamore (<schisamo@opscode.com>)

```text
Copyright:: Copyright (c) 2011 Opscode, Inc.
Copyright:: Copyright (c) 2011-2015 Opscode, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
1 change: 1 addition & 0 deletions attributes/default.rb
@@ -0,0 +1 @@
default['firewall']['allow_ssh'] = false
12 changes: 12 additions & 0 deletions attributes/ufw.rb
@@ -0,0 +1,12 @@
default['firewall']['ufw']['defaults'] = {
ipv6: 'yes',
manage_builtins: 'no',
ipt_sysctl: '/etc/ufw/sysctl.conf',
ipt_modules: 'nf_conntrack_ftp nf_nat_ftp nf_conntrack_netbios_ns',
policy: {
input: 'DROP',
output: 'ACCEPT',
forward: 'DROP',
application: 'SKIP'
}
}
13 changes: 13 additions & 0 deletions libraries/helpers.rb
@@ -0,0 +1,13 @@
module FirewallCookbook
module Helpers
def port_to_s(p)
if p && p.is_a?(Integer)
p.to_s
elsif p && p.is_a?(Array)
p.join(',')
elsif p && p.is_a?(Range)
"#{p.first}:#{p.last} "
end
end
end
end
31 changes: 19 additions & 12 deletions libraries/provider_firewall_iptables.rb
Expand Up @@ -21,20 +21,27 @@ class Chef
class Provider::FirewallIptables < Provider
include Poise
include Chef::Mixin::ShellOut
provides :firewall, os: "linux", platform_family: [ "rhel" ]

def action_enable
# prints all the firewall rules
# pp @new_resource.subresources
log_current_iptables
if active?
Chef::Log.debug("#{@new_resource} already enabled.")
else
Chef::Log.debug("#{@new_resource} is about to be enabled")
shell_out!("echo iptables -P INPUT DROP")
shell_out!("echo iptables -P OUTPUT DROP")
shell_out!("echo iptables -P FORWARD DROP")
Chef::Log.info("#{@new_resource} enabled.")
new_resource.updated_by_last_action(true)
converge_by('install package iptables and default DROP all rules') do
package 'iptables' do
action :install
end

# prints all the firewall rules
# pp @new_resource.subresources
log_current_iptables
if active?
Chef::Log.debug("#{@new_resource} already enabled.")
else
Chef::Log.debug("#{@new_resource} is about to be enabled")
shell_out!("echo iptables -P INPUT DROP")
shell_out!("echo iptables -P OUTPUT DROP")
shell_out!("echo iptables -P FORWARD DROP")
Chef::Log.info("#{@new_resource} enabled.")
new_resource.updated_by_last_action(true)
end
end
end

Expand Down
13 changes: 9 additions & 4 deletions libraries/provider_firewall_rule_iptables.rb
Expand Up @@ -21,6 +21,8 @@ class Chef
class Provider::FirewallRuleIptables < Provider
include Poise
include Chef::Mixin::ShellOut
include FirewallCookbook::Helpers
provides :firewall_rule, os: "linux", platform_family: [ "rhel" ]

def action_allow
converge_by("Allowing #{@new_resource.name}") do
Expand Down Expand Up @@ -66,17 +68,17 @@ def apply_rule(type=nil)

firewall_rule = ""
if @new_resource.direction
firewall_rule << "#{CHAIN[@new_resource.direction]} "
firewall_rule << "#{CHAIN[@new_resource.direction.to_sym]} "
else
firewall_rule << "FORWARD "
end
if [:pre, :post].include?(@new_resource.direction)
if [:pre, :post].include?(@new_resource.direction.to_sym)
firewall_rule << '-t nat '
end
firewall_rule << "-s #{@new_resource.source} " if @new_resource.source
firewall_rule << "-p #{@new_resource.protocol} -m tcp " if @new_resource.protocol
firewall_rule << "--sport #{@new_resource.port} " if @new_resource.port
firewall_rule << "--dport #{@new_resource.dest_port} " if @new_resource.dest_port
firewall_rule << "--sport #{port_to_s(@new_resource.source_port)} " if @new_resource.source_port
firewall_rule << "--dport #{dport_calc} " if dport_calc
firewall_rule << "-i #{@new_resource.interface} " if @new_resource.interface
firewall_rule << "-o #{@new_resource.dest_interface} " if @new_resource.dest_interface
firewall_rule << "-d #{@new_resource.destination} " if @new_resource.destination
Expand Down Expand Up @@ -124,5 +126,8 @@ def rule_exists?(rule)
false
end

def dport_calc
new_resource.dest_port || new_resource.port
end
end
end
64 changes: 40 additions & 24 deletions libraries/provider_firewall_rule_ufw.rb
Expand Up @@ -21,11 +21,13 @@ class Chef
class Provider::FirewallRuleUfw < Provider
include Poise
include Chef::Mixin::ShellOut
include FirewallCookbook::Helpers
provides :firewall_rule, os: "linux", platform_family: [ "debian" ]

def action_allow
Chef::Log.info("#{new_resource.name} action_allow")
if rule_exists?
Chef::Log.debug "Rule #{rule} already allowed - skipping"
Chef::Log.debug("Rule #{rule} already allowed - skipping")
else
converge_by("Allowing #{rule}") do
apply_rule('allow')
Expand All @@ -34,8 +36,9 @@ def action_allow
end

def action_deny
Chef::Log.info("#{new_resource.name} action_deny")
if rule_exists?
Chef::Log.debug "Rule #{rule} already denied - skipping"
Chef::Log.debug("Rule #{rule} already denied - skipping")
else
converge_by("Denying #{rule}") do
apply_rule('deny')
Expand All @@ -44,8 +47,9 @@ def action_deny
end

def action_reject
Chef::Log.info("#{new_resource.name} action_reject")
if rule_exists?
Chef::Log.debug "Rule #{rule} already rejected - skipping"
Chef::Log.debug("Rule #{rule} already rejected - skipping")
else
converge_by("Rejecting #{rule}") do
apply_rule('reject')
Expand All @@ -56,6 +60,9 @@ def action_reject
private

def apply_rule(type = nil) # rubocop:disable MethodLength
Chef::Log.info("#{new_resource.name} apply_rule #{type}")
# if we don't do this, we may see some bugs where traffic is opened on all ports to all hosts when only RELATED,ESTABLISHED was intended
fail "firewall_rule[#{new_resource.name}] was asked to #{type} a stateful rule using #{new_resource.stateful} but ufw does not support this kind of rule. Consider guarding by platform_family." if new_resource.stateful

# some examples:
# ufw allow from 192.168.0.4 to any port 22
Expand All @@ -81,7 +88,7 @@ def rule
rule << rule_logging
rule << rule_proto
rule << rule_dest_port
rule << rule_port
rule << rule_source_port
rule.strip
end

Expand All @@ -101,33 +108,36 @@ def rule_interface
def rule_proto
rule = ''
rule << "proto #{new_resource.protocol.to_s} " if new_resource.protocol
if new_resource.source
rule << "from #{new_resource.source} "
else
rule << 'from any '
end
rule
end

def rule_dest_port
rule = ''
rule << "port #{new_resource.dest_port} " if new_resource.dest_port

if new_resource.destination
rule << "to #{new_resource.destination} "
else
rule << 'to any '
end

if dport_calc
rule << "port #{port_to_s(dport_calc)} "
end

rule
end

def rule_port
def rule_source_port
rule = ''
if new_resource.port && new_resource.port.is_a?(Integer)
rule << "port #{new_resource.port} "
elsif new_resource.port && new_resource.port.is_a?(Array)
rule << "port #{new_resource.port.join(',')} "
elsif new_resource.port && new_resource.port.is_a?(Range)
rule << "port #{new_resource.port.first}:#{new_resource.port.last} "

if new_resource.source
rule << "from #{new_resource.source} "
else
rule << 'from any '
end

if new_resource.source_port
rule << "port #{port_to_s(new_resource.source_port)} "
end
rule
end
Expand All @@ -145,6 +155,7 @@ def rule_logging

# TODO: currently only works when firewall is enabled
def rule_exists?
Chef::Log.info("#{new_resource.name} rule_exists?")
# To Action From
# -- ------ ----
# 22 ALLOW Anywhere
Expand Down Expand Up @@ -175,7 +186,9 @@ def rule_exists?
def rule_exists_to?
to = ''
to << rule_exists_dest?
to << rule_exists_proto?

proto = rule_exists_proto?
to << proto if proto

if to.empty?
to << "Anywhere\s"
Expand Down Expand Up @@ -203,20 +216,23 @@ def rule_exists_dest?
end

def rule_exists_regex?(to, action, from)
if new_resource.direction && new_resource.direction.to_sym == :out
if to && new_resource.direction && new_resource.direction.to_sym == :out
/^#{to}.*#{action}OUT\s.*#{from}$/
else
elsif to
/^#{to}.*#{action}.*#{from}$/
end
end

def rule_exists_proto?
if new_resource.protocol && new_resource.port
"#{Regexp.escape(new_resource.port)}/#{Regexp.escape(new_resource.protocol)}\s"
elsif new_resource.port
"#{Regexp.escape("#{new_resource.port}")}\s"
if new_resource.protocol && dport_calc
"port #{Regexp.escape(port_to_s(dport_calc))}/#{Regexp.escape(new_resource.protocol)}\s "
elsif dport_calc
"port #{Regexp.escape("#{port_to_s(dport_calc)}")}\s "
end
end

def dport_calc
new_resource.dest_port || new_resource.port
end
end
end

0 comments on commit f15b20d

Please sign in to comment.