puffy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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