Added puppetlabs-firewall (required by puppetlabs-postgresql), updated the other modules.
This commit is contained in:
parent
5f4b7a3b72
commit
dee66abcdd
137 changed files with 11118 additions and 419 deletions
11
modules/firewall/lib/facter/ip6tables_version.rb
Normal file
11
modules/firewall/lib/facter/ip6tables_version.rb
Normal 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
|
15
modules/firewall/lib/facter/iptables_persistent_version.rb
Normal file
15
modules/firewall/lib/facter/iptables_persistent_version.rb
Normal 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
|
11
modules/firewall/lib/facter/iptables_version.rb
Normal file
11
modules/firewall/lib/facter/iptables_version.rb
Normal 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
|
34
modules/firewall/lib/puppet/provider/firewall.rb
Normal file
34
modules/firewall/lib/puppet/provider/firewall.rb
Normal 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
|
136
modules/firewall/lib/puppet/provider/firewall/ip6tables.rb
Normal file
136
modules/firewall/lib/puppet/provider/firewall/ip6tables.rb
Normal 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
|
501
modules/firewall/lib/puppet/provider/firewall/iptables.rb
Normal file
501
modules/firewall/lib/puppet/provider/firewall/iptables.rb
Normal 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
|
|
@ -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
|
1077
modules/firewall/lib/puppet/type/firewall.rb
Normal file
1077
modules/firewall/lib/puppet/type/firewall.rb
Normal file
File diff suppressed because it is too large
Load diff
222
modules/firewall/lib/puppet/type/firewallchain.rb
Normal file
222
modules/firewall/lib/puppet/type/firewallchain.rb
Normal 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
|
225
modules/firewall/lib/puppet/util/firewall.rb
Normal file
225
modules/firewall/lib/puppet/util/firewall.rb
Normal 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
|
42
modules/firewall/lib/puppet/util/ipcidr.rb
Normal file
42
modules/firewall/lib/puppet/util/ipcidr.rb
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue