Update cookbooks and add wordpress cookbook

This commit is contained in:
Greg Karékinian
2016-02-19 18:09:49 +01:00
parent 9ba973e3ac
commit 820b0ab3f8
606 changed files with 22421 additions and 14084 deletions

View File

@@ -1,13 +1,91 @@
module FirewallCookbook
module Helpers
def dport_calc(new_resource)
new_resource.dest_port || new_resource.port
end
def port_to_s(p)
if p && p.is_a?(Integer)
if p.is_a?(String)
p
elsif p && p.is_a?(Integer)
p.to_s
elsif p && p.is_a?(Array)
p.join(',')
p.map! { |o| port_to_s(o) }
p.sort.join(',')
elsif p && p.is_a?(Range)
"#{p.first}:#{p.last} "
if platform_family?('windows')
"#{p.first}-#{p.last}"
else
"#{p.first}:#{p.last}"
end
end
end
def ipv6_enabled?(new_resource)
new_resource.ipv6_enabled
end
def disabled?(new_resource)
# if either flag is found in the non-default boolean state
disable_flag = !(new_resource.enabled && !new_resource.disabled)
Chef::Log.warn("#{new_resource} has been disabled, not proceeding") if disable_flag
disable_flag
end
def ip_with_mask(new_resource, ip)
if ip.include?('/')
ip
elsif ipv4_rule?(new_resource)
"#{ip}/32"
elsif ipv6_rule?(new_resource)
"#{ip}/128"
else
ip
end
end
# ipv4-specific rule?
def ipv4_rule?(new_resource)
if (new_resource.source && IPAddr.new(new_resource.source).ipv4?) ||
(new_resource.destination && IPAddr.new(new_resource.destination).ipv4?)
true
else
false
end
end
# ipv6-specific rule?
def ipv6_rule?(new_resource)
if (new_resource.source && IPAddr.new(new_resource.source).ipv6?) ||
(new_resource.destination && IPAddr.new(new_resource.destination).ipv6?) ||
new_resource.protocol =~ /ipv6/ ||
new_resource.protocol =~ /icmpv6/
true
else
false
end
end
def ubuntu?(current_node)
current_node['platform'] == 'ubuntu'
end
def build_rule_file(rules)
contents = []
sorted_values = rules.values.sort.uniq
sorted_values.each do |sorted_value|
contents << "# position #{sorted_value}"
rules.each do |k, v|
next unless v == sorted_value
contents << if k.start_with?('COMMIT')
'COMMIT'
else
k
end
end
end
"#{contents.join("\n")}\n"
end
end
end

View File

@@ -0,0 +1,106 @@
module FirewallCookbook
module Helpers
module Firewalld
include FirewallCookbook::Helpers
include Chef::Mixin::ShellOut
def firewalld_rules_filename
'/etc/sysconfig/firewalld-chef.rules'
end
def firewalld_rule!(cmd)
shell_out!(cmd, input: 'yes')
end
def firewalld_active?
cmd = shell_out('firewall-cmd', '--state')
cmd.stdout =~ /^running$/
end
def firewalld_default_zone?(z)
cmd = shell_out('firewall-cmd', '--get-default-zone')
cmd.stdout =~ /^#{z.to_s}$/
end
def firewalld_default_zone!(z)
shell_out!('firewall-cmd', "--set-default-zone=#{z}")
end
def log_current_firewalld
shell_out!('firewall-cmd --direct --get-all-rules')
end
def firewalld_flush!
shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
end
def firewalld_all_rules_permanent!
rules = shell_out!('firewall-cmd', '--direct', '--get-all-rules').stdout
perm_rules = shell_out!('firewall-cmd', '--direct', '--permanent', '--get-all-rules').stdout
rules == perm_rules
end
def firewalld_save!
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
shell_out!('firewall-cmd', '--direct', '--get-all-rules').stdout.lines do |line|
shell_out!("firewall-cmd --direct --permanent --add-rule #{line}")
end
end
def ip_versions(resource)
if ipv4_rule?(resource)
%w(ipv4)
elsif ipv6_rule?(resource)
%w(ipv6)
else # no source or destination address, add rules for both ipv4 and ipv6
%w(ipv4 ipv6)
end
end
CHAIN = { in: 'INPUT', out: 'OUTPUT', pre: 'PREROUTING', post: 'POSTROUTING' }.freeze unless defined? CHAIN # , nil => "FORWARD"}
TARGET = { allow: 'ACCEPT', reject: 'REJECT', deny: 'DROP', masquerade: 'MASQUERADE', redirect: 'REDIRECT', log: 'LOG --log-prefix \'iptables: \' --log-level 7' }.freeze unless defined? TARGET
def build_firewall_rule(new_resource, ip_version = 'ipv4')
return new_resource.raw.strip if new_resource.raw
type = new_resource.command
firewall_rule = if new_resource.direction
"#{ip_version} filter #{CHAIN[new_resource.direction.to_sym]} "
else
"#{ip_version} filter FORWARD "
end
firewall_rule << "#{new_resource.position} "
if [:pre, :post].include?(new_resource.direction)
firewall_rule << '-t nat '
end
# Firewalld order of prameters is important here see example output below:
# ipv4 filter INPUT 1 -s 1.2.3.4/32 -d 5.6.7.8/32 -i lo -p tcp -m tcp -m state --state NEW -m comment --comment "hello" -j DROP
firewall_rule << "-s #{ip_with_mask(new_resource, new_resource.source)} " if new_resource.source && new_resource.source != '0.0.0.0/0'
firewall_rule << "-d #{new_resource.destination} " if new_resource.destination
firewall_rule << "-i #{new_resource.interface} " if new_resource.interface
firewall_rule << "-o #{new_resource.dest_interface} " if new_resource.dest_interface
firewall_rule << "-p #{new_resource.protocol} " if new_resource.protocol && new_resource.protocol.to_s.to_sym != :none
firewall_rule << '-m tcp ' if new_resource.protocol && new_resource.protocol.to_s.to_sym == :tcp
# using multiport here allows us to simplify our greps and rule building
firewall_rule << "-m multiport --sports #{port_to_s(new_resource.source_port)} " if new_resource.source_port
firewall_rule << "-m multiport --dports #{port_to_s(dport_calc(new_resource))} " if dport_calc(new_resource)
firewall_rule << "-m state --state #{new_resource.stateful.is_a?(Array) ? new_resource.stateful.join(',').upcase : new_resource.stateful.to_s.upcase} " if new_resource.stateful
firewall_rule << "-m comment --comment '#{new_resource.description}' "
firewall_rule << "-j #{TARGET[type]} "
firewall_rule << "--to-ports #{new_resource.redirect_port} " if type == :redirect
firewall_rule.strip!
firewall_rule
end
end
end
end

View File

@@ -0,0 +1,105 @@
module FirewallCookbook
module Helpers
module Iptables
include FirewallCookbook::Helpers
include Chef::Mixin::ShellOut
CHAIN = { in: 'INPUT', out: 'OUTPUT', pre: 'PREROUTING', post: 'POSTROUTING' }.freeze unless defined? CHAIN # , nil => "FORWARD"}
TARGET = { allow: 'ACCEPT', reject: 'REJECT', deny: 'DROP', masquerade: 'MASQUERADE', redirect: 'REDIRECT', log: 'LOG --log-prefix "iptables: " --log-level 7' }.freeze unless defined? TARGET
def build_firewall_rule(current_node, rule_resource, ipv6 = false)
el5 = (current_node['platform'] == 'rhel' || current_node['platform'] == 'centos') && Gem::Dependency.new('', '~> 5.0').match?('', current_node['platform_version'])
return rule_resource.raw.strip if rule_resource.raw
firewall_rule = if rule_resource.direction
"-A #{CHAIN[rule_resource.direction.to_sym]} "
else
'-A FORWARD '
end
if [:pre, :post].include?(rule_resource.direction)
firewall_rule << '-t nat '
end
# Iptables order of prameters is important here see example output below:
# -A INPUT -s 1.2.3.4/32 -d 5.6.7.8/32 -i lo -p tcp -m tcp -m state --state NEW -m comment --comment "hello" -j DROP
firewall_rule << "-s #{ip_with_mask(rule_resource, rule_resource.source)} " if rule_resource.source && rule_resource.source != '0.0.0.0/0'
firewall_rule << "-d #{rule_resource.destination} " if rule_resource.destination
firewall_rule << "-i #{rule_resource.interface} " if rule_resource.interface
firewall_rule << "-o #{rule_resource.dest_interface} " if rule_resource.dest_interface
firewall_rule << "-p #{rule_resource.protocol} " if rule_resource.protocol && rule_resource.protocol.to_s.to_sym != :none
firewall_rule << '-m tcp ' if rule_resource.protocol && rule_resource.protocol.to_s.to_sym == :tcp
# using multiport here allows us to simplify our greps and rule building
firewall_rule << "-m multiport --sports #{port_to_s(rule_resource.source_port)} " if rule_resource.source_port
firewall_rule << "-m multiport --dports #{port_to_s(dport_calc(rule_resource))} " if dport_calc(rule_resource)
firewall_rule << "-m state --state #{rule_resource.stateful.is_a?(Array) ? rule_resource.stateful.join(',').upcase : rule_resource.stateful.upcase} " if rule_resource.stateful
# the comments extension is not available for ip6tables on rhel/centos 5
unless el5 && ipv6
firewall_rule << "-m comment --comment \"#{rule_resource.description}\" "
end
firewall_rule << "-j #{TARGET[rule_resource.command.to_sym]} "
firewall_rule << "--to-ports #{rule_resource.redirect_port} " if rule_resource.command == :redirect
firewall_rule.strip!
firewall_rule
end
def iptables_packages(new_resource)
if ipv6_enabled?(new_resource)
%w(iptables iptables-ipv6)
else
%w(iptables)
end
end
def iptables_commands(new_resource)
if ipv6_enabled?(new_resource)
%w(iptables ip6tables)
else
%w(iptables)
end
end
def log_iptables(new_resource)
iptables_commands(new_resource).each do |cmd|
shell_out!("#{cmd} -L -n")
end
rescue
Chef::Log.info('log_iptables failed!')
end
def iptables_flush!(new_resource)
iptables_commands(new_resource).each do |cmd|
shell_out!("#{cmd} -F")
end
end
def iptables_default_allow!(new_resource)
iptables_commands(new_resource).each do |cmd|
shell_out!("#{cmd} -P INPUT ACCEPT")
shell_out!("#{cmd} -P OUTPUT ACCEPT")
shell_out!("#{cmd} -P FORWARD ACCEPT")
end
end
def default_ruleset(current_node)
current_node['firewall']['iptables']['defaults'][:ruleset]
end
def ensure_default_rules_exist(current_node, new_resource)
input = new_resource.rules
# don't use iptables_commands here since we do populate the
# hash regardless of ipv6 status
%w(iptables ip6tables).each do |name|
input[name] = {} unless input[name]
input[name].merge!(default_ruleset(current_node))
end
end
end
end
end

View File

@@ -0,0 +1,129 @@
module FirewallCookbook
module Helpers
module Ufw
include FirewallCookbook::Helpers
include Chef::Mixin::ShellOut
def ufw_rules_filename
'/etc/default/ufw-chef.rules'
end
def ufw_active?
cmd = shell_out!('ufw', 'status')
cmd.stdout =~ /^Status:\sactive/
end
def ufw_disable!
shell_out!('ufw', 'disable', input: 'yes')
end
def ufw_enable!
shell_out!('ufw', 'enable', input: 'yes')
end
def ufw_reset!
shell_out!('ufw', 'reset', input: 'yes')
end
def ufw_logging!(param)
shell_out!('ufw', 'logging', param.to_s)
end
def ufw_rule!(cmd)
shell_out!(cmd, input: 'yes')
end
def build_rule(new_resource)
Chef::Log.info("#{new_resource.name} apply_rule #{new_resource.command}")
# 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 << "#{new_resource.command} 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
# if we don't do this, ufw will fail as it does not support protocol numbers, so we'll only allow it to run if specifying icmp/tcp/udp protocol types
if new_resource.protocol && !new_resource.protocol.to_s.downcase.match('^(tcp|udp|icmp)$')
msg = ''
msg << "firewall_rule[#{new_resource.name}] was asked to "
msg << "#{new_resource.command} a rule using protocol #{new_resource.protocol} "
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
if new_resource.raw
"ufw #{new_resource.raw.strip}"
else
"ufw #{rule(new_resource)}"
end
end
def rule(new_resource)
rule = ''
rule << "#{new_resource.command} "
rule << rule_interface(new_resource)
rule << rule_logging(new_resource)
rule << rule_proto(new_resource)
rule << rule_dest_port(new_resource)
rule << rule_source_port(new_resource)
rule.strip
end
def rule_interface(new_resource)
rule = ''
rule << "#{new_resource.direction} " if new_resource.direction
rule << "on #{new_resource.interface} " if new_resource.interface && new_resource.direction
rule << "in on #{new_resource.interface} " if new_resource.interface && !new_resource.direction
rule
end
def rule_proto(new_resource)
rule = ''
rule << "proto #{new_resource.protocol} " if new_resource.protocol && new_resource.protocol.to_s.to_sym != :none
rule
end
def rule_dest_port(new_resource)
rule = if new_resource.destination
"to #{new_resource.destination} "
else
'to any '
end
rule << "port #{port_to_s(dport_calc(new_resource))} " if dport_calc(new_resource)
rule
end
def rule_source_port(new_resource)
rule = if new_resource.source
"from #{new_resource.source} "
else
'from any '
end
if new_resource.source_port
rule << "port #{port_to_s(new_resource.source_port)} "
end
rule
end
def rule_logging(new_resource)
case new_resource.logging && new_resource.logging.to_sym
when :connections
'log '
when :packets
'log-all '
else
''
end
end
end
end
end

View File

@@ -0,0 +1,130 @@
module FirewallCookbook
module Helpers
module Windows
include FirewallCookbook::Helpers
include Chef::Mixin::ShellOut
def fixup_cidr(str)
newstr = str.clone
newstr.gsub!('0.0.0.0/0', 'any') if newstr.include?('0.0.0.0/0')
newstr.gsub!('/0', '') if newstr.include?('/0')
newstr
end
def windows_rules_filename
"#{ENV['HOME']}/windows-chef.rules"
end
def active?
@active ||= begin
cmd = shell_out!('netsh advfirewall show currentprofile')
cmd.stdout =~ /^State\sON/
end
end
def enable!
shell_out!('netsh advfirewall set currentprofile state on')
end
def disable!
shell_out!('netsh advfirewall set currentprofile state off')
end
def reset!
shell_out!('netsh advfirewall reset')
end
def add_rule!(params)
shell_out!("netsh advfirewall #{params}")
end
def delete_all_rules!
shell_out!('netsh advfirewall firewall delete rule name=all')
end
def to_type(new_resource)
cmd = new_resource.command
type = if cmd == :reject || cmd == :deny
:block
else
:allow
end
type
end
def build_rule(new_resource)
type = to_type(new_resource)
parameters = {}
parameters['description'] = "\"#{new_resource.description}\""
parameters['dir'] = new_resource.direction
new_resource.program && parameters['program'] = new_resource.program
parameters['service'] = new_resource.service ? new_resource.service : 'any'
parameters['protocol'] = new_resource.protocol
if new_resource.direction.to_sym == :out
parameters['localip'] = new_resource.source ? fixup_cidr(new_resource.source) : 'any'
parameters['localport'] = new_resource.source_port ? port_to_s(new_resource.source_port) : 'any'
parameters['interfacetype'] = new_resource.source_interface ? new_resource.source_interface : 'any'
parameters['remoteip'] = new_resource.destination ? fixup_cidr(new_resource.destination) : 'any'
parameters['remoteport'] = port_to_s(new_resource.dest_port) ? new_resource.dest_port : 'any'
else
parameters['localip'] = new_resource.destination ? new_resource.destination : 'any'
parameters['localport'] = dport_calc(new_resource) ? port_to_s(dport_calc(new_resource)) : 'any'
parameters['interfacetype'] = new_resource.dest_interface ? new_resource.dest_interface : 'any'
parameters['remoteip'] = new_resource.source ? fixup_cidr(new_resource.source) : 'any'
parameters['remoteport'] = new_resource.source_port ? port_to_s(new_resource.source_port) : 'any'
end
parameters['action'] = type.to_s
partial_command = parameters.map { |k, v| "#{k}=#{v}" }.join(' ')
"firewall add rule name=\"#{new_resource.name}\" #{partial_command}"
end
def rule_exists?(name)
@exists ||= begin
cmd = shell_out!("netsh advfirewall firewall show rule name=\"#{name}\"", returns: [0, 1])
cmd.stdout !~ /^No rules match the specified criteria/
end
end
def show_all_rules!
cmd = shell_out!('netsh advfirewall firewall show rule name=all')
cmd.stdout.each_line do |line|
Chef::Log.warn(line)
end
end
def rule_up_to_date?(name, type)
@up_to_date ||= begin
desired_parameters = rule_parameters(type)
current_parameters = {}
cmd = shell_out!("netsh advfirewall firewall show rule name=\"#{name}\" verbose")
cmd.stdout.each_line do |line|
current_parameters['description'] = "\"#{Regexp.last_match(1).chomp}\"" if line =~ /^Description:\s+(.*)$/
current_parameters['dir'] = Regexp.last_match(1).chomp if line =~ /^Direction:\s+(.*)$/
current_parameters['program'] = Regexp.last_match(1).chomp if line =~ /^Program:\s+(.*)$/
current_parameters['service'] = Regexp.last_match(1).chomp if line =~ /^Service:\s+(.*)$/
current_parameters['protocol'] = Regexp.last_match(1).chomp if line =~ /^Protocol:\s+(.*)$/
current_parameters['localip'] = Regexp.last_match(1).chomp if line =~ /^LocalIP:\s+(.*)$/
current_parameters['localport'] = Regexp.last_match(1).chomp if line =~ /^LocalPort:\s+(.*)$/
current_parameters['interfacetype'] = Regexp.last_match(1).chomp if line =~ /^InterfaceTypes:\s+(.*)$/
current_parameters['remoteip'] = Regexp.last_match(1).chomp if line =~ /^RemoteIP:\s+(.*)$/
current_parameters['remoteport'] = Regexp.last_match(1).chomp if line =~ /^RemotePort:\s+(.*)$/
current_parameters['action'] = Regexp.last_match(1).chomp if line =~ /^Action:\s+(.*)$/
end
up_to_date = true
desired_parameters.each do |k, v|
up_to_date = false if current_parameters[k] !~ /^["]?#{v}["]?$/i
end
up_to_date
end
end
end
end
end

View File

@@ -2,31 +2,29 @@ if defined?(ChefSpec)
ChefSpec.define_matcher(:firewall)
ChefSpec.define_matcher(:firewall_rule)
def enable_firewall(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall, :enable, resource)
# actions(:install, :restart, :disable, :flush, :save)
def install_firewall(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall, :install, resource)
end
def restart_firewall(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall, :restart, resource)
end
def disable_firewall(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall, :disable, resource)
end
def allow_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :allow, resource)
def flush_firewall(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall, :flush, resource)
end
def deny_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :deny, resource)
def save_firewall(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall, :save, resource)
end
def reject_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :reject, resource)
end
def log_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :log, resource)
end
def remove_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :remove, resource)
def create_firewall_rule(resource)
ChefSpec::Matchers::ResourceMatcher.new(:firewall_rule, :create, resource)
end
end

View File

@@ -15,78 +15,141 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'poise'
class Chef
class Provider::FirewallFirewalld < Provider
include Poise
include Chef::Mixin::ShellOut
class Provider::FirewallFirewalld < Chef::Provider::LWRPBase
include FirewallCookbook::Helpers::Firewalld
def action_enable
# prints all the firewall rules
# pp @new_resource.subresources
log_current_firewalld
if active?
Chef::Log.debug("#{@new_resource} already enabled.")
else
Chef::Log.debug("#{@new_resource} is about to be enabled")
shell_out!('service', 'firewalld', 'start')
shell_out!('firewall-cmd', '--set-default-zone=drop')
Chef::Log.info("#{@new_resource} enabled.")
new_resource.updated_by_last_action(true)
end
provides :firewall, os: 'linux', platform_family: %w(rhel fedora) do |node|
node['platform_version'].to_f >= 7.0 && !node['firewall']['redhat7_iptables']
end
def action_disable
if active?
shell_out!('firewall-cmd', '--set-default-zone=public')
shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
Chef::Log.info("#{@new_resource} disabled")
new_resource.updated_by_last_action(true)
else
Chef::Log.debug("#{@new_resource} already disabled.")
end
def whyrun_supported?
false
end
def action_flush
shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
Chef::Log.info("#{@new_resource} flushed.")
end
action :install do
next if disabled?(new_resource)
def action_save
if shell_out!('firewall-cmd', '--direct', '--get-all-rules').stdout != shell_out!('firewall-cmd', '--direct', '--permanent', '--get-all-rules').stdout
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'INPUT')
shell_out!('firewall-cmd', '--direct', '--permanent', '--remove-rules', 'ipv4', 'filter', 'OUTPUT')
shell_out!('firewall-cmd', '--direct', '--get-all-rules').stdout.lines do |line|
shell_out!("firewall-cmd --direct --permanent --add-rule #{line}")
converge_by('install firewalld, create template for /etc/sysconfig') do
package 'firewalld' do
action :install
end
Chef::Log.info("#{@new_resource} saved.")
service 'firewalld' do
action [:enable, :start]
end
file "create empty #{firewalld_rules_filename}" do
path firewalld_rules_filename
content '# created by chef to allow service to start'
not_if { ::File.exist?(firewalld_rules_filename) }
end
end
end
action :restart do
next if disabled?(new_resource)
# ensure it's initialized
new_resource.rules({}) unless new_resource.rules
new_resource.rules['firewalld'] = {} unless new_resource.rules['firewalld']
# this populates the hash of rules from firewall_rule resources
firewall_rules = run_context.resource_collection.select { |item| item.is_a?(Chef::Resource::FirewallRule) }
firewall_rules.each do |firewall_rule|
next unless firewall_rule.action.include?(:create) && !firewall_rule.should_skip?(:create)
ip_versions(firewall_rule).each do |ip_version|
# build rules to apply with weight
k = "firewall-cmd --direct --add-rule #{build_firewall_rule(firewall_rule, ip_version)}"
v = firewall_rule.position
# unless we're adding them for the first time.... bail out.
next if new_resource.rules['firewalld'].key?(k) && new_resource.rules['firewalld'][k] == v
new_resource.rules['firewalld'][k] = v
# If persistent rules is enabled (default) make sure we add a permanent rule at the same time
perm_rules = node && node['firewall'] && node['firewall']['firewalld'] && node['firewall']['firewalld']['permanent']
if firewall_rule.permanent || perm_rules
k = "firewall-cmd --permanent --direct --add-rule #{build_firewall_rule(firewall_rule, ip_version)}"
new_resource.rules['firewalld'][k] = v
end
end
end
# ensure a file resource exists with the current firewalld rules
begin
firewalld_file = run_context.resource_collection.find(file: firewalld_rules_filename)
rescue
firewalld_file = file firewalld_rules_filename do
action :nothing
end
end
firewalld_file.content build_rule_file(new_resource.rules['firewalld'])
firewalld_file.run_action(:create)
# ensure the service is running
service 'firewalld' do
action [:enable, :start]
end
# mark updated if we changed the zone
unless firewalld_default_zone?(new_resource.enabled_zone)
firewalld_default_zone!(new_resource.enabled_zone)
new_resource.updated_by_last_action(true)
end
# if the file was changed, load new ruleset
if firewalld_file.updated_by_last_action?
firewalld_flush!
# TODO: support logging
new_resource.rules['firewalld'].sort_by { |_k, v| v }.map { |k, _v| k }.each do |cmd|
firewalld_rule!(cmd)
end
new_resource.updated_by_last_action(true)
else
Chef::Log.info("#{@new_resource} already up-to-date.")
end
end
private
action :disable do
next if disabled?(new_resource)
def active?
@active ||= begin
cmd = shell_out('firewall-cmd', '--state')
cmd.stdout =~ /^running$/
firewalld_flush!
firewalld_default_zone!(new_resource.disabled_zone)
new_resource.updated_by_last_action(true)
service 'firewalld' do
action [:disable, :stop]
end
file "create empty #{firewalld_rules_filename}" do
path firewalld_rules_filename
content '# created by chef to allow service to start'
action :create
end
end
def log_current_firewalld
cmdstr = 'firewall-cmd --direct --get-all-rules'
Chef::Log.info("#{@new_resource} log_current_firewalld (#{cmdstr}):")
cmd = shell_out!(cmdstr)
Chef::Log.info(cmd.inspect)
rescue
Chef::Log.info("#{@new_resource} log_current_firewalld failed!")
action :flush do
next if disabled?(new_resource)
firewalld_flush!
new_resource.updated_by_last_action(true)
file "create empty #{firewalld_rules_filename}" do
path firewalld_rules_filename
content '# created by chef to allow service to start'
action :create
end
end
action :save do
next if disabled?(new_resource)
unless firewalld_all_rules_permanent!
firewalld_save!
new_resource.updated_by_last_action(true)
end
end
end
end

View File

@@ -17,94 +17,139 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'poise'
class Chef
class Provider::FirewallIptables < Provider
include Poise
include Chef::Mixin::ShellOut
class Provider::FirewallIptables < Chef::Provider::LWRPBase
include FirewallCookbook::Helpers
include FirewallCookbook::Helpers::Iptables
def action_enable
converge_by('install package iptables and default DROP if no rules exist') do
package 'iptables' do
action :install
provides :firewall, os: 'linux', platform_family: %w(rhel fedora) do |node|
node['platform_version'].to_f < 7.0 || node['firewall']['redhat7_iptables']
end
def whyrun_supported?
false
end
action :install do
next if disabled?(new_resource)
converge_by('install iptables and enable/start services') do
# can't pass an array without breaking chef 11 support
iptables_packages(new_resource).each do |p|
package p do
action :install
end
end
# prints all the firewall rules
# pp new_resource.subresources
log_current_iptables
if active?
Chef::Log.info("#{new_resource} already enabled.")
else
Chef::Log.debug("#{new_resource} is about to be enabled")
shell_out!('iptables -P INPUT DROP')
shell_out!('iptables -P OUTPUT DROP')
shell_out!('iptables -P FORWARD DROP')
iptables_commands(new_resource).each do |svc|
# must create empty file for service to start
file "create empty /etc/sysconfig/#{svc}" do
path "/etc/sysconfig/#{svc}"
content '# created by chef to allow service to start'
not_if { ::File.exist?("/etc/sysconfig/#{svc}") }
end
shell_out!('ip6tables -P INPUT DROP')
shell_out!('ip6tables -P OUTPUT DROP')
shell_out!('ip6tables -P FORWARD DROP')
Chef::Log.info("#{new_resource} enabled.")
new_resource.updated_by_last_action(true)
service svc do
action [:enable, :start]
end
end
end
end
def action_disable
if active?
shell_out!('iptables -P INPUT ACCEPT')
shell_out!('iptables -P OUTPUT ACCEPT')
shell_out!('iptables -P FORWARD ACCEPT')
shell_out!('iptables -F')
action :restart do
next if disabled?(new_resource)
shell_out!('ip6tables -P INPUT ACCEPT')
shell_out!('ip6tables -P OUTPUT ACCEPT')
shell_out!('ip6tables -P FORWARD ACCEPT')
shell_out!('ip6tables -F')
Chef::Log.info("#{new_resource} disabled")
# prints all the firewall rules
log_iptables(new_resource)
# ensure it's initialized
new_resource.rules({}) unless new_resource.rules
ensure_default_rules_exist(node, new_resource)
# this populates the hash of rules from firewall_rule resources
firewall_rules = run_context.resource_collection.select { |item| item.is_a?(Chef::Resource::FirewallRule) }
firewall_rules.each do |firewall_rule|
next unless firewall_rule.action.include?(:create) && !firewall_rule.should_skip?(:create)
types = if ipv6_rule?(firewall_rule) # an ip4 specific rule
%w(ip6tables)
elsif ipv4_rule?(firewall_rule) # an ip6 specific rule
%w(iptables)
else # or not specific
%w(iptables ip6tables)
end
types.each do |iptables_type|
# build rules to apply with weight
k = build_firewall_rule(node, firewall_rule, iptables_type == 'ip6tables')
v = firewall_rule.position
# unless we're adding them for the first time.... bail out.
next if new_resource.rules[iptables_type].key?(k) && new_resource.rules[iptables_type][k] == v
new_resource.rules[iptables_type][k] = v
end
end
iptables_commands(new_resource).each do |iptables_type|
iptables_filename = "/etc/sysconfig/#{iptables_type}"
# ensure a file resource exists with the current iptables rules
begin
iptables_file = run_context.resource_collection.find(file: iptables_filename)
rescue
iptables_file = file iptables_filename do
action :nothing
end
end
# this takes the commands in each hash entry and builds a rule file
iptables_file.content build_rule_file(new_resource.rules[iptables_type])
iptables_file.run_action(:create)
# if the file was unchanged, skip loop iteration, otherwise restart iptables
next unless iptables_file.updated_by_last_action?
service_affected = service iptables_type do
action :nothing
end
new_resource.notifies(:restart, service_affected, :delayed)
new_resource.updated_by_last_action(true)
else
Chef::Log.debug("#{new_resource} already disabled.")
end
end
def action_flush
shell_out!('iptables -F')
shell_out!('ip6tables -F')
Chef::Log.info("#{new_resource} flushed.")
end
action :disable do
next if disabled?(new_resource)
def action_save
shell_out!('service iptables save')
shell_out!('service ip6tables save')
Chef::Log.info("#{new_resource} saved.")
end
iptables_flush!(new_resource)
iptables_default_allow!(new_resource)
new_resource.updated_by_last_action(true)
private
iptables_commands(new_resource).each do |svc|
service svc do
action [:disable, :stop]
end
def active?
@active ||= begin
cmd = shell_out!('iptables-save')
cmd.stdout =~ /INPUT ACCEPT/
# must create empty file for service to start
file "create empty /etc/sysconfig/#{svc}" do
path "/etc/sysconfig/#{svc}"
content '# created by chef to allow service to start'
end
end
@active_v6 ||= begin
cmd = shell_out!('ip6tables-save')
cmd.stdout =~ /INPUT ACCEPT/
end
@active && @active_v6
end
def log_current_iptables
cmdstr = 'iptables -L'
Chef::Log.info("#{new_resource} log_current_iptables (#{cmdstr}):")
cmd = shell_out!(cmdstr)
Chef::Log.info(cmd.inspect)
cmdstr = 'ip6tables -L'
Chef::Log.info("#{new_resource} log_current_iptables (#{cmdstr}):")
cmd = shell_out!(cmdstr)
Chef::Log.info(cmd.inspect)
rescue
Chef::Log.info("#{new_resource} log_current_iptables failed!")
action :flush do
next if disabled?(new_resource)
iptables_flush!(new_resource)
new_resource.updated_by_last_action(true)
iptables_commands(new_resource).each do |svc|
# must create empty file for service to start
file "create empty /etc/sysconfig/#{svc}" do
path "/etc/sysconfig/#{svc}"
content '# created by chef to allow service to start'
end
end
end
end
end

View File

@@ -0,0 +1,158 @@
#
# Author:: Seth Chisamore (<schisamo@opscode.com>)
# Cookbook Name:: firewall
# Resource:: default
#
# 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.
#
class Chef
class Provider::FirewallIptablesUbuntu < Chef::Provider::LWRPBase
include FirewallCookbook::Helpers
include FirewallCookbook::Helpers::Iptables
provides :firewall, os: 'linux', platform_family: %w(debian) do |node|
node['firewall'] && node['firewall']['ubuntu_iptables']
end
def whyrun_supported?
false
end
action :install do
next if disabled?(new_resource)
converge_by('install iptables and enable/start services') do
# Can't pass an array without breaking chef 11 support
%w(iptables-persistent).each do |p|
package p do
action :install
end
end
%w(rules.v4 rules.v6).each do |svc|
# must create empty file for service to start
file "create empty /etc/iptables/#{svc}" do
path "/etc/iptables/#{svc}"
content '# created by chef to allow service to start'
not_if { ::File.exist?("/etc/iptables/#{svc}") }
end
end
service 'iptables-persistent' do
action [:enable, :start]
end
end
end
action :restart do
next if disabled?(new_resource)
# prints all the firewall rules
log_iptables(new_resource)
# ensure it's initialized
new_resource.rules({}) unless new_resource.rules
ensure_default_rules_exist(node, new_resource)
# this populates the hash of rules from firewall_rule resources
firewall_rules = run_context.resource_collection.select { |item| item.is_a?(Chef::Resource::FirewallRule) }
firewall_rules.each do |firewall_rule|
next unless firewall_rule.action.include?(:create) && !firewall_rule.should_skip?(:create)
types = if ipv6_rule?(firewall_rule) # an ip4 specific rule
%w(ip6tables)
elsif ipv4_rule?(firewall_rule) # an ip6 specific rule
%w(iptables)
else # or not specific
%w(iptables ip6tables)
end
types.each do |iptables_type|
# build rules to apply with weight
k = build_firewall_rule(node, firewall_rule, iptables_type == 'ip6tables')
v = firewall_rule.position
# unless we're adding them for the first time.... bail out.
next if new_resource.rules[iptables_type].key?(k) && new_resource.rules[iptables_type][k] == v
new_resource.rules[iptables_type][k] = v
end
end
%w(iptables ip6tables).each do |iptables_type|
iptables_filename = if iptables_type == 'ip6tables'
'/etc/iptables/rules.v6'
else
'/etc/iptables/rules.v4'
end
# ensure a file resource exists with the current iptables rules
begin
iptables_file = run_context.resource_collection.find(file: iptables_filename)
rescue
iptables_file = file iptables_filename do
action :nothing
end
end
iptables_file.content build_rule_file(new_resource.rules[iptables_type])
iptables_file.run_action(:create)
# if the file was changed, restart iptables
next unless iptables_file.updated_by_last_action?
service_affected = service 'iptables-persistent' do
action :nothing
end
new_resource.notifies(:restart, service_affected, :delayed)
new_resource.updated_by_last_action(true)
end
end
action :disable do
next if disabled?(new_resource)
iptables_flush!(new_resource)
iptables_default_allow!(new_resource)
new_resource.updated_by_last_action(true)
service 'iptables-persistent' do
action [:disable, :stop]
end
%w(rules.v4 rules.v6).each do |svc|
# must create empty file for service to start
file "create empty /etc/iptables/#{svc}" do
path "/etc/iptables/#{svc}"
content '# created by chef to allow service to start'
action :create
end
end
end
action :flush do
next if disabled?(new_resource)
iptables_flush!(new_resource)
new_resource.updated_by_last_action(true)
%w(rules.v4 rules.v6).each do |svc|
# must create empty file for service to start
file "create empty /etc/iptables/#{svc}" do
path "/etc/iptables/#{svc}"
content '# created by chef to allow service to start'
end
end
end
end
end

View File

@@ -0,0 +1,34 @@
#
# Author:: Ronald Doorn (<rdoorn@schubergphilis.com>)
# Cookbook Name:: firewall
# Provider:: rule_iptables
#
# Copyright 2015, computerlyrik
#
# 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.
#
class Chef
class Provider::FirewallRuleGeneric < Chef::Provider::LWRPBase
provides :firewall_rule
action :create do
return unless new_resource.notify_firewall
firewall_resource = run_context.resource_collection.find(firewall: new_resource.firewall_name)
fail 'could not find a firewall resource' unless firewall_resource
new_resource.notifies(:restart, firewall_resource, :delayed)
new_resource.updated_by_last_action(true)
end
end
end

View File

@@ -1,213 +0,0 @@
#
# Author:: Ronald Doorn (<rdoorn@schubergphilis.com>)
# Cookbook Name:: firewall
# Provider:: rule_iptables
#
# 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::FirewallRuleFirewalld < Provider
include Poise
include Chef::Mixin::ShellOut
include FirewallCookbook::Helpers
def action_allow
apply_rule(:allow)
end
def action_deny
apply_rule(:deny)
end
def action_reject
apply_rule(:reject)
end
def action_redirect
apply_rule(:redirect)
end
def action_masquerade
apply_rule(:masquerade)
end
def action_log
apply_rule(:log)
end
def action_remove
# TODO: specify which target to delete
# for now this will remove raw + all targeted lines
remove_rule(:allow)
remove_rule(:deny)
remove_rule(:reject)
remove_rule(:redirect)
remove_rule(:masquerade)
end
private
CHAIN = { :in => 'INPUT', :out => 'OUTPUT', :pre => 'PREROUTING', :post => 'POSTROUTING' } unless defined? CHAIN # , nil => "FORWARD"}
TARGET = { :allow => 'ACCEPT', :reject => 'REJECT', :deny => 'DROP', :masquerade => 'MASQUERADE', :redirect => 'REDIRECT', :log => 'LOG --log-prefix \'iptables: \' --log-level 7' } unless defined? TARGET
def apply_rule(type = nil)
ip_versions.each do |ip_version|
firewall_command = 'firewall-cmd --direct --add-rule '
# TODO: implement logging for :connections :packets
firewall_rule = build_firewall_rule(type, ip_version)
Chef::Log.debug("#{new_resource}: #{firewall_rule}")
if rule_exists?(firewall_rule)
Chef::Log.info("#{new_resource} #{type} rule exists... won't apply")
else
cmdstr = firewall_command + firewall_rule
converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do
notifying_block do
shell_out!(cmdstr) # shell_out! is already logged
new_resource.updated_by_last_action(true)
end
end
end
end
end
def remove_rule(type = nil)
ip_versions.each do |_ip_version|
firewall_command = 'firewall-cmd --direct --remove-rule '
# TODO: implement logging for :connections :packets
firewall_rule = build_firewall_rule(type)
Chef::Log.debug("#{new_resource}: #{firewall_rule}")
if rule_exists?(firewall_rule)
cmdstr = firewall_command + firewall_rule
converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do
notifying_block do
shell_out!(cmdstr) # shell_out! is already logged
new_resource.updated_by_last_action(true)
end
end
else
Chef::Log.info("#{new_resource} #{type} rule does not exists... won't remove")
end
end
end
def ipv4_rule?
if (new_resource.source && IPAddr.new(new_resource.source).ipv4?) ||
(new_resource.destination && IPAddr.new(new_resource.destination).ipv4?)
true
else
false
end
end
def ipv6_rule?
if (new_resource.source && IPAddr.new(new_resource.source).ipv6?) ||
(new_resource.destination && IPAddr.new(new_resource.destination).ipv6?)
true
else
false
end
end
def ip_versions
if ipv4_rule?
versions = ['ipv4']
elsif ipv6_rule?
versions = ['ipv6']
else # no source or destination address, add rules for both ipv4 and ipv6
versions = %w(ipv4 ipv6)
end
versions
end
def build_firewall_rule(type = nil, ip_version = 'ipv4')
if new_resource.raw
firewall_rule = new_resource.raw.strip
else
firewall_rule = "#{ip_version} filter "
if new_resource.direction
firewall_rule << "#{CHAIN[new_resource.direction.to_sym]} "
else
firewall_rule << 'FORWARD '
end
firewall_rule << "#{new_resource.position ? new_resource.position : 1} "
if [:pre, :post].include?(new_resource.direction)
firewall_rule << '-t nat '
end
# Firewalld order of prameters is important here see example output below:
# ipv4 filter INPUT 1 -s 1.2.3.4/32 -d 5.6.7.8/32 -i lo -p tcp -m tcp -m state --state NEW -m comment --comment "hello" -j DROP
firewall_rule << "-s #{ip_with_mask(new_resource.source)} " if new_resource.source && new_resource.source != '0.0.0.0/0'
firewall_rule << "-d #{new_resource.destination} " if new_resource.destination
firewall_rule << "-i #{new_resource.interface} " if new_resource.interface
firewall_rule << "-o #{new_resource.dest_interface} " if new_resource.dest_interface
firewall_rule << "-p #{new_resource.protocol} " if new_resource.protocol
firewall_rule << '-m tcp ' if new_resource.protocol.to_sym == :tcp
# using multiport here allows us to simplify our greps and rule building
firewall_rule << "-m multiport --sports #{port_to_s(new_resource.source_port)} " if new_resource.source_port
firewall_rule << "-m multiport --dports #{port_to_s(dport_calc)} " if dport_calc
firewall_rule << "-m state --state #{new_resource.stateful.is_a?(Array) ? new_resource.stateful.join(',').upcase : new_resource.stateful.upcase} " if new_resource.stateful
firewall_rule << "-m comment --comment '#{new_resource.description}' "
firewall_rule << "-j #{TARGET[type]} "
firewall_rule << "--to-ports #{new_resource.redirect_port} " if type == 'redirect'
firewall_rule.strip!
end
firewall_rule
end
def rule_exists?(rule)
fail 'no rule supplied' unless rule
# match quotes generously
detect_rule = rule.gsub(/'/, "'*")
detect_rule = detect_rule.gsub(/"/, '"*')
match = shell_out!('firewall-cmd --direct --get-all-rules').stdout.lines.find do |line|
# Chef::Log.debug("matching: [#{detect_rule}] to [#{line.chomp.rstrip}]")
line =~ /#{detect_rule}/
end
match
rescue Mixlib::ShellOut::ShellCommandFailed
Chef::Log.debug("#{new_resource} check fails with: " + match.inspect)
Chef::Log.debug("#{new_resource} assuming #{rule} rule does not exist")
false
end
def dport_calc
new_resource.dest_port || new_resource.port
end
def ip_with_mask(ip)
if ip.include?('/')
ip
elsif ipv4_rule?
"#{ip}/32"
elsif ipv6_rule?
"#{ip}/128"
else
ip
end
end
end
end

View File

@@ -1,231 +0,0 @@
#
# Cookbook Name:: firewall
# Provider:: rule_iptables
#
# Copyright 2012, computerlyrik
#
# 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::FirewallRuleIptables < Provider
include Poise
include Chef::Mixin::ShellOut
include FirewallCookbook::Helpers
def action_allow
apply_rule(:allow)
end
def action_deny
apply_rule(:deny)
end
def action_reject
apply_rule(:reject)
end
def action_redirect
apply_rule(:redirect)
end
def action_masquerade
apply_rule(:masquerade)
end
def action_log
apply_rule(:log)
end
def action_remove
# TODO: specify which target to delete
# for now this will remove raw + all targeted lines
remove_rule(:allow)
remove_rule(:deny)
remove_rule(:reject)
remove_rule(:redirect)
remove_rule(:masquerade)
end
private
CHAIN = { :in => 'INPUT', :out => 'OUTPUT', :pre => 'PREROUTING', :post => 'POSTROUTING' } unless defined? CHAIN # , nil => "FORWARD"}
TARGET = { :allow => 'ACCEPT', :reject => 'REJECT', :deny => 'DROP', :masquerade => 'MASQUERADE', :redirect => 'REDIRECT', :log => 'LOG --log-prefix "iptables: " --log-level 7' } unless defined? TARGET
def apply_rule(type = nil)
firewall_commands = determine_iptables_commands
firewall_commands.each do |firewall_command|
ipv6 = (firewall_command == 'ip6tables') ? true : false
if new_resource.position
firewall_command << ' -I '
else
firewall_command << ' -A '
end
# TODO: implement logging for :connections :packets
firewall_rule = build_firewall_rule(type)
Chef::Log.debug("#{new_resource}: #{firewall_rule}")
if rule_exists?(firewall_rule, ipv6)
Chef::Log.info("#{new_resource} #{type} rule exists... won't apply")
else
cmdstr = firewall_command + firewall_rule
converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do
notifying_block do
shell_out!(cmdstr) # shell_out! is already logged
new_resource.updated_by_last_action(true)
end
end
end
end
end
def remove_rule(type = nil)
firewall_commands = determine_iptables_commands
firewall_commands.each do |firewall_command|
ipv6 = (firewall_command == 'ip6tables') ? true : false
# TODO: implement logging for :connections :packets
firewall_rule = build_firewall_rule(type)
Chef::Log.debug("#{new_resource}: #{firewall_rule}")
if rule_exists?(firewall_rule, ipv6)
cmdstr = firewall_command + firewall_rule
converge_by("firewall_rule[#{new_resource.name}] #{firewall_rule}") do
notifying_block do
shell_out!(cmdstr) # shell_out! is already logged
new_resource.updated_by_last_action(true)
end
end
else
Chef::Log.info("#{new_resource} #{type} rule does not exist... won't remove")
end
end
end
def ipv4_rule?
if (new_resource.source && IPAddr.new(new_resource.source).ipv4?) ||
(new_resource.destination && IPAddr.new(new_resource.destination).ipv4?)
true
else
false
end
end
def ipv6_rule?
if (new_resource.source && IPAddr.new(new_resource.source).ipv6?) ||
(new_resource.destination && IPAddr.new(new_resource.destination).ipv6?)
true
else
false
end
end
def determine_iptables_commands
if ipv4_rule?
commands = ['iptables']
elsif ipv6_rule?
commands = ['ip6tables']
else # no source or destination address, add rules for both ipv4 and ipv6
commands = %w(iptables ip6tables)
end
commands
end
def build_firewall_rule(type = nil)
if new_resource.raw
firewall_rule = new_resource.raw.strip
else
firewall_rule = ''
if new_resource.direction
firewall_rule << "#{CHAIN[new_resource.direction.to_sym]} "
else
firewall_rule << 'FORWARD '
end
firewall_rule << "#{new_resource.position} " if new_resource.position
if [:pre, :post].include?(new_resource.direction)
firewall_rule << '-t nat '
end
# Iptables order of prameters is important here see example output below:
# -A INPUT -s 1.2.3.4/32 -d 5.6.7.8/32 -i lo -p tcp -m tcp -m state --state NEW -m comment --comment "hello" -j DROP
firewall_rule << "-s #{ip_with_mask(new_resource.source)} " if new_resource.source && new_resource.source != '0.0.0.0/0'
firewall_rule << "-d #{new_resource.destination} " if new_resource.destination
firewall_rule << "-i #{new_resource.interface} " if new_resource.interface
firewall_rule << "-o #{new_resource.dest_interface} " if new_resource.dest_interface
firewall_rule << "-p #{new_resource.protocol} " if new_resource.protocol
firewall_rule << '-m tcp ' if new_resource.protocol.to_sym == :tcp
# using multiport here allows us to simplify our greps and rule building
firewall_rule << "-m multiport --sports #{port_to_s(new_resource.source_port)} " if new_resource.source_port
firewall_rule << "-m multiport --dports #{port_to_s(dport_calc)} " if dport_calc
firewall_rule << "-m state --state #{new_resource.stateful.is_a?(Array) ? new_resource.stateful.join(',').upcase : new_resource.stateful.upcase} " if new_resource.stateful
firewall_rule << "-m comment --comment \"#{new_resource.description}\" "
firewall_rule << "-j #{TARGET[type]} "
firewall_rule << "--to-ports #{new_resource.redirect_port} " if type == 'redirect'
firewall_rule.strip!
end
firewall_rule
end
def rule_exists?(rule, ipv6 = false)
fail 'no rule supplied' unless rule
if new_resource.position
detect_rule = rule.gsub(/#{CHAIN[new_resource.direction]}\s(\d+)/, '\1' + " -A #{CHAIN[new_resource.direction]}")
else
detect_rule = rule
end
# match quotes generously
detect_rule = detect_rule.gsub(/'/, "'*")
detect_rule = detect_rule.gsub(/"/, '"*')
line_number = 0
match = shell_out!(ipv6 ? 'ip6tables-save' : 'iptables-save').stdout.lines.find do |line|
next if line !~ /#{CHAIN[new_resource.direction]}/
next if line[0] != '-'
line_number += 1
line = "#{line_number} #{line}" if new_resource.position
# Chef::Log.debug("matching: [#{detect_rule}] to [#{line.chomp.rstrip}]")
line =~ /#{detect_rule}/
end
match
rescue Mixlib::ShellOut::ShellCommandFailed
Chef::Log.debug("#{new_resource} check fails with: " + match.inspect)
Chef::Log.debug("#{new_resource} assuming #{rule} rule does not exist")
false
end
def dport_calc
new_resource.dest_port || new_resource.port
end
def ip_with_mask(ip)
if ip.include?('/')
ip
elsif ipv4_rule?
"#{ip}/32"
elsif ipv6_rule?
"#{ip}/128"
else
ip
end
end
end
end

View File

@@ -1,241 +0,0 @@
#
# 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

View File

@@ -17,18 +17,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'poise'
class Chef
class Provider::FirewallUfw < Provider
include Poise
include Chef::Mixin::ShellOut
class Provider::FirewallUfw < Chef::Provider::LWRPBase
include FirewallCookbook::Helpers::Ufw
def action_enable
converge_by('install ufw, template some defaults, and ufw enable') do
provides :firewall, os: 'linux', platform_family: %w(debian) do |node|
!(node['firewall'] && node['firewall']['ubuntu_iptables'])
end
def whyrun_supported?
false
end
action :install do
next if disabled?(new_resource)
converge_by('install ufw, create template for /etc/default') do
package 'ufw' do
action :nothing
end.run_action(:install) # need this now if running in a provider
action :install
end
template '/etc/default/ufw' do
action [:create]
@@ -37,40 +44,87 @@ class Chef
mode '0644'
source 'ufw/default.erb'
cookbook 'firewall'
action :nothing
end.run_action(:create) # need this now if running in a provider
end
# new_resource.subresources contains all the firewall rules
if active?
Chef::Log.debug("#{new_resource} already enabled.")
else
shell_out!('ufw', 'enable', :input => 'yes')
Chef::Log.info("#{new_resource} enabled")
if new_resource.log_level
shell_out!('ufw', 'logging', new_resource.log_level.to_s)
Chef::Log.info("#{new_resource} logging enabled at '#{new_resource.log_level}' level")
end
new_resource.updated_by_last_action(true)
file "create empty #{ufw_rules_filename}" do
path ufw_rules_filename
content '# created by chef to allow service to start'
not_if { ::File.exist?(ufw_rules_filename) }
end
end
end
def action_disable
if active?
shell_out!('ufw', 'disable')
Chef::Log.info("#{new_resource} disabled")
action :restart do
next if disabled?(new_resource)
# ensure it's initialized
new_resource.rules({}) unless new_resource.rules
new_resource.rules['ufw'] = {} unless new_resource.rules['ufw']
# this populates the hash of rules from firewall_rule resources
firewall_rules = run_context.resource_collection.select { |item| item.is_a?(Chef::Resource::FirewallRule) }
firewall_rules.each do |firewall_rule|
next unless firewall_rule.action.include?(:create) && !firewall_rule.should_skip?(:create)
# build rules to apply with weight
k = build_rule(firewall_rule)
v = firewall_rule.position
# unless we're adding them for the first time.... bail out.
unless new_resource.rules['ufw'].key?(k) && new_resource.rules['ufw'][k] == v
new_resource.rules['ufw'][k] = v
end
end
# ensure a file resource exists with the current ufw rules
begin
ufw_file = run_context.resource_collection.find(file: ufw_rules_filename)
rescue
ufw_file = file ufw_rules_filename do
action :nothing
end
end
ufw_file.content build_rule_file(new_resource.rules['ufw'])
ufw_file.run_action(:create)
# if the file was changed, restart iptables
if ufw_file.updated_by_last_action?
ufw_reset!
ufw_logging!(new_resource.log_level) if new_resource.log_level
new_resource.rules['ufw'].sort_by { |_k, v| v }.map { |k, _v| k }.each do |cmd|
ufw_rule!(cmd)
end
# ensure it's enabled _after_ rules are inputted, to catch malformed rules
ufw_enable! unless ufw_active?
new_resource.updated_by_last_action(true)
else
Chef::Log.debug("#{new_resource} already disabled.")
end
end
private
action :disable do
next if disabled?(new_resource)
def active?
@active ||= begin
cmd = shell_out!('ufw', 'status')
cmd.stdout =~ /^Status:\sactive/
file "create empty #{ufw_rules_filename}" do
path ufw_rules_filename
content '# created by chef to allow service to start'
end
if ufw_active?
ufw_disable!
new_resource.updated_by_last_action(true)
end
end
action :flush do
next if disabled?(new_resource)
ufw_reset!
new_resource.updated_by_last_action(true)
file "create empty #{ufw_rules_filename}" do
path ufw_rules_filename
content '# created by chef to allow service to start'
end
end
end

View File

@@ -0,0 +1,112 @@
#
# Author:: Sander van Harmelen (<svanharmelen@schubergphilis.com>)
# Cookbook Name:: firewall
# Provider:: windows
#
# 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.
#
class Chef
class Provider::FirewallWindows < Chef::Provider::LWRPBase
include FirewallCookbook::Helpers::Windows
provides :firewall, os: 'windows'
def whyrun_supported?
false
end
action :install do
next if disabled?(new_resource)
converge_by('enable and start Windows Firewall service') do
service 'MpsSvc' do
action [:enable, :start]
end
end
end
action :restart do
next if disabled?(new_resource)
# ensure it's initialized
new_resource.rules({}) unless new_resource.rules
new_resource.rules['windows'] = {} unless new_resource.rules['windows']
firewall_rules = run_context.resource_collection.select { |item| item.is_a?(Chef::Resource::FirewallRule) }
firewall_rules.each do |firewall_rule|
next unless firewall_rule.action.include?(:create) && !firewall_rule.should_skip?(:create)
# build rules to apply with weight
k = build_rule(firewall_rule)
v = firewall_rule.position
# unless we're adding them for the first time.... bail out.
unless new_resource.rules['windows'].key?(k) && new_resource.rules['windows'][k] == v
new_resource.rules['windows'][k] = v
end
end
# ensure a file resource exists with the current rules
begin
windows_file = run_context.resource_collection.find(file: windows_rules_filename)
rescue
windows_file = file windows_rules_filename do
action :nothing
end
end
windows_file.content build_rule_file(new_resource.rules['windows'])
windows_file.run_action(:create)
# if the file was changed, restart iptables
if windows_file.updated_by_last_action?
disable! if active?
delete_all_rules! # clear entirely
reset! # populate default rules
new_resource.rules['windows'].sort_by { |_k, v| v }.map { |k, _v| k }.each do |cmd|
add_rule!(cmd)
end
# ensure it's enabled _after_ rules are inputted, to catch malformed rules
enable! unless active?
new_resource.updated_by_last_action(true)
end
end
action :disable do
next if disabled?(new_resource)
converge_by('disable and stop Windows Firewall service') do
if active?
disable!
Chef::Log.info("#{new_resource} disabled.")
new_resource.updated_by_last_action(true)
else
Chef::Log.debug("#{new_resource} already disabled.")
end
service 'MpsSvc' do
action [:disable, :stop]
end
end
end
action :flush do
next if disabled?(new_resource)
reset!
Chef::Log.info("#{new_resource} reset.")
end
end
end

View File

@@ -1,10 +1,23 @@
require 'poise'
class Chef
class Resource::Firewall < Resource
include Poise(:container => true)
class Resource::Firewall < Chef::Resource::LWRPBase
resource_name(:firewall)
provides(:firewall)
actions(:install, :restart, :disable, :flush, :save)
default_action(:install)
actions(:enable, :disable, :flush, :save)
attribute(:log_level, :kind_of => [Symbol, String], :equal_to => [:low, :medium, :high, :full, 'low', 'medium', 'high', 'full'], :default => :low)
# allow both kinds of logic -- eventually remove the :disabled one.
# the positive logic is much easier to follow.
attribute(:disabled, kind_of: [TrueClass, FalseClass], default: false)
attribute(:enabled, kind_of: [TrueClass, FalseClass], default: true)
attribute(:log_level, kind_of: Symbol, equal_to: [:low, :medium, :high, :full], default: :low)
attribute(:rules, kind_of: Hash)
# for firewalld, specify the zone when firewall is disable and enabled
attribute(:disabled_zone, kind_of: Symbol, default: :public)
attribute(:enabled_zone, kind_of: Symbol, default: :drop)
# for firewall implementations where ipv6 can be skipped (currently iptables-specific)
attribute(:ipv6_enabled, kind_of: [TrueClass, FalseClass], default: true)
end
end

View File

@@ -1,36 +1,53 @@
require 'poise'
require 'ipaddr'
class Chef
class Resource::FirewallRule < Resource
include Poise(Chef::Resource::Firewall)
class Resource::FirewallRule < Chef::Resource::LWRPBase
include FirewallCookbook::Helpers
actions(:reject, :allow, :deny, :masquerade, :redirect, :log, :remove)
resource_name(:firewall_rule)
provides(:firewall_rule)
actions(:create)
default_action(:create)
attribute(:protocol, :kind_of => [Symbol, String], :equal_to => [:udp, :tcp, :icmp, 'tcp', 'udp', 'icmp'], :default => :tcp)
attribute(:direction, :kind_of => [Symbol, String], :equal_to => [:in, :out, :pre, :post, 'in', 'out', 'pre', 'post'], :default => :in)
attribute(:logging, :kind_of => [Symbol, String], :equal_to => [:connections, :packets, 'connections', 'packets'])
attribute(:firewall_name, kind_of: String, default: 'default')
attribute(:source, :callbacks => { 'must be a valid ip address' => ->(s) { valid_ip?(s) } })
attribute(:source_port, :kind_of => [Integer, Array, Range]) # source port
attribute(:interface, :kind_of => String)
attribute(:command, kind_of: Symbol, equal_to: [:reject, :allow, :deny, :masquerade, :redirect, :log], default: :allow)
attribute(:port, :kind_of => [Integer, Array, Range]) # shorthand for dest_port
attribute(:destination, :callbacks => { 'must be a valid ip address' => ->(s) { valid_ip?(s) } })
attribute(:dest_port, :kind_of => [Integer, Array, Range])
attribute(:dest_interface, :kind_of => String)
attribute(:protocol, kind_of: [Integer, Symbol], default: :tcp,
callbacks: { 'must be either :tcp, :udp, :icmp, :\'ipv6-icmp\', :icmpv6, :none, or a valid IP protocol number' => lambda do |p|
!!(p.to_s =~ /(udp|tcp|icmp|icmpv6|ipv6-icmp|none)/ || (p.to_s =~ /^\d+$/ && p.between?(0, 142)))
end
}
)
attribute(:direction, kind_of: Symbol, equal_to: [:in, :out, :pre, :post], default: :in)
attribute(:logging, kind_of: Symbol, equal_to: [:connections, :packets])
attribute(:position, :kind_of => Integer)
attribute(:stateful, :kind_of => [Symbol, String, Array])
attribute(:redirect_port, :kind_of => Integer)
attribute(:description, :kind_of => String, :name_attribute => true)
attribute(:source, callbacks: { 'must be a valid ip address' => ->(ip) { !!IPAddr.new(ip) } })
attribute(:source_port, kind_of: [Integer, Array, Range]) # source port
attribute(:interface, kind_of: String)
attribute(:port, kind_of: [Integer, Array, Range]) # shorthand for dest_port
attribute(:destination, callbacks: { 'must be a valid ip address' => ->(ip) { !!IPAddr.new(ip) } })
attribute(:dest_port, kind_of: [Integer, Array, Range])
attribute(:dest_interface, kind_of: String)
attribute(:position, kind_of: Integer, default: 50)
attribute(:stateful, kind_of: [Symbol, Array])
attribute(:redirect_port, kind_of: Integer)
attribute(:description, kind_of: String, name_attribute: true)
# only used for firewalld
attribute(:permanent, kind_of: [TrueClass, FalseClass], default: false)
# only used for Windows Firewalls
attribute(:program, kind_of: String)
attribute(:service, kind_of: String)
# for when you just want to pass a raw rule
attribute(:raw, :kind_of => String)
attribute(:raw, kind_of: String)
def self.valid_ip?(ip)
IPAddr.new(ip) ? true : false
rescue
false
end
# do you want this rule to notify the firewall to recalculate
# (and potentially reapply) the firewall_rule(s) it finds?
attribute(:notify_firewall, kind_of: [TrueClass, FalseClass], default: true)
end
end

View File

@@ -1,22 +0,0 @@
# provider mappings for Chef 11
# https://www.chef.io/blog/2015/02/10/chef-12-provider-resolver/
#########
# firewall
#########
Chef::Platform.set platform: :centos, version: '< 7.0', resource: :firewall, provider: Chef::Provider::FirewallIptables
Chef::Platform.set platform: :centos, version: '>= 7.0', resource: :firewall, provider: Chef::Provider::FirewallFirewalld
Chef::Platform.set platform: :redhat, version: '< 7.0', resource: :firewall, provider: Chef::Provider::FirewallIptables
Chef::Platform.set platform: :redhat, version: '>= 7.0', resource: :firewall, provider: Chef::Provider::FirewallFirewalld
Chef::Platform.set platform: :debian, resource: :firewall, provider: Chef::Provider::FirewallUfw
Chef::Platform.set platform: :ubuntu, resource: :firewall, provider: Chef::Provider::FirewallUfw
#########
# firewall_rule
#########
Chef::Platform.set platform: :centos, version: '< 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleIptables
Chef::Platform.set platform: :centos, version: '>= 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleFirewalld
Chef::Platform.set platform: :redhat, version: '< 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleIptables
Chef::Platform.set platform: :redhat, version: '>= 7.0', resource: :firewall_rule, provider: Chef::Provider::FirewallRuleFirewalld
Chef::Platform.set platform: :debian, resource: :firewall_rule, provider: Chef::Provider::FirewallRuleUfw
Chef::Platform.set platform: :ubuntu, resource: :firewall_rule, provider: Chef::Provider::FirewallRuleUfw