chef/cookbooks/firewall/libraries/provider_firewall_rule_ufw.rb
2015-07-21 19:45:23 +02:00

242 lines
6.5 KiB
Ruby

#
# Author:: Seth Chisamore (<schisamo@opscode.com>)
# Cookbook Name:: firwall
# Resource:: rule
#
# Copyright:: 2011, Opscode, Inc.
#
# 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.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'poise'
class Chef
class Provider::FirewallRuleUfw < Provider
include Poise
include Chef::Mixin::ShellOut
include FirewallCookbook::Helpers
def action_allow
if rule_exists?
Chef::Log.info("#{new_resource.name} already allowed, skipping")
else
apply_rule(:allow)
end
end
def action_deny
if rule_exists?
Chef::Log.info("#{new_resource.name} already denied, skipping")
else
apply_rule(:deny)
end
end
def action_reject
if rule_exists?
Chef::Log.info("#{new_resource.name} already rejected, skipping")
else
apply_rule(:reject)
end
end
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
if new_resource.stateful
msg = ''
msg << "firewall_rule[#{new_resource.name}] was asked to "
msg << "#{type} a stateful rule using #{new_resource.stateful} "
msg << 'but ufw does not support this kind of rule. Consider guarding by platform_family.'
fail msg
end
# some examples:
# ufw allow from 192.168.0.4 to any port 22
# ufw deny proto tcp from 10.0.0.0/8 to 192.168.0.1 port 25
# ufw insert 1 allow proto tcp from 0.0.0.0/0 to 192.168.0.1 port 25
ufw_command = ['ufw']
if new_resource.position
ufw_command << 'insert'
ufw_command << new_resource.position.to_s
end
ufw_command << type.to_s
ufw_command << rule.split
converge_by("firewall_rule[#{new_resource.name}] #{rule}") do
notifying_block do
# fail 'should be no actions'
shell_out!(*ufw_command.flatten)
shell_out!('ufw', 'status', 'verbose') # purely for the Chef::Log.debug output
new_resource.updated_by_last_action(true)
end
end
end
def rule
rule = ''
rule << rule_interface
rule << rule_logging
rule << rule_proto
rule << rule_dest_port
rule << rule_source_port
rule.strip
end
def rule_interface
rule = ''
rule << "#{new_resource.direction} " if new_resource.direction
if new_resource.interface
if new_resource.direction
rule << "on #{new_resource.interface} "
else
rule << "in on #{new_resource.interface} "
end
end
rule
end
def rule_proto
rule = ''
rule << "proto #{new_resource.protocol} " if new_resource.protocol
rule
end
def rule_dest_port
rule = ''
if new_resource.destination
rule << "to #{new_resource.destination} "
else
rule << 'to any '
end
rule << "port #{port_to_s(dport_calc)} " if dport_calc
rule
end
def rule_source_port
rule = ''
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
def rule_logging
case new_resource.logging && new_resource.logging.to_sym
when :connections
'log '
when :packets
'log-all '
else
''
end
end
# 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
# 192.168.0.1 25/tcp DENY 10.0.0.0/8
# 22 ALLOW Anywhere
# 3309 on eth9 ALLOW Anywhere
# Anywhere ALLOW Anywhere
# 80 ALLOW Anywhere (log)
# 8080 DENY 192.168.1.0/24
# 1.2.3.5 5469/udp ALLOW 1.2.3.4 5469/udp
# 3308 ALLOW OUT Anywhere on eth8
to = rule_exists_to? # col 1
action = rule_exists_action? # col 2
from = rule_exists_from? # col 3
# full regex from columns
regex = rule_exists_regex?(to, action, from)
match = shell_out!('ufw', 'status').stdout.lines.find do |line|
# TODO: support IPv6
return false if line =~ /\(v6\)$/
line =~ regex
end
match
end
def rule_exists_to?
to = ''
to << rule_exists_dest?
proto = rule_exists_proto?
to << proto if proto
if to.empty?
to << "Anywhere\s"
else
to
end
end
def rule_exists_action?
action = new_resource.action
action = action.first if action.is_a?(Enumerable)
"#{Regexp.escape(action.to_s.upcase)}\s"
end
def rule_exists_from?
if new_resource.source && new_resource.source != '0.0.0.0/0'
Regexp.escape(new_resource.source)
elsif new_resource.source
Regexp.escape('Anywhere')
end
end
def rule_exists_dest?
if new_resource.destination
"#{Regexp.escape(new_resource.destination)}\s"
else
''
end
end
def rule_exists_regex?(to, action, from)
if to && new_resource.direction && new_resource.direction.to_sym == :out
/^#{to}.*#{action}OUT\s.*#{from}$/
elsif to
/^#{to}.*#{action}.*#{from}$/
end
end
def rule_exists_proto?
if new_resource.protocol && dport_calc
"#{Regexp.escape(port_to_s(dport_calc))}/#{Regexp.escape(new_resource.protocol)}\s "
elsif dport_calc
"#{Regexp.escape(port_to_s(dport_calc))}\s "
end
end
def dport_calc
new_resource.dest_port || new_resource.port
end
end
end