Added puppetlabs-firewall (required by puppetlabs-postgresql), updated the other modules.

This commit is contained in:
Ciaby 2014-07-11 14:51:15 -05:00
parent 5f4b7a3b72
commit dee66abcdd
137 changed files with 11118 additions and 419 deletions

View file

@ -0,0 +1,11 @@
Facter.add(:ip6tables_version) do
confine :kernel => :linux
setcode do
version = Facter::Util::Resolution.exec('ip6tables --version')
if version
version.match(/\d+\.\d+\.\d+/).to_s
else
nil
end
end
end

View file

@ -0,0 +1,15 @@
Facter.add(:iptables_persistent_version) do
confine :operatingsystem => %w{Debian Ubuntu}
setcode do
# Throw away STDERR because dpkg >= 1.16.7 will make some noise if the
# package isn't currently installed.
cmd = "dpkg-query -Wf '${Version}' iptables-persistent 2>/dev/null"
version = Facter::Util::Resolution.exec(cmd)
if version.nil? or !version.match(/\d+\.\d+/)
nil
else
version
end
end
end

View file

@ -0,0 +1,11 @@
Facter.add(:iptables_version) do
confine :kernel => :linux
setcode do
version = Facter::Util::Resolution.exec('iptables --version')
if version
version.match(/\d+\.\d+\.\d+/).to_s
else
nil
end
end
end

View file

@ -0,0 +1,34 @@
class Puppet::Provider::Firewall < Puppet::Provider
# Prefetch our rule list. This is ran once every time before any other
# action (besides initialization of each object).
def self.prefetch(resources)
debug("[prefetch(resources)]")
instances.each do |prov|
if resource = resources[prov.name] || resources[prov.name.downcase]
resource.provider = prov
end
end
end
# Look up the current status. This allows us to conventiently look up
# existing status with properties[:foo].
def properties
if @property_hash.empty?
@property_hash = query || {:ensure => :absent}
@property_hash[:ensure] = :absent if @property_hash.empty?
end
@property_hash.dup
end
# Pull the current state of the list from the full list. We're
# getting some double entendre here....
def query
self.class.instances.each do |instance|
if instance.name == self.name or instance.name.downcase == self.name
return instance.properties
end
end
nil
end
end

View file

@ -0,0 +1,136 @@
Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source => :iptables do
@doc = "Ip6tables type provider"
has_feature :iptables
has_feature :connection_limiting
has_feature :hop_limiting
has_feature :rate_limiting
has_feature :recent_limiting
has_feature :snat
has_feature :dnat
has_feature :interface_match
has_feature :icmp_match
has_feature :owner
has_feature :state_match
has_feature :reject_type
has_feature :log_level
has_feature :log_prefix
has_feature :mark
has_feature :tcp_flags
has_feature :pkttype
has_feature :ishasmorefrags
has_feature :islastfrag
has_feature :isfirstfrag
optional_commands({
:ip6tables => 'ip6tables',
:ip6tables_save => 'ip6tables-save',
})
def initialize(*args)
if Facter.fact('ip6tables_version').value.match /1\.3\.\d/
raise ArgumentError, 'The ip6tables provider is not supported on version 1.3 of iptables'
else
super
end
end
def self.iptables(*args)
ip6tables(*args)
end
def self.iptables_save(*args)
ip6tables_save(*args)
end
@protocol = "IPv6"
@resource_map = {
:burst => "--limit-burst",
:connlimit_above => "-m connlimit --connlimit-above",
:connlimit_mask => "--connlimit-mask",
:connmark => "-m connmark --mark",
:ctstate => "-m conntrack --ctstate",
:destination => "-d",
:dport => "-m multiport --dports",
:gid => "-m owner --gid-owner",
:icmp => "-m icmp6 --icmpv6-type",
:iniface => "-i",
:jump => "-j",
:hop_limit => "-m hl --hl-eq",
:limit => "-m limit --limit",
:log_level => "--log-level",
:log_prefix => "--log-prefix",
:name => "-m comment --comment",
:outiface => "-o",
:port => '-m multiport --ports',
:proto => "-p",
:rdest => "--rdest",
:reap => "--reap",
:recent => "-m recent",
:reject => "--reject-with",
:rhitcount => "--hitcount",
:rname => "--name",
:rseconds => "--seconds",
:rsource => "--rsource",
:rttl => "--rttl",
:source => "-s",
:state => "-m state --state",
:sport => "-m multiport --sports",
:table => "-t",
:todest => "--to-destination",
:toports => "--to-ports",
:tosource => "--to-source",
:uid => "-m owner --uid-owner",
:pkttype => "-m pkttype --pkt-type",
:ishasmorefrags => "-m frag --fragid 0 --fragmore",
:islastfrag => "-m frag --fragid 0 --fraglast",
:isfirstfrag => "-m frag --fragid 0 --fragfirst",
}
# These are known booleans that do not take a value, but we want to munge
# to true if they exist.
@known_booleans = [:ishasmorefrags, :islastfrag, :isfirstfrag, :rsource, :rdest, :reap, :rttl]
# Create property methods dynamically
(@resource_map.keys << :chain << :table << :action).each do |property|
if @known_booleans.include?(property) then
# The boolean properties default to '' which should be read as false
define_method "#{property}" do
@property_hash[property] = :false if @property_hash[property] == nil
@property_hash[property.to_sym]
end
else
define_method "#{property}" do
@property_hash[property.to_sym]
end
end
if property == :chain
define_method "#{property}=" do |value|
if @property_hash[:chain] != value
raise ArgumentError, "Modifying the chain for existing rules is not supported."
end
end
else
define_method "#{property}=" do |value|
@property_hash[:needs_change] = true
end
end
end
# This is the order of resources as they appear in iptables-save output,
# we need it to properly parse and apply rules, if the order of resource
# changes between puppet runs, the changed rules will be re-applied again.
# This order can be determined by going through iptables source code or just tweaking and trying manually
# (Note: on my CentOS 6.4 ip6tables-save returns -m frag on the place
# I put it when calling the command. So compability with manual changes
# not provided with current parser [georg.koester])
@resource_list = [:table, :source, :destination, :iniface, :outiface,
:proto, :ishasmorefrags, :islastfrag, :isfirstfrag, :gid, :uid, :sport, :dport,
:port, :pkttype, :name, :state, :ctstate, :icmp, :hop_limit, :limit, :burst,
:recent, :rseconds, :reap, :rhitcount, :rttl, :rname, :rsource, :rdest,
:jump, :todest, :tosource, :toports, :log_level, :log_prefix, :reject,
:connlimit_above, :connlimit_mask, :connmark]
end

View file

@ -0,0 +1,501 @@
require 'puppet/provider/firewall'
require 'digest/md5'
Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Firewall do
include Puppet::Util::Firewall
@doc = "Iptables type provider"
has_feature :iptables
has_feature :connection_limiting
has_feature :rate_limiting
has_feature :recent_limiting
has_feature :snat
has_feature :dnat
has_feature :interface_match
has_feature :icmp_match
has_feature :owner
has_feature :state_match
has_feature :reject_type
has_feature :log_level
has_feature :log_prefix
has_feature :mark
has_feature :tcp_flags
has_feature :pkttype
has_feature :isfragment
has_feature :socket
has_feature :address_type
has_feature :iprange
has_feature :ipsec_dir
has_feature :ipsec_policy
has_feature :mask
optional_commands({
:iptables => 'iptables',
:iptables_save => 'iptables-save',
})
defaultfor :kernel => :linux
iptables_version = Facter.fact('iptables_version').value
if (iptables_version and Puppet::Util::Package.versioncmp(iptables_version, '1.4.1') < 0)
mark_flag = '--set-mark'
else
mark_flag = '--set-xmark'
end
@protocol = "IPv4"
@resource_map = {
:burst => "--limit-burst",
:connlimit_above => "-m connlimit --connlimit-above",
:connlimit_mask => "--connlimit-mask",
:connmark => "-m connmark --mark",
:ctstate => "-m conntrack --ctstate",
:destination => "-d",
:dst_type => "-m addrtype --dst-type",
:dst_range => "-m iprange --dst-range",
:dport => ["-m multiport --dports", "--dport"],
:gid => "-m owner --gid-owner",
:icmp => "-m icmp --icmp-type",
:iniface => "-i",
:jump => "-j",
:limit => "-m limit --limit",
:log_level => "--log-level",
:log_prefix => "--log-prefix",
:name => "-m comment --comment",
:outiface => "-o",
:port => '-m multiport --ports',
:proto => "-p",
:random => "--random",
:rdest => "--rdest",
:reap => "--reap",
:recent => "-m recent",
:reject => "--reject-with",
:rhitcount => "--hitcount",
:rname => "--name",
:rseconds => "--seconds",
:rsource => "--rsource",
:rttl => "--rttl",
:set_mark => mark_flag,
:socket => "-m socket",
:source => "-s",
:src_type => "-m addrtype --src-type",
:src_range => "-m iprange --src-range",
:sport => ["-m multiport --sports", "--sport"],
:state => "-m state --state",
:table => "-t",
:tcp_flags => "-m tcp --tcp-flags",
:todest => "--to-destination",
:toports => "--to-ports",
:tosource => "--to-source",
:uid => "-m owner --uid-owner",
:pkttype => "-m pkttype --pkt-type",
:isfragment => "-f",
:ipsec_dir => "-m policy --dir",
:ipsec_policy => "--pol",
:mask => '--mask',
}
# These are known booleans that do not take a value, but we want to munge
# to true if they exist.
@known_booleans = [
:isfragment,
:random,
:rdest,
:reap,
:rsource,
:rttl,
:socket
]
# Create property methods dynamically
(@resource_map.keys << :chain << :table << :action).each do |property|
if @known_booleans.include?(property) then
# The boolean properties default to '' which should be read as false
define_method "#{property}" do
@property_hash[property] = :false if @property_hash[property] == nil
@property_hash[property.to_sym]
end
else
define_method "#{property}" do
@property_hash[property.to_sym]
end
end
if property == :chain
define_method "#{property}=" do |value|
if @property_hash[:chain] != value
raise ArgumentError, "Modifying the chain for existing rules is not supported."
end
end
else
define_method "#{property}=" do |value|
@property_hash[:needs_change] = true
end
end
end
# This is the order of resources as they appear in iptables-save output,
# we need it to properly parse and apply rules, if the order of resource
# changes between puppet runs, the changed rules will be re-applied again.
# This order can be determined by going through iptables source code or just tweaking and trying manually
@resource_list = [
:table, :source, :destination, :iniface, :outiface, :proto, :isfragment,
:src_range, :dst_range, :tcp_flags, :gid, :uid, :sport, :dport, :port,
:dst_type, :src_type, :socket, :pkttype, :name, :ipsec_dir, :ipsec_policy,
:state, :ctstate, :icmp, :limit, :burst, :recent, :rseconds, :reap,
:rhitcount, :rttl, :rname, :mask, :rsource, :rdest, :jump, :todest,
:tosource, :toports, :random, :log_prefix, :log_level, :reject, :set_mark,
:connlimit_above, :connlimit_mask, :connmark
]
def insert
debug 'Inserting rule %s' % resource[:name]
iptables insert_args
end
def update
debug 'Updating rule %s' % resource[:name]
iptables update_args
end
def delete
debug 'Deleting rule %s' % resource[:name]
iptables delete_args
end
def exists?
properties[:ensure] != :absent
end
# Flush the property hash once done.
def flush
debug("[flush]")
if @property_hash.delete(:needs_change)
notice("Properties changed - updating rule")
update
end
persist_iptables(self.class.instance_variable_get(:@protocol))
@property_hash.clear
end
def self.instances
debug "[instances]"
table = nil
rules = []
counter = 1
# String#lines would be nice, but we need to support Ruby 1.8.5
iptables_save.split("\n").each do |line|
unless line =~ /^\#\s+|^\:\S+|^COMMIT|^FATAL/
if line =~ /^\*/
table = line.sub(/\*/, "")
else
if hash = rule_to_hash(line, table, counter)
rules << new(hash)
counter += 1
end
end
end
end
rules
end
def self.rule_to_hash(line, table, counter)
hash = {}
keys = []
values = line.dup
####################
# PRE-PARSE CLUDGING
####################
# --tcp-flags takes two values; we cheat by adding " around it
# so it behaves like --comment
values = values.sub(/--tcp-flags (\S*) (\S*)/, '--tcp-flags "\1 \2"')
# we do a similar thing for negated address masks (source and destination).
values = values.sub(/(-\S+) (!)\s?(\S*)/,'\1 "\2 \3"')
# the actual rule will have the ! mark before the option.
values = values.sub(/(!)\s*(-\S+)\s*(\S*)/, '\2 "\1 \3"')
# The match extension for tcp & udp are optional and throws off the @resource_map.
values = values.sub(/-m (tcp|udp) (--(s|d)port|-m multiport)/, '\2')
# Trick the system for booleans
@known_booleans.each do |bool|
# append "true" because all params are expected to have values
if bool == :isfragment then
# -f requires special matching:
# only replace those -f that are not followed by an l to
# distinguish between -f and the '-f' inside of --tcp-flags.
values = values.sub(/-f(?!l)(?=.*--comment)/, '-f true')
else
values = values.sub(/#{@resource_map[bool]}/, "#{@resource_map[bool]} true")
end
end
############
# Populate parser_list with used value, in the correct order
############
map_index={}
@resource_map.each_pair do |map_k,map_v|
[map_v].flatten.each do |v|
ind=values.index(/\s#{v}/)
next unless ind
map_index[map_k]=ind
end
end
# Generate parser_list based on the index of the found option
parser_list=[]
map_index.sort_by{|k,v| v}.each{|mapi| parser_list << mapi.first }
############
# MAIN PARSE
############
# Here we iterate across our values to generate an array of keys
parser_list.reverse.each do |k|
resource_map_key = @resource_map[k]
[resource_map_key].flatten.each do |opt|
if values.slice!(/\s#{opt}/)
keys << k
break
end
end
end
# Manually remove chain
values.slice!('-A')
keys << :chain
# Here we generate the main hash
keys.zip(values.scan(/"[^"]*"|\S+/).reverse) { |f, v| hash[f] = v.gsub(/"/, '') }
#####################
# POST PARSE CLUDGING
#####################
# Normalise all rules to CIDR notation.
[:source, :destination].each do |prop|
next if hash[prop].nil?
m = hash[prop].match(/(!?)\s?(.*)/)
neg = "! " if m[1] == "!"
hash[prop] = "#{neg}#{Puppet::Util::IPCidr.new(m[2]).cidr}"
end
[:dport, :sport, :port, :state, :ctstate].each do |prop|
hash[prop] = hash[prop].split(',') if ! hash[prop].nil?
end
# Convert booleans removing the previous cludge we did
@known_booleans.each do |bool|
if hash[bool] != nil then
if hash[bool] != "true" then
raise "Parser error: #{bool} was meant to be a boolean but received value: #{hash[bool]}."
end
end
end
# Our type prefers hyphens over colons for ranges so ...
# Iterate across all ports replacing colons with hyphens so that ranges match
# the types expectations.
[:dport, :sport, :port].each do |prop|
next unless hash[prop]
hash[prop] = hash[prop].collect do |elem|
elem.gsub(/:/,'-')
end
end
# States should always be sorted. This ensures that the output from
# iptables-save and user supplied resources is consistent.
hash[:state] = hash[:state].sort unless hash[:state].nil?
hash[:ctstate] = hash[:ctstate].sort unless hash[:ctstate].nil?
# This forces all existing, commentless rules or rules with invalid comments to be moved
# to the bottom of the stack.
# Puppet-firewall requires that all rules have comments (resource names) and match this
# regex and will fail if a rule in iptables does not have a comment. We get around this
# by appending a high level
if ! hash[:name]
num = 9000 + counter
hash[:name] = "#{num} #{Digest::MD5.hexdigest(line)}"
elsif not /^\d+[[:alpha:][:digit:][:punct:][:space:]]+$/ =~ hash[:name]
num = 9000 + counter
hash[:name] = "#{num} #{/([[:alpha:][:digit:][:punct:][:space:]]+)/.match(hash[:name])[1]}"
end
# Iptables defaults to log_level '4', so it is omitted from the output of iptables-save.
# If the :jump value is LOG and you don't have a log-level set, we assume it to be '4'.
if hash[:jump] == 'LOG' && ! hash[:log_level]
hash[:log_level] = '4'
end
# Iptables defaults to burst '5', so it is ommitted from the output of iptables-save.
# If the :limit value is set and you don't have a burst set, we assume it to be '5'.
if hash[:limit] && ! hash[:burst]
hash[:burst] = '5'
end
hash[:line] = line
hash[:provider] = self.name.to_s
hash[:table] = table
hash[:ensure] = :present
# Munge some vars here ...
# Proto should equal 'all' if undefined
hash[:proto] = "all" if !hash.include?(:proto)
# If the jump parameter is set to one of: ACCEPT, REJECT or DROP then
# we should set the action parameter instead.
if ['ACCEPT','REJECT','DROP'].include?(hash[:jump]) then
hash[:action] = hash[:jump].downcase
hash.delete(:jump)
end
hash
end
def insert_args
args = []
args << ["-I", resource[:chain], insert_order]
args << general_args
args
end
def update_args
args = []
args << ["-R", resource[:chain], insert_order]
args << general_args
args
end
def delete_args
# Split into arguments
line = properties[:line].gsub(/\-A/, '-D').split(/\s(?=(?:[^"]|"[^"]*")*$)/).map{|v| v.gsub(/"/, '')}
line.unshift("-t", properties[:table])
end
# This method takes the resource, and attempts to generate the command line
# arguments for iptables.
def general_args
debug "Current resource: %s" % resource.class
args = []
resource_list = self.class.instance_variable_get('@resource_list')
resource_map = self.class.instance_variable_get('@resource_map')
known_booleans = self.class.instance_variable_get('@known_booleans')
resource_list.each do |res|
resource_value = nil
if (resource[res]) then
resource_value = resource[res]
# If socket is true then do not add the value as -m socket is standalone
if known_booleans.include?(res) then
if resource[res] == :true then
resource_value = nil
else
# If the property is not :true then we don't want to add the value
# to the args list
next
end
end
elsif res == :jump and resource[:action] then
# In this case, we are substituting jump for action
resource_value = resource[:action].to_s.upcase
else
next
end
args << [resource_map[res]].flatten.first.split(' ')
# On negations, the '!' has to be before the option (eg: "! -d 1.2.3.4")
if resource_value.is_a?(String) and resource_value.sub!(/^!\s*/, '') then
# we do this after adding the 'dash' argument because of ones like "-m multiport --dports", where we want it before the "--dports" but after "-m multiport".
# so we insert before whatever the last argument is
args.insert(-2, '!')
end
# For sport and dport, convert hyphens to colons since the type
# expects hyphens for ranges of ports.
if [:sport, :dport, :port].include?(res) then
resource_value = resource_value.collect do |elem|
elem.gsub(/-/, ':')
end
end
# our tcp_flags takes a single string with comma lists separated
# by space
# --tcp-flags expects two arguments
if res == :tcp_flags
one, two = resource_value.split(' ')
args << one
args << two
elsif resource_value.is_a?(Array)
args << resource_value.join(',')
elsif !resource_value.nil?
args << resource_value
end
end
args
end
def insert_order
debug("[insert_order]")
rules = []
# Find list of current rules based on chain and table
self.class.instances.each do |rule|
if rule.chain == resource[:chain].to_s and rule.table == resource[:table].to_s
rules << rule.name
end
end
# No rules at all? Just bail now.
return 1 if rules.empty?
# Add our rule to the end of the array of known rules
my_rule = resource[:name].to_s
rules << my_rule
unmanaged_rule_regex = /^9[0-9]{3}\s[a-f0-9]{32}$/
# Find if this is a new rule or an existing rule, then find how many
# unmanaged rules preceed it.
if rules.length == rules.uniq.length
# This is a new rule so find its ordered location.
new_rule_location = rules.sort.uniq.index(my_rule)
if new_rule_location == 0
# The rule will be the first rule in the chain because nothing came
# before it.
offset_rule = rules[0]
else
# This rule will come after other managed rules, so find the rule
# immediately preceeding it.
offset_rule = rules.sort.uniq[new_rule_location - 1]
end
else
# This is a pre-existing rule, so find the offset from the original
# ordering.
offset_rule = my_rule
end
# Count how many unmanaged rules are ahead of the target rule so we know
# how much to add to the insert order
unnamed_offset = rules[0..rules.index(offset_rule)].inject(0) do |sum,rule|
# This regex matches the names given to unmanaged rules (a number
# 9000-9999 followed by an MD5 hash).
sum + (rule.match(unmanaged_rule_regex) ? 1 : 0)
end
# We want our rule to come before unmanaged rules if it's not a 9-rule
if offset_rule.match(unmanaged_rule_regex) and ! my_rule.match(/^9/)
unnamed_offset -= 1
end
# Insert our new or updated rule in the correct order of named rules, but
# offset for unnamed rules.
rules.reject{|r|r.match(unmanaged_rule_regex)}.sort.index(my_rule) + 1 + unnamed_offset
end
end

View file

@ -0,0 +1,178 @@
Puppet::Type.type(:firewallchain).provide :iptables_chain do
include Puppet::Util::Firewall
@doc = "Iptables chain provider"
has_feature :iptables_chain
has_feature :policy
optional_commands({
:iptables => 'iptables',
:iptables_save => 'iptables-save',
:ip6tables => 'ip6tables',
:ip6tables_save => 'ip6tables-save',
:ebtables => 'ebtables',
:ebtables_save => 'ebtables-save',
})
defaultfor :kernel => :linux
# chain name is greedy so we anchor from the end.
# [\d+:\d+] doesn't exist on ebtables
Mapping = {
:IPv4 => {
:tables => method(:iptables),
:save => method(:iptables_save),
:re => /^:(.+)\s(\S+)\s\[\d+:\d+\]$/,
},
:IPv6 => {
:tables => method(:ip6tables),
:save => method(:ip6tables_save),
:re => /^:(.+)\s(\S+)\s\[\d+:\d+\]$/,
},
:ethernet => {
:tables => method(:ebtables),
:save => method(:ebtables_save),
:re => /^:(.+)\s(\S+)$/,
}
}
InternalChains = /^(PREROUTING|POSTROUTING|BROUTING|INPUT|FORWARD|OUTPUT)$/
Tables = 'nat|mangle|filter|raw|rawpost|broute'
Nameformat = /^(.+):(#{Tables}):(IP(v[46])?|ethernet)$/
def create
allvalidchains do |t, chain, table, protocol|
if chain =~ InternalChains
# can't create internal chains
warning "Attempting to create internal chain #{@resource[:name]}"
end
if properties[:ensure] == protocol
debug "Skipping Inserting chain #{chain} on table #{table} (#{protocol}) already exists"
else
debug "Inserting chain #{chain} on table #{table} (#{protocol}) using #{t}"
t.call ['-t',table,'-N',chain]
unless @resource[:policy].nil?
t.call ['-t',table,'-P',chain,@resource[:policy].to_s.upcase]
end
end
end
end
def destroy
allvalidchains do |t, chain, table|
if chain =~ InternalChains
# can't delete internal chains
warning "Attempting to destroy internal chain #{@resource[:name]}"
end
debug "Deleting chain #{chain} on table #{table}"
t.call ['-t',table,'-X',chain]
end
end
def exists?
allvalidchains do |t, chain|
if chain =~ InternalChains
# If the chain isn't present, it's likely because the module isn't loaded.
# If this is true, then we fall into 2 cases
# 1) It'll be loaded on demand
# 2) It won't be loaded on demand, and we throw an error
# This is the intended behavior as it's not the provider's job to load kernel modules
# So we pretend it exists...
return true
end
end
properties[:ensure] == :present
end
def policy=(value)
return if value == :empty
allvalidchains do |t, chain, table|
p = ['-t',table,'-P',chain,value.to_s.upcase]
debug "[set policy] #{t} #{p}"
t.call p
end
end
def policy
debug "[get policy] #{@resource[:name]} =#{@property_hash[:policy].to_s.downcase}"
return @property_hash[:policy].to_s.downcase
end
def self.prefetch(resources)
debug("[prefetch(resources)]")
instances.each do |prov|
if resource = resources[prov.name]
resource.provider = prov
end
end
end
def flush
debug("[flush]")
persist_iptables(@resource[:name].match(Nameformat)[3])
# Clear the property hash so we re-initialize with updated values
@property_hash.clear
end
# Look up the current status. This allows us to conventiently look up
# existing status with properties[:foo].
def properties
if @property_hash.empty?
@property_hash = query || {:ensure => :absent}
end
@property_hash.dup
end
# Pull the current state of the list from the full list.
def query
self.class.instances.each do |instance|
if instance.name == self.name
debug "query found #{self.name}" % instance.properties.inspect
return instance.properties
end
end
nil
end
def self.instances
debug "[instances]"
table = nil
chains = []
Mapping.each { |p, c|
begin
c[:save].call.each_line do |line|
if line =~ c[:re] then
name = $1 + ':' + (table == 'filter' ? 'filter' : table) + ':' + p.to_s
policy = $2 == '-' ? nil : $2.downcase.to_sym
chains << new({
:name => name,
:policy => policy,
:ensure => :present,
})
debug "[instance] '#{name}' #{policy}"
elsif line =~ /^\*(\S+)/
table = $1
else
next
end
end
rescue Puppet::Error
# ignore command not found for ebtables or anything that doesn't exist
end
}
chains
end
def allvalidchains
@resource[:name].match(Nameformat)
chain = $1
table = $2
protocol = $3
yield Mapping[protocol.to_sym][:tables],chain,table,protocol.to_sym
end
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,222 @@
# This is a workaround for bug: #4248 whereby ruby files outside of the normal
# provider/type path do not load until pluginsync has occured on the puppetmaster
#
# In this case I'm trying the relative path first, then falling back to normal
# mechanisms. This should be fixed in future versions of puppet but it looks
# like we'll need to maintain this for some time perhaps.
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__),"..",".."))
require 'puppet/util/firewall'
Puppet::Type.newtype(:firewallchain) do
include Puppet::Util::Firewall
@doc = <<-EOS
This type provides the capability to manage rule chains for firewalls.
Currently this supports only iptables, ip6tables and ebtables on Linux. And
provides support for setting the default policy on chains and tables that
allow it.
**Autorequires:**
If Puppet is managing the iptables or iptables-persistent packages, and
the provider is iptables_chain, the firewall resource will autorequire
those packages to ensure that any required binaries are installed.
EOS
feature :iptables_chain, "The provider provides iptables chain features."
feature :policy, "Default policy (inbuilt chains only)"
ensurable do
defaultvalues
defaultto :present
end
newparam(:name) do
desc <<-EOS
The canonical name of the chain.
For iptables the format must be {chain}:{table}:{protocol}.
EOS
isnamevar
validate do |value|
if value !~ Nameformat then
raise ArgumentError, "Inbuilt chains must be in the form {chain}:{table}:{protocol} where {table} is one of FILTER, NAT, MANGLE, RAW, RAWPOST, BROUTE or empty (alias for filter), chain can be anything without colons or one of PREROUTING, POSTROUTING, BROUTING, INPUT, FORWARD, OUTPUT for the inbuilt chains, and {protocol} being IPv4, IPv6, ethernet (ethernet bridging) got '#{value}' table:'#{$1}' chain:'#{$2}' protocol:'#{$3}'"
else
chain = $1
table = $2
protocol = $3
case table
when 'filter'
if chain =~ /^(PREROUTING|POSTROUTING|BROUTING)$/
raise ArgumentError, "INPUT, OUTPUT and FORWARD are the only inbuilt chains that can be used in table 'filter'"
end
when 'mangle'
if chain =~ InternalChains && chain == 'BROUTING'
raise ArgumentError, "PREROUTING, POSTROUTING, INPUT, FORWARD and OUTPUT are the only inbuilt chains that can be used in table 'mangle'"
end
when 'nat'
if chain =~ /^(BROUTING|FORWARD)$/
raise ArgumentError, "PREROUTING, POSTROUTING, INPUT, and OUTPUT are the only inbuilt chains that can be used in table 'nat'"
end
if protocol =~/^(IP(v6)?)?$/
raise ArgumentError, "table nat isn't valid in IPv6. You must specify ':IPv4' as the name suffix"
end
when 'raw'
if chain =~ /^(POSTROUTING|BROUTING|INPUT|FORWARD)$/
raise ArgumentError,'PREROUTING and OUTPUT are the only inbuilt chains in the table \'raw\''
end
when 'broute'
if protocol != 'ethernet'
raise ArgumentError,'BROUTE is only valid with protocol \'ethernet\''
end
if chain =~ /^PREROUTING|POSTROUTING|INPUT|FORWARD|OUTPUT$/
raise ArgumentError,'BROUTING is the only inbuilt chain allowed on on table \'broute\''
end
end
if chain == 'BROUTING' && ( protocol != 'ethernet' || table!='broute')
raise ArgumentError,'BROUTING is the only inbuilt chain allowed on on table \'BROUTE\' with protocol \'ethernet\' i.e. \'broute:BROUTING:enternet\''
end
end
end
end
newproperty(:policy) do
desc <<-EOS
This is the action to when the end of the chain is reached.
It can only be set on inbuilt chains (INPUT, FORWARD, OUTPUT,
PREROUTING, POSTROUTING) and can be one of:
* accept - the packet is accepted
* drop - the packet is dropped
* queue - the packet is passed userspace
* return - the packet is returned to calling (jump) queue
or the default of inbuilt chains
EOS
newvalues(:accept, :drop, :queue, :return)
defaultto do
# ethernet chain have an ACCEPT default while other haven't got an
# allowed value
if @resource[:name] =~ /:ethernet$/
:accept
else
nil
end
end
end
newparam(:purge, :boolean => true) do
desc <<-EOS
Purge unmanaged firewall rules in this chain
EOS
newvalues(:false, :true)
defaultto :false
end
newparam(:ignore) do
desc <<-EOS
Regex to perform on firewall rules to exempt unmanaged rules from purging (when enabled).
This is matched against the output of `iptables-save`.
This can be a single regex, or an array of them.
To support flags, use the ruby inline flag mechanism.
Meaning a regex such as
/foo/i
can be written as
'(?i)foo' or '(?i:foo)'
Full example:
firewallchain { 'INPUT:filter:IPv4':
purge => true,
ignore => [
'-j fail2ban-ssh', # ignore the fail2ban jump rule
'--comment "[^"]*(?i:ignore)[^"]*"', # ignore any rules with "ignore" (case insensitive) in the comment in the rule
],
}
EOS
validate do |value|
unless value.is_a?(Array) or value.is_a?(String) or value == false
self.devfail "Ignore must be a string or an Array"
end
end
munge do |patterns| # convert into an array of {Regex}es
patterns = [patterns] if patterns.is_a?(String)
patterns.map{|p| Regexp.new(p)}
end
end
# Classes would be a better abstraction, pending:
# http://projects.puppetlabs.com/issues/19001
autorequire(:package) do
case value(:provider)
when :iptables_chain
%w{iptables iptables-persistent}
else
[]
end
end
validate do
debug("[validate]")
value(:name).match(Nameformat)
chain = $1
table = $2
protocol = $3
# Check that we're not removing an internal chain
if chain =~ InternalChains && value(:ensure) == :absent
self.fail "Cannot remove in-built chains"
end
if value(:policy).nil? && protocol == 'ethernet'
self.fail "you must set a non-empty policy on all ethernet table chains"
end
# Check that we're not setting a policy on a user chain
if chain !~ InternalChains &&
!value(:policy).nil? &&
protocol != 'ethernet'
self.fail "policy can only be set on in-built chains (with the exception of ethernet chains) (table:#{table} chain:#{chain} protocol:#{protocol})"
end
# no DROP policy on nat table
if table == 'nat' &&
value(:policy) == :drop
self.fail 'The "nat" table is not intended for filtering, the use of DROP is therefore inhibited'
end
end
def generate
return [] unless self.purge?
value(:name).match(Nameformat)
chain = $1
table = $2
protocol = $3
provider = case protocol
when 'IPv4'
:iptables
when 'IPv6'
:ip6tables
end
# gather a list of all rules present on the system
rules_resources = Puppet::Type.type(:firewall).instances
# Keep only rules in this chain
rules_resources.delete_if { |res| (res[:provider] != provider or res.provider.properties[:table].to_s != table or res.provider.properties[:chain] != chain) }
# Remove rules which match our ignore filter
rules_resources.delete_if {|res| value(:ignore).find_index{|f| res.provider.properties[:line].match(f)}} if value(:ignore)
# We mark all remaining rules for deletion, and then let the catalog override us on rules which should be present
rules_resources.each {|res| res[:ensure] = :absent}
rules_resources
end
end

View file

@ -0,0 +1,225 @@
require 'socket'
require 'resolv'
require 'puppet/util/ipcidr'
# Util module for puppetlabs-firewall
module Puppet::Util::Firewall
# Translate the symbolic names for icmp packet types to integers
def icmp_name_to_number(value_icmp, protocol)
if value_icmp =~ /\d{1,2}$/
value_icmp
elsif protocol == 'inet'
case value_icmp
when "echo-reply" then "0"
when "destination-unreachable" then "3"
when "source-quench" then "4"
when "redirect" then "6"
when "echo-request" then "8"
when "router-advertisement" then "9"
when "router-solicitation" then "10"
when "time-exceeded" then "11"
when "parameter-problem" then "12"
when "timestamp-request" then "13"
when "timestamp-reply" then "14"
when "address-mask-request" then "17"
when "address-mask-reply" then "18"
else nil
end
elsif protocol == 'inet6'
case value_icmp
when "destination-unreachable" then "1"
when "time-exceeded" then "3"
when "parameter-problem" then "4"
when "echo-request" then "128"
when "echo-reply" then "129"
when "router-solicitation" then "133"
when "router-advertisement" then "134"
when "redirect" then "137"
else nil
end
else
raise ArgumentError, "unsupported protocol family '#{protocol}'"
end
end
# Convert log_level names to their respective numbers
def log_level_name_to_number(value)
#TODO make this 0-7 only
if value =~ /\d/
value
else
case value
when "panic" then "0"
when "alert" then "1"
when "crit" then "2"
when "err" then "3"
when "error" then "3"
when "warn" then "4"
when "warning" then "4"
when "not" then "5"
when "notice" then "5"
when "info" then "6"
when "debug" then "7"
else nil
end
end
end
# This method takes a string and a protocol and attempts to convert
# it to a port number if valid.
#
# If the string already contains a port number or perhaps a range of ports
# in the format 22:1000 for example, it simply returns the string and does
# nothing.
def string_to_port(value, proto)
proto = proto.to_s
unless proto =~ /^(tcp|udp)$/
proto = 'tcp'
end
if value.kind_of?(String)
if value.match(/^\d+(-\d+)?$/)
return value
else
return Socket.getservbyname(value, proto).to_s
end
else
Socket.getservbyname(value.to_s, proto).to_s
end
end
# Takes an address and returns it in CIDR notation.
#
# If the address is:
#
# - A hostname:
# It will be resolved
# - An IPv4 address:
# It will be qualified with a /32 CIDR notation
# - An IPv6 address:
# It will be qualified with a /128 CIDR notation
# - An IP address with a CIDR notation:
# It will be normalised
# - An IP address with a dotted-quad netmask:
# It will be converted to CIDR notation
# - Any address with a resulting prefix length of zero:
# It will return nil which is equivilent to not specifying an address
#
def host_to_ip(value)
begin
value = Puppet::Util::IPCidr.new(value)
rescue
value = Puppet::Util::IPCidr.new(Resolv.getaddress(value))
end
return nil if value.prefixlen == 0
value.cidr
end
# Takes an address mask and converts the host portion to CIDR notation.
#
# This takes into account you can negate a mask but follows all rules
# defined in host_to_ip for the host/address part.
#
def host_to_mask(value)
match = value.match /(!)\s?(.*)$/
return host_to_ip(value) unless match
cidr = host_to_ip(match[2])
return nil if cidr == nil
"#{match[1]} #{cidr}"
end
# Validates the argument is int or hex, and returns valid hex
# conversion of the value or nil otherwise.
def to_hex32(value)
begin
value = Integer(value)
if value.between?(0, 0xffffffff)
return '0x' + value.to_s(16)
end
rescue ArgumentError
# pass
end
return nil
end
def persist_iptables(proto)
debug("[persist_iptables]")
# Basic normalisation for older Facter
os_key = Facter.value(:osfamily)
os_key ||= case Facter.value(:operatingsystem)
when 'RedHat', 'CentOS', 'Fedora', 'Scientific', 'SL', 'SLC', 'Ascendos', 'CloudLinux', 'PSBM', 'OracleLinux', 'OVS', 'OEL', 'Amazon', 'XenServer'
'RedHat'
when 'Debian', 'Ubuntu'
'Debian'
else
Facter.value(:operatingsystem)
end
# Older iptables-persistent doesn't provide save action.
if os_key == 'Debian'
persist_ver = Facter.value(:iptables_persistent_version)
if (persist_ver and Puppet::Util::Package.versioncmp(persist_ver, '0.5.0') < 0)
os_key = 'Debian_manual'
end
end
# Fedora 15 and newer use systemd to persist iptable rules
if os_key == 'RedHat' && Facter.value(:operatingsystem) == 'Fedora' && Facter.value(:operatingsystemrelease).to_i >= 15
os_key = 'Fedora'
end
# RHEL 7 and newer also use systemd to persist iptable rules
if os_key == 'RedHat' && Facter.value(:operatingsystem) == 'RedHat' && Facter.value(:operatingsystemrelease).to_i >= 7
os_key = 'Fedora'
end
cmd = case os_key.to_sym
when :RedHat
case proto.to_sym
when :IPv4
%w{/sbin/service iptables save}
when :IPv6
%w{/sbin/service ip6tables save}
end
when :Fedora
case proto.to_sym
when :IPv4
%w{/usr/libexec/iptables/iptables.init save}
when :IPv6
%w{/usr/libexec/iptables/ip6tables.init save}
end
when :Debian
case proto.to_sym
when :IPv4, :IPv6
%w{/usr/sbin/service iptables-persistent save}
end
when :Debian_manual
case proto.to_sym
when :IPv4
["/bin/sh", "-c", "/sbin/iptables-save > /etc/iptables/rules"]
end
when :Archlinux
case proto.to_sym
when :IPv4
["/bin/sh", "-c", "/usr/sbin/iptables-save > /etc/iptables/iptables.rules"]
when :IPv6
["/bin/sh", "-c", "/usr/sbin/ip6tables-save > /etc/iptables/ip6tables.rules"]
end
end
# Catch unsupported OSs from the case statement above.
if cmd.nil?
debug('firewall: Rule persistence is not supported for this type/OS')
return
end
begin
execute(cmd)
rescue Puppet::ExecutionFailure => detail
warning("Unable to persist firewall rules: #{detail}")
end
end
end

View file

@ -0,0 +1,42 @@
require 'ipaddr'
# IPCidr object wrapper for IPAddr
module Puppet
module Util
class IPCidr < IPAddr
def initialize(ipaddr)
begin
super(ipaddr)
rescue ArgumentError => e
if e.message =~ /invalid address/
raise ArgumentError, "Invalid address from IPAddr.new: #{ipaddr}"
else
raise e
end
end
end
def netmask
_to_string(@mask_addr)
end
def prefixlen
m = case @family
when Socket::AF_INET
IN4MASK
when Socket::AF_INET6
IN6MASK
else
raise "unsupported address family"
end
return $1.length if /\A(1*)(0*)\z/ =~ (@mask_addr & m).to_s(2)
raise "bad addr_mask format"
end
def cidr
cidr = sprintf("%s/%s", self.to_s, self.prefixlen)
cidr
end
end
end
end