-
Notifications
You must be signed in to change notification settings - Fork 456
/
Copy pathfirewallchain.rb
248 lines (212 loc) · 8.84 KB
/
firewallchain.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# 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 puppet server
#
# 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 = <<-PUPPETCODE
@summary
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, iptables-persistent, or iptables-services packages,
and the provider is iptables_chain, the firewall resource will autorequire
those packages to ensure that any required binaries are installed.
#### Providers
* iptables_chain is the only provider that supports firewallchain.
#### Features
* iptables_chain: The provider provides iptables chain features.
* policy: Default policy (inbuilt chains only).
PUPPETCODE
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 <<-PUPPETCODE
The canonical name of the chain.
For iptables the format must be {chain}:{table}:{protocol}.
PUPPETCODE
isnamevar
validate do |value|
if value !~ NAME_FORMAT
raise ArgumentError, 'Inbuilt chains must be in the form {chain}:{table}:{protocol} where {table} is one of filter,' \
' nat, mangle, raw, rawpost, broute, security 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:'#{Regexp.last_match(1)}' chain:'#{Regexp.last_match(2)}' protocol:'#{Regexp.last_match(3)}'"
else
chain = Regexp.last_match(1)
table = Regexp.last_match(2)
protocol = Regexp.last_match(3)
case table
when 'filter'
if chain =~ %r{^(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 =~ INTERNAL_CHAINS && 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 =~ %r{^(BROUTING|FORWARD)$}
raise ArgumentError, "PREROUTING, POSTROUTING, INPUT, and OUTPUT are the only inbuilt chains that can be used in table 'nat'"
end
if Gem::Version.new(Facter['kernelmajversion'].value.dup) < Gem::Version.new('3.7') && protocol =~ %r{^(IP(v6)?)?$}
raise ArgumentError, "table nat isn't valid in IPv6. You must specify ':IPv4' as the name suffix"
end
when 'raw'
if chain =~ %r{^(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 =~ %r{^PREROUTING|POSTROUTING|INPUT|FORWARD|OUTPUT$}
raise ArgumentError, 'BROUTING is the only inbuilt chain allowed on on table \'broute\''
end
when 'security'
if chain =~ %r{^(PREROUTING|POSTROUTING|BROUTING)$}
raise ArgumentError, "INPUT, OUTPUT and FORWARD are the only inbuilt chains that can be used in table 'security'"
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 <<-PUPPETCODE
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
PUPPETCODE
newvalues(:accept, :drop, :queue, :return)
defaultto do
# ethernet chain have an ACCEPT default while other haven't got an
# allowed value
if @resource[:name] =~ %r{:ethernet$}
:accept
else
nil
end
end
end
newparam(:purge, boolean: true) do
desc <<-PUPPETCODE
Purge unmanaged firewall rules in this chain
PUPPETCODE
newvalues(false, true)
defaultto false
end
newparam(:ignore) do
desc <<-PUPPETCODE
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
],
}
```
PUPPETCODE
validate do |value|
unless value.is_a?(Array) || value.is_a?(String) || value == false
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
['iptables', 'iptables-persistent', 'iptables-services']
else
[]
end
end
autorequire(:service) do
case value(:provider)
when :iptables, :ip6tables
['firewalld', 'iptables', 'ip6tables', 'iptables-persistent', 'netfilter-persistent']
else
[]
end
end
validate do
debug('[validate]')
value(:name).match(NAME_FORMAT)
chain = Regexp.last_match(1)
table = Regexp.last_match(2)
protocol = Regexp.last_match(3)
# Check that we're not removing an internal chain
if chain =~ INTERNAL_CHAINS && value(:ensure) == :absent
raise 'Cannot remove in-built chains'
end
if value(:policy).nil? && protocol == 'ethernet'
raise '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 !~ INTERNAL_CHAINS &&
!value(:policy).nil? &&
protocol != 'ethernet'
raise "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
raise 'The "nat" table is not intended for filtering, the use of DROP is therefore inhibited'
end
end
def generate
return [] unless purge?
value(:name).match(NAME_FORMAT)
chain = Regexp.last_match(1)
table = Regexp.last_match(2)
protocol = Regexp.last_match(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 || res.provider.properties[:table].to_s != table || 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