Skip to content

Commit 9438275

Browse files
authored
Merge pull request #1126 from greatflyingsteve/support-tcp-option
Add support for parsing and using --tcp-option
2 parents a89d13a + a82cd69 commit 9438275

File tree

10 files changed

+400
-109
lines changed

10 files changed

+400
-109
lines changed

.rubocop.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ AllCops:
2626
Layout/LineLength:
2727
Description: People have wide screens, use them.
2828
Max: 200
29+
Metrics/BlockLength:
30+
AllowedMethods:
31+
- provide
32+
- newtype
2933
RSpec/BeforeAfterAll:
3034
Description: Beware of using after(:all) as it may cause state to leak between tests.
3135
A necessary evil in acceptance testing.
@@ -83,4 +87,4 @@ Style/Documentation:
8387
- lib/puppet/parser/functions/**/*
8488
- spec/**/*
8589
Style/WordArray:
86-
EnforcedStyle: brackets
90+
EnforcedStyle: brackets

.rubocop_todo.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,11 @@ Lint/RedundantSafeNavigation:
114114
Metrics/AbcSize:
115115
Max: 235
116116

117-
# Offense count: 23
117+
# Offense count: 17
118118
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
119119
# AllowedMethods: refine
120120
Metrics/BlockLength:
121-
Max: 1961
121+
Max: 64
122122

123123
# Offense count: 2
124124
# Configuration parameters: CountBlocks.

lib/puppet/provider/firewall/ip6tables.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
has_feature :nflog_prefix
3030
has_feature :nflog_range
3131
has_feature :nflog_threshold
32+
has_feature :tcp_option
3233
has_feature :tcp_flags
3334
has_feature :pkttype
3435
has_feature :ishasmorefrags
@@ -183,7 +184,8 @@ def self.iptables_save(*args)
183184
string_from: '--from',
184185
string_to: '--to',
185186
table: '-t',
186-
tcp_flags: '-m tcp --tcp-flags',
187+
tcp_option: '--tcp-option',
188+
tcp_flags: '--tcp-flags',
187189
todest: '--to-destination',
188190
toports: '--to-ports',
189191
tosource: '--to-source',
@@ -312,7 +314,7 @@ def self.iptables_save(*args)
312314
@resource_list = [:table, :source, :destination, :iniface, :outiface, :physdev_in,
313315
:physdev_out, :physdev_is_bridged, :physdev_is_in, :physdev_is_out,
314316
:proto, :ishasmorefrags, :islastfrag, :isfirstfrag, :src_range, :dst_range,
315-
:tcp_flags, :uid, :gid, :mac_source, :sport, :dport, :port, :src_type,
317+
:tcp_option, :tcp_flags, :uid, :gid, :mac_source, :sport, :dport, :port, :src_type,
316318
:dst_type, :socket, :pkttype, :ipsec_dir, :ipsec_policy, :state,
317319
:ctstate, :ctproto, :ctorigsrc, :ctorigdst, :ctreplsrc, :ctrepldst,
318320
:ctorigsrcport, :ctorigdstport, :ctreplsrcport, :ctrepldstport, :ctstatus, :ctexpire, :ctdir,

lib/puppet/provider/firewall/iptables.rb

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
has_feature :nflog_prefix
3535
has_feature :nflog_range
3636
has_feature :nflog_threshold
37+
has_feature :tcp_option
3738
has_feature :tcp_flags
3839
has_feature :pkttype
3940
has_feature :isfragment
@@ -173,7 +174,8 @@
173174
string_from: '--from',
174175
string_to: '--to',
175176
table: '-t',
176-
tcp_flags: ['-m tcp --tcp-flags', '--tcp-flags'],
177+
tcp_option: '--tcp-option',
178+
tcp_flags: '--tcp-flags',
177179
todest: '--to-destination',
178180
toports: '--to-ports',
179181
tosource: '--to-source',
@@ -296,7 +298,13 @@ def self.munge_resource_map_from_existing_values(resource_map_original, compare)
296298

297299
def munge_resource_map_from_resource(resource_map_original, compare)
298300
resource_map_new = resource_map_original.clone
299-
module_to_argument_mapping = self.class.instance_variable_get('@module_to_argument_mapping')
301+
# We ignore the 'tcp' match module on rule parse, but it needs to be included to generate
302+
# arguments, since both '--tcp-option' and '--tcp-flags' should be prefixed with the match
303+
# module spec. '--dport' and '--sport' are ignored because we only produce multiport-style
304+
# portspecs, which do not require '-m tcp', and for which iptables-save does not include
305+
# '-m tcp' in its output.
306+
tcp_module_arguments = { tcp: [:tcp_option, :tcp_flags] }
307+
module_to_argument_mapping = self.class.instance_variable_get('@module_to_argument_mapping').merge(tcp_module_arguments)
300308

301309
module_to_argument_mapping.each do |ipt_module, arg_array|
302310
arg_array.each do |argument|
@@ -348,7 +356,7 @@ def munge_resource_map_from_resource(resource_map_original, compare)
348356
:table, :source, :destination, :iniface, :outiface,
349357
:physdev_in, :physdev_out, :physdev_is_bridged, :physdev_is_in, :physdev_is_out,
350358
:proto, :isfragment, :stat_mode, :stat_every, :stat_packet, :stat_probability,
351-
:src_range, :dst_range, :tcp_flags, :uid, :gid, :mac_source, :sport, :dport, :port,
359+
:src_range, :dst_range, :tcp_option, :tcp_flags, :uid, :gid, :mac_source, :sport, :dport, :port,
352360
:src_type, :dst_type, :socket, :pkttype, :ipsec_dir, :ipsec_policy,
353361
:state, :ctstate, :ctproto, :ctorigsrc, :ctorigdst, :ctreplsrc, :ctrepldst,
354362
:ctorigsrcport, :ctorigdstport, :ctreplsrcport, :ctrepldstport, :ctstatus, :ctexpire, :ctdir,
@@ -507,8 +515,12 @@ def self.rule_to_hash(line, table, counter)
507515
values = values.gsub(%r{(?<=\s)(-\S+) (!)\s?(\S*)}, '\1 "\2 \3"')
508516
# fix negated physdev rules
509517
values = values.gsub(%r{-m physdev ! (--physdev-is-\S+)}, '-m physdev \1 "!"')
510-
# The match extension for tcp & udp are optional and throws off the @resource_map.
511-
values = values.gsub(%r{(?!-m tcp --tcp-flags)-m (tcp|udp) }, '')
518+
# The match extensions for tcp & udp are implied by the protocol, cannot be
519+
# given unless the protocol matches the extension name, are never required,
520+
# and if multiport matches are used, may not even be in the iptables-save
521+
# output in predictable locations. They need to be removed to preserve
522+
# parse sanity.
523+
values = values.gsub(%r{-m (tcp|udp) }, '')
512524
# There is a bug in EL5 which puts 2 spaces before physdev, so we fix it
513525
values = values.gsub(%r{\s{2}--physdev}, ' --physdev')
514526
# '--pol ipsec' takes many optional arguments; we cheat again by adding " around them

lib/puppet/type/firewall.rb

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@
134134
135135
* string_matching: The ability to match a given string by using some pattern matching strategy.
136136
137+
* tcp_option: The ability to match on particular TCP options.
138+
137139
* tcp_flags: The ability to match on particular TCP flag settings.
138140
139141
* netmap: The ability to map entire subnets via source or destination nat rules.
@@ -171,6 +173,7 @@
171173
feature :log_ip_options, 'Add IP/IPv6 packet header to log messages'
172174
feature :mark, 'Match or Set the netfilter mark value associated with the packet'
173175
feature :mss, 'Match a given TCP MSS value or range.'
176+
feature :tcp_option, 'The ability to match on particular TCP options'
174177
feature :tcp_flags, 'The ability to match on particular TCP flag settings'
175178
feature :pkttype, 'Match a packet type'
176179
feature :rpfilter, 'Perform reverse-path filtering'
@@ -569,6 +572,24 @@ def should_to_s(value)
569572
PUPPETCODE
570573
end
571574

575+
# tcp-specific
576+
newproperty(:tcp_option, required_features: :tcp_option) do
577+
desc <<-PUPPETCODE
578+
Match when the TCP option is present or absent.
579+
Given as a single TCP option, optionally prefixed with '! ' to match
580+
on absence instead. Only one TCP option can be matched in a given rule.
581+
TCP option numbers are an eight-bit field, so valid option numbers range
582+
from 0-255.
583+
PUPPETCODE
584+
585+
validate do |value|
586+
unless value.to_i.bit_length < 8 && value.to_i >= 0
587+
raise ArgumentError, "TCP Options fall in the range 0-255, #{value} is not a valid TCP Option number"
588+
end
589+
end
590+
munge { |value| value.to_s }
591+
end
592+
572593
# tcp-specific
573594
newproperty(:tcp_flags, required_features: :tcp_flags) do
574595
desc <<-PUPPETCODE
@@ -2341,7 +2362,7 @@ def should_to_s(value)
23412362
['/etc/sysconfig/iptables', '/etc/sysconfig/ip6tables']
23422363
end
23432364

2344-
validate do
2365+
validate do # rubocop:disable Metrics/BlockLength
23452366
debug('[validate]')
23462367

23472368
# TODO: this is put here to skip validation if ensure is not set. This

metadata.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "puppetlabs-firewall",
3-
"version": "5.0.0",
3+
"version": "5.1.0",
44
"author": "puppetlabs",
55
"summary": "Manages Firewalls such as iptables",
66
"license": "Apache-2.0",

spec/fixtures/ip6tables/conversion_hash.rb

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,94 @@
4949
params: {
5050
random_fully: 'true',
5151
}
52-
}
52+
},
53+
'tcp_flags_1' => {
54+
line: '-A INPUT -p tcp -m tcp --tcp-flags SYN,RST,ACK,FIN SYN -m comment --comment "000 initiation"',
55+
compare_all: true,
56+
table: 'filter',
57+
chain: 'INPUT',
58+
proto: 'tcp',
59+
params: {
60+
name: '000 initiation',
61+
tcp_flags: 'SYN,RST,ACK,FIN SYN',
62+
proto: 'tcp',
63+
chain: 'INPUT',
64+
line: '-A INPUT -p tcp -m tcp --tcp-flags SYN,RST,ACK,FIN SYN -m comment --comment "000 initiation"',
65+
provider: 'ip6tables',
66+
table: 'filter',
67+
ensure: :present,
68+
},
69+
},
70+
'tcp_option_1' => {
71+
line: '-A INPUT -p tcp -m tcp --tcp-option 8 -m comment --comment "001 tcp_option works alone"',
72+
compare_all: true,
73+
table: 'filter',
74+
chain: 'INPUT',
75+
proto: 'tcp',
76+
params: {
77+
chain: 'INPUT',
78+
ensure: :present,
79+
line: '-A INPUT -p tcp -m tcp --tcp-option 8 -m comment --comment "001 tcp_option works alone"',
80+
name: '001 tcp_option works alone',
81+
proto: 'tcp',
82+
provider: 'ip6tables',
83+
table: 'filter',
84+
tcp_option: '8',
85+
},
86+
},
87+
'tcp_option_2' => {
88+
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 -m comment --comment "002 tcp_option works alone, negated"',
89+
compare_all: true,
90+
table: 'filter',
91+
chain: 'INPUT',
92+
proto: 'tcp',
93+
params: {
94+
chain: 'INPUT',
95+
ensure: :present,
96+
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 -m comment --comment "002 tcp_option works alone, negated"',
97+
name: '002 tcp_option works alone, negated',
98+
proto: 'tcp',
99+
provider: 'ip6tables',
100+
table: 'filter',
101+
tcp_option: '! 8',
102+
},
103+
},
104+
'tcp_option_with_tcp_flags_1' => {
105+
line: '-A INPUT -p tcp -m tcp --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
106+
table: 'filter',
107+
compare_all: true,
108+
chain: 'INPUT',
109+
proto: 'tcp',
110+
params: {
111+
chain: 'INPUT',
112+
ensure: :present,
113+
line: '-A INPUT -p tcp -m tcp --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
114+
name: '000 initiation',
115+
proto: 'tcp',
116+
provider: 'ip6tables',
117+
table: 'filter',
118+
tcp_flags: 'FIN,SYN,RST,ACK SYN',
119+
tcp_option: '8',
120+
},
121+
},
122+
'tcp_option_with_tcp_flags_2' => {
123+
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
124+
table: 'filter',
125+
compare_all: true,
126+
chain: 'INPUT',
127+
proto: 'tcp',
128+
params: {
129+
chain: 'INPUT',
130+
ensure: :present,
131+
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
132+
name: '000 initiation',
133+
proto: 'tcp',
134+
provider: 'ip6tables',
135+
table: 'filter',
136+
tcp_flags: 'FIN,SYN,RST,ACK SYN',
137+
tcp_option: '! 8',
138+
},
139+
},
53140
}.freeze
54141

55142
# This hash is for testing converting a hash to an argument line.
@@ -141,4 +228,55 @@
141228
},
142229
args: ['-t', :filter, '-p', :tcp, '-j', 'NFLOG', '--nflog-group', 1, '--nflog-prefix', 'myprefix', '-m', 'comment', '--comment', '100 nflog'],
143230
},
231+
'tcp_flags_1' => {
232+
params: {
233+
name: '000 initiation',
234+
tcp_flags: 'SYN,RST,ACK,FIN SYN',
235+
table: 'filter',
236+
},
237+
238+
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '--tcp-flags', 'SYN,RST,ACK,FIN', 'SYN', '-m', 'comment', '--comment', '000 initiation'],
239+
},
240+
'tcp_option_1' => {
241+
params: {
242+
name: '000 initiation',
243+
table: 'filter',
244+
chain: 'INPUT',
245+
proto: 'tcp',
246+
tcp_option: '8',
247+
},
248+
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '--tcp-option', '8', '-m', 'comment', '--comment', '000 initiation'],
249+
},
250+
'tcp_option_2' => {
251+
params: {
252+
name: '000 initiation',
253+
table: 'filter',
254+
chain: 'INPUT',
255+
proto: 'tcp',
256+
tcp_option: '! 8',
257+
},
258+
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '!', '--tcp-option', '8', '-m', 'comment', '--comment', '000 initiation'],
259+
},
260+
'tcp_option_with_tcp_flags_1' => {
261+
params: {
262+
name: '000 initiation',
263+
table: 'filter',
264+
chain: 'INPUT',
265+
proto: 'tcp',
266+
tcp_flags: 'FIN,SYN,RST,ACK SYN',
267+
tcp_option: '8',
268+
},
269+
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '--tcp-option', '8', '--tcp-flags', 'FIN,SYN,RST,ACK', 'SYN', '-m', 'comment', '--comment', '000 initiation'],
270+
},
271+
'tcp_option_with_tcp_flags_2' => {
272+
params: {
273+
name: '000 initiation',
274+
table: 'filter',
275+
chain: 'INPUT',
276+
proto: 'tcp',
277+
tcp_flags: 'FIN,SYN,RST,ACK SYN',
278+
tcp_option: '! 8',
279+
},
280+
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '!', '--tcp-option', '8', '--tcp-flags', 'FIN,SYN,RST,ACK', 'SYN', '-m', 'comment', '--comment', '000 initiation'],
281+
},
144282
}.freeze

0 commit comments

Comments
 (0)