Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ticket/10162 firewallchain support for merge #62

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 32 additions & 2 deletions README.markdown
Expand Up @@ -4,14 +4,21 @@

### Overview

This type provides the capability to manage firewall rules within
puppet.
This module provides the resource 'firewall' which provides the capability to
manage firewall rules within puppet.

Current support includes:

* iptables
* ip6tables

With the resource 'firewallchain' we also provide a mechanism to manage chains
for:

* iptables
* ip6tables
* ebtables

### Disclaimer

Warning! While this software is written in the best interest of quality it has
Expand Down Expand Up @@ -93,6 +100,25 @@ Source NAT example (perfect for a virtualization host):
table => 'nat',
}

Creating a new rule that forwards to a chain, then adding a rule to this chain:

firewall { '100 forward to MY_CHAIN':
chain => 'INPUT',
jump => 'MY_CHAIN',
require => Firewallchain["MY_CHAIN:filter:IPv4"],
}
# The namevar here is in the format chain_name:table:protocol
firewallchain { 'MY_CHAIN:filter:IPv4':
ensure => present,
}
firewall { '100 my rule':
chain => 'MY_CHAIN',
action => 'accept',
proto => 'tcp',
dport => 5000,
require => Firewallchain["MY_CHAIN:filter:IPv4"],
}

You can make firewall rules persistent with the following iptables example:

exec { "persist-firewall":
Expand All @@ -105,6 +131,9 @@ You can make firewall rules persistent with the following iptables example:
Firewall {
notify => Exec["persist-firewall"]
}
Firewallchain {
notify => Exec["persist-firewall"]
}

If you wish to ensure any reject rules are executed last, try using stages.
The following example shows the creation of a class which is where your
Expand Down Expand Up @@ -156,6 +185,7 @@ Currently we support:

* iptables
* ip6tables
* ebtables (chains only)

But plans are to support lots of other firewall implementations:

Expand Down
164 changes: 164 additions & 0 deletions lib/puppet/provider/firewallchain/iptables_chain.rb
@@ -0,0 +1,164 @@
Puppet::Type.type(:firewallchain).provide :iptables_chain do
@doc = "Iptables chain provider"

has_feature :iptables_chain
has_feature :policy

optional_commands({
:iptables => '/sbin/iptables',
:iptables_save => '/sbin/iptables-save',
:ip6tables => '/sbin/ip6tables',
:ip6tables_save => '/sbin/ip6tables-save',
:ebtables => '/sbin/ebtables',
:ebtables_save => '/sbin/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
# can't create internal chains
if @resource[:name] =~ InternalChains
self.warn "Attempting to create internal chain #{@resource[:name]}"
end
allvalidchains do |t, chain, table, protocol|
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
# can't delete internal chains
if @resource[:name] =~ InternalChains
self.warn "Attempting to destroy internal chain #{@resource[:name]}"
end
allvalidchains do |t, table, chain|
debug "Deleting chain #{chain} on table #{table}"
t.call ['-t',table,'-X',chain]
end
end

def exists?
properties[:ensure] == :present
end

def policy=(value)
return if value == :empty
allvalidchains do |t, table, chain|
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]")
# 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
125 changes: 125 additions & 0 deletions lib/puppet/type/firewallchain.rb
@@ -0,0 +1,125 @@
Puppet::Type.newtype(:firewallchain) do

@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.
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|INPUT|FORWARD)$/
raise ArgumentError, "PREROUTING, POSTROUTING 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

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
end