puffy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puffy
4
+ module Formatters
5
+ module Netfilter # :nodoc:
6
+ # Returns the target to jump to
7
+ #
8
+ # @return [String]
9
+ def self.iptables_action(rule_or_action, ret: false)
10
+ case rule_or_action
11
+ when :pass then 'ACCEPT'
12
+ when :log then 'LOG'
13
+ when :block then ret ? 'RETURN' : 'DROP'
14
+ when Puffy::Rule then iptables_action(rule_or_action.action, ret: rule_or_action.return)
15
+ end
16
+ end
17
+
18
+ # Netfilter implementation of a Puffy Ruleset formatter.
19
+ class Ruleset < Puffy::Formatters::Base::Ruleset # :nodoc:
20
+ def self.known_conntrack_helpers
21
+ {
22
+ 21 => 'ftp',
23
+ 69 => 'tftp',
24
+ 194 => 'irc',
25
+ 6566 => 'sane',
26
+ 5060 => 'sip',
27
+ }
28
+ end
29
+
30
+ # Returns a Netfilter String representation of the provided +rules+ Array of Puffy::Rule with the +policy+ policy.
31
+ def emit_ruleset(rules, policy = :block)
32
+ parts = []
33
+ parts << emit_header
34
+ parts << raw_ruleset(raw_rules(rules))
35
+ parts << nat_ruleset(nat_rules(rules))
36
+ parts << filter_ruleset(filter_rules(rules), policy)
37
+ ruleset = parts.flatten.compact.join("\n")
38
+ "#{ruleset}\n"
39
+ end
40
+
41
+ private
42
+
43
+ def raw_ruleset(rules)
44
+ return unless rules.any?
45
+
46
+ parts = ['*raw']
47
+ parts << emit_chain_policies(prerouting: :pass, output: :pass)
48
+ parts << rules.map { |rule| @rule_formatter.emit_ct_rule(rule) }.uniq
49
+ parts << 'COMMIT'
50
+ parts
51
+ end
52
+
53
+ def nat_ruleset(rules)
54
+ return unless rules.any?
55
+
56
+ parts = ['*nat']
57
+ parts << emit_chain_policies(prerouting: :pass, input: :pass, output: :pass, postrouting: :pass)
58
+ parts << rules.select(&:rdr?).map { |rule| @rule_formatter.emit_rule(rule) }
59
+ parts << rules.select(&:nat?).map { |rule| @rule_formatter.emit_rule(rule) }
60
+ parts << 'COMMIT'
61
+ parts
62
+ end
63
+
64
+ def filter_ruleset(rules, policy)
65
+ return unless rules.any?
66
+
67
+ parts = ['*filter']
68
+ parts << emit_chain_policies(input: policy, forward: policy, output: policy)
69
+ parts << input_filter_ruleset(rules)
70
+ parts << forward_filter_ruleset(rules)
71
+ parts << output_filter_rulset(rules)
72
+ parts << 'COMMIT'
73
+ parts
74
+ end
75
+
76
+ def emit_chain_policies(policies)
77
+ policies.map { |chain, action| ":#{chain.upcase} #{Puffy::Formatters::Netfilter.iptables_action(action)} [0:0]" }
78
+ end
79
+
80
+ def input_filter_ruleset(rules)
81
+ parts = ['-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT']
82
+ parts << input_filter_rules(rules).map { |rule| @rule_formatter.emit_rule(rule) }
83
+ end
84
+
85
+ def forward_filter_ruleset(rules)
86
+ parts = ['-A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT']
87
+ parts << rules.select(&:fwd?).map { |rule| @rule_formatter.emit_rule(rule) }
88
+ parts << rules.select { |r| r.rdr? && !Puffy::Formatters::Base.loopback_addresses.include?(r.rdr_to_host) }.map { |rule| @rule_formatter.emit_rule(Puffy::Rule.fwd_rule(rule)) }
89
+ end
90
+
91
+ def output_filter_rulset(rules)
92
+ parts = ['-A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT']
93
+ parts << output_filter_rules(rules).map { |rule| @rule_formatter.emit_rule(rule) }
94
+ end
95
+
96
+ def raw_rules(rules)
97
+ rules.select { |r| r.action == :pass && Ruleset.known_conntrack_helpers.include?(r.to_port) }
98
+ end
99
+
100
+ def nat_rules(rules)
101
+ rules.select { |r| r.nat? || r.rdr? }
102
+ end
103
+
104
+ def filter_rules(rules)
105
+ rules.select { |r| %i[pass block log].include?(r.action) }
106
+ end
107
+
108
+ def input_filter_rules(rules)
109
+ rules.select { |r| r.filter? && r.in? }
110
+ end
111
+
112
+ def output_filter_rules(rules)
113
+ rules.select { |r| r.filter? && r.out? }
114
+ end
115
+ end
116
+
117
+ # Netfilter implementation of a Puffy Rule formatter.
118
+ class Rule < Puffy::Formatters::Base::Rule # :nodoc:
119
+ # Returns a Netfilter String representation of the provided +rule+ Puffy::Rule.
120
+ def emit_rule(rule)
121
+ if rule.nat?
122
+ emit_postrouting_rule(rule)
123
+ elsif rule.rdr?
124
+ emit_prerouting_rule(rule)
125
+ else
126
+ emit_filter_rule(rule)
127
+ end
128
+ end
129
+
130
+ def emit_ct_rule(rule)
131
+ parts = ['-A PREROUTING']
132
+ parts << emit_if(rule)
133
+ parts << emit_proto(rule)
134
+ parts << emit_src_port(rule)
135
+ parts << emit_dst_port(rule)
136
+ parts << '-j CT'
137
+ parts << "--helper #{Ruleset.known_conntrack_helpers[rule.to_port]}"
138
+ pp_rule(parts)
139
+ end
140
+
141
+ def emit_postrouting_rule(rule)
142
+ "-A POSTROUTING -o #{rule.on} -j MASQUERADE"
143
+ end
144
+
145
+ def emit_prerouting_rule(rule)
146
+ parts = ['-A PREROUTING']
147
+ parts << emit_on(rule)
148
+ parts << emit_proto(rule)
149
+ parts << emit_src(rule)
150
+ parts << emit_dst(rule)
151
+ parts << emit_redirect_or_dnat(rule)
152
+ pp_rule(parts)
153
+ end
154
+
155
+ def emit_filter_rule(rule)
156
+ iptables_direction = { in: 'INPUT', out: 'OUTPUT', fwd: 'FORWARD' }
157
+ parts = ["-A #{iptables_direction[rule.dir]}"]
158
+ parts << '-m conntrack --ctstate NEW' if %i[tcp udp].include?(rule.proto)
159
+ parts << emit_if(rule)
160
+ parts << emit_proto(rule)
161
+ parts << emit_src(rule)
162
+ parts << emit_dst(rule)
163
+ parts << emit_jump(rule)
164
+ pp_rule(parts)
165
+ end
166
+
167
+ def emit_if(rule)
168
+ if rule.on
169
+ emit_on(rule)
170
+ else
171
+ emit_in_out(rule)
172
+ end
173
+ end
174
+
175
+ def emit_on(rule)
176
+ on_direction_flag = { in: '-i', out: '-o' }
177
+
178
+ return unless rule.on || rule.dir
179
+
180
+ matches = /(!)?(.*)/.match(rule.on)
181
+ [matches[1], on_direction_flag[rule.dir], matches[2]].compact
182
+ end
183
+
184
+ def emit_in_out(rule)
185
+ parts = []
186
+ parts << "-i #{rule.in}" if rule.in
187
+ parts << "-o #{rule.out}" if rule.out
188
+ parts
189
+ end
190
+
191
+ def emit_proto(rule)
192
+ "-p #{rule.proto}" if rule.proto
193
+ end
194
+
195
+ def emit_src(rule)
196
+ emit_src_host(rule) + emit_src_port(rule)
197
+ end
198
+
199
+ def emit_src_host(rule)
200
+ if rule.from_host
201
+ ['-s', emit_address(rule.from_host)]
202
+ else
203
+ []
204
+ end
205
+ end
206
+
207
+ def emit_src_port(rule)
208
+ if rule.from_port
209
+ ['--sport', emit_port(rule.from_port)]
210
+ else
211
+ []
212
+ end
213
+ end
214
+
215
+ def emit_dst(rule)
216
+ emit_dst_host(rule) + emit_dst_port(rule)
217
+ end
218
+
219
+ def emit_dst_host(rule)
220
+ if rule.to_host
221
+ ['-d', emit_address(rule.to_host)]
222
+ else
223
+ []
224
+ end
225
+ end
226
+
227
+ def emit_dst_port(rule)
228
+ if rule.to_port
229
+ ['--dport', emit_port(rule.to_port)]
230
+ else
231
+ []
232
+ end
233
+ end
234
+
235
+ def emit_redirect_or_dnat(rule)
236
+ if Puffy::Formatters::Base.loopback_addresses.include?(rule.rdr_to_host)
237
+ emit_redirect(rule)
238
+ else
239
+ emit_dnat(rule)
240
+ end
241
+ end
242
+
243
+ def emit_redirect(rule)
244
+ "-j REDIRECT --to-port #{rule.rdr_to_port}"
245
+ end
246
+
247
+ def emit_dnat(rule)
248
+ res = "-j DNAT --to-destination #{rule.rdr_to_host}"
249
+ res += ":#{rule.rdr_to_port}" if rule.rdr_to_port && rule.rdr_to_port != rule.to_port
250
+ res
251
+ end
252
+
253
+ def emit_jump(rule)
254
+ "-j #{Puffy::Formatters::Netfilter.iptables_action(rule)}"
255
+ end
256
+
257
+ def pp_rule(parts)
258
+ parts.flatten.compact.join(' ')
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puffy
4
+ module Formatters
5
+ module Netfilter4 # :nodoc:
6
+ # IPv4 Netfilter implementation of a Puffy Ruleset formatter.
7
+ class Ruleset < Puffy::Formatters::Netfilter::Ruleset # :nodoc:
8
+ # Return an IPv4 Netfilter String representation of the provided +rules+ Puffy::Rule with the +policy+ policy.
9
+ def emit_ruleset(rules, policy = :block)
10
+ super(rules.select(&:ipv4?), policy)
11
+ end
12
+
13
+ def filename_fragment
14
+ ['netfilter', 'rules.v4']
15
+ end
16
+ end
17
+
18
+ # IPv4 Netfilter implementation of a Puffy Rulet formatter.
19
+ class Rule < Puffy::Formatters::Netfilter::Rule # :nodoc:
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puffy
4
+ module Formatters
5
+ module Netfilter6 # :nodoc:
6
+ # IPv6 Netfilter implementation of a Puffy Ruleset formatter.
7
+ class Ruleset < Puffy::Formatters::Netfilter::Ruleset # :nodoc:
8
+ # Return an IPv6 Netfilter String representation of the provided +rules+ Puffy::Rule with the +policy+ policy.
9
+ def emit_ruleset(rules, policy = :block)
10
+ super(rules.select(&:ipv6?), policy)
11
+ end
12
+
13
+ def filename_fragment
14
+ ['netfilter', 'rules.v6']
15
+ end
16
+ end
17
+
18
+ # IPv6 Netfilter implementation of a Puffy Rule formatter.
19
+ class Rule < Puffy::Formatters::Netfilter::Rule # :nodoc:
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puffy
4
+ module Formatters
5
+ module Pf # :nodoc:
6
+ # Pf implementation of a Puffy Ruleset formatter.
7
+ class Ruleset < Puffy::Formatters::Base::Ruleset # :nodoc:
8
+ # Returns a Pf String representation of the provided +rules+ Array of Puffy::Rule.
9
+ def emit_ruleset(rules, policy = :block)
10
+ parts = []
11
+
12
+ parts << emit_header(policy)
13
+
14
+ parts << super(rules.select(&:nat?))
15
+ parts << super(rules.select(&:rdr?))
16
+ parts << super(rules.select(&:filter?))
17
+
18
+ ruleset = parts.reject(&:empty?).join("\n")
19
+ "#{ruleset}\n"
20
+ end
21
+
22
+ def filename_fragment
23
+ ['pf', 'pf.conf']
24
+ end
25
+
26
+ def emit_header(policy)
27
+ parts = super()
28
+ parts << 'match in all scrub (no-df)'
29
+ parts << 'set skip on lo'
30
+ parts << @rule_formatter.emit_rule(Puffy::Rule.new(action: policy, dir: :in, no_quick: true))
31
+ parts << @rule_formatter.emit_rule(Puffy::Rule.new(action: policy, dir: :out, no_quick: true))
32
+ parts
33
+ end
34
+ end
35
+
36
+ # Pf implementation of a Puffy Rule formatter.
37
+ class Rule < Puffy::Formatters::Base::Rule # :nodoc:
38
+ # Returns a Pf String representation of the provided +rule+ Puffy::Rule.
39
+ def emit_rule(rule)
40
+ parts = []
41
+ parts << emit_action(rule)
42
+ parts << emit_direction(rule)
43
+ parts << emit_quick(rule)
44
+ parts << emit_on(rule)
45
+ parts << emit_what(rule)
46
+ parts.flatten.compact.join(' ')
47
+ end
48
+
49
+ private
50
+
51
+ def emit_action(rule)
52
+ parts = [rule.action]
53
+ parts << 'return' if rule.action == :block && rule.return
54
+ parts
55
+ end
56
+
57
+ def emit_direction(rule)
58
+ rule.dir
59
+ end
60
+
61
+ def emit_quick(rule)
62
+ 'quick' unless rule.no_quick
63
+ end
64
+
65
+ def emit_on(rule)
66
+ "on #{rule.on.gsub('!', '! ')}" if rule.on
67
+ end
68
+
69
+ def emit_what(rule)
70
+ parts = [emit_af(rule)]
71
+ parts << emit_proto(rule)
72
+ parts << emit_from(rule)
73
+ parts << emit_to(rule)
74
+ parts << emit_rdr_to(rule)
75
+ parts << emit_nat_to(rule)
76
+
77
+ parts.flatten.compact.empty? ? 'all' : parts
78
+ end
79
+
80
+ def emit_af(rule)
81
+ if rule.implicit_ipv4? || rule.implicit_ipv6?
82
+ nil
83
+ elsif rule.ipv4? || rule.ipv6?
84
+ rule.af
85
+ end
86
+ end
87
+
88
+ def emit_proto(rule)
89
+ "proto #{rule.proto}" if rule.proto
90
+ end
91
+
92
+ def emit_from(rule)
93
+ emit_endpoint_specification('from', rule.from_host, rule.from_port) if rule.from_host || rule.from_port
94
+ end
95
+
96
+ def emit_to(rule)
97
+ emit_endpoint_specification('to', rule.to_host, rule.to_port) if rule.to_host || rule.to_port
98
+ end
99
+
100
+ def emit_endpoint_specification(keyword, host, port)
101
+ parts = [keyword]
102
+ parts << emit_address(host)
103
+ parts << "port #{emit_port(port)}" if port
104
+ parts
105
+ end
106
+
107
+ # Return a valid PF representation of +host+.
108
+ def emit_address(host, if_unspecified = 'any')
109
+ if host
110
+ super(host)
111
+ else
112
+ if_unspecified
113
+ end
114
+ end
115
+
116
+ def emit_rdr_to(rule)
117
+ return unless rule.rdr?
118
+
119
+ keyword = Puffy::Formatters::Base.loopback_addresses.include?(rule.rdr_to_host) ? 'divert-to' : 'rdr-to'
120
+ destination = rule.rdr_to_host || loopback_address(rule.af)
121
+ raise 'Unspecified address family' if destination.nil?
122
+
123
+ emit_endpoint_specification(keyword, destination, rule.rdr_to_port)
124
+ end
125
+
126
+ def emit_nat_to(rule)
127
+ "nat-to #{emit_address(rule.nat_to)}" if rule.nat_to
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end