-
Notifications
You must be signed in to change notification settings - Fork 8
/
suse_firewall.rb
334 lines (297 loc) · 11.1 KB
/
suse_firewall.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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# ------------------------------------------------------------------------------
# Copyright (c) 2017 SUSE LLC
#
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of version 2 of the GNU General Public License as published by the
# Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, contact SUSE.
#
# To contact SUSE about this file by physical or electronic mail, you may find
# current contact information at www.suse.com.
# ------------------------------------------------------------------------------
require "yast"
require "y2firewall/firewalld"
require "ui/text_helpers"
module Y2Firewall
module ImporterStrategies
# This class is reponsible of parsing SuSEFirewall2 firewalld profile's
# section configuring the Y2Firewall::Firewalld instance according to it.
class SuseFirewall
include Yast::Logger
include Yast::I18n
include UI::TextHelpers
# @return [Hash] AutoYaST profile firewall's section
attr_accessor :profile
Yast.import "Report"
# SuSEFirewall2 zones
ZONES = ["DMZ", "INT", "EXT"].freeze
# Best effort conversion of SuSEFirewall2 services into firewalld
# predefined ones.
SERVICE_MAP = {
"bind" => ["dns"],
"dhcp-server" => ["dhcp"],
"dhcp6-server" => ["dhcpv6"],
"nfs-client" => ["nfs"],
"nfs-kernel-server" => ["mountd", "nfs", "rpc-bind"],
"netbios-server" => ["samba"],
"openldap" => ["ldap", "ldaps"],
"rsync-server" => ["rsyncd"],
"sshd" => ["ssh"],
"samba-server" => ["samba"]
}.freeze
SUPPORTED_PROPERTIES = [
"enable_firewall",
"start_firewall",
"FW_CONFIGURATIONS_DMZ",
"FW_CONFIGURATIONS_EXT",
"FW_CONFIGURATIONS_INT",
"FW_DEV_DMZ",
"FW_DEV_EXT",
"FW_DEV_INT",
"FW_SERVICES_DMZ_TCP",
"FW_SERVICES_EXT_TCP",
"FW_SERVICES_INT_TCP",
"FW_SERVICES_DMZ_UDP",
"FW_SERVICES_EXT_UDP",
"FW_SERVICES_INT_UDP",
"FW_SERVICES_DMZ_IP",
"FW_SERVICES_EXT_IP",
"FW_SERVICES_INT_IP",
"FW_LOG_DROP_CRIT",
"FW_LOG_DROP_ALL",
"FW_MASQUERADE",
"FW_PROTECT_FROM_INT"
].freeze
# @return [Array<string>] list of zones
def zones
ZONES
end
# Constructor
#
# @param profile [Hash] AutoYaST profile firewall's section
def initialize(profile)
textdomain "firewall"
@profile = profile
end
# Return whether some of the profile properties are not supported
#
# @return [Boolean] true if all the profiles properties are supported;
# false otherwise
def completely_supported?
unsupported_properties.empty?
end
# Return the list of not supported properties that are defined in the
# profile
#
# @return [Array<String>] not supported properties
def unsupported_properties
@profile.keys.reject { |k| SUPPORTED_PROPERTIES.include?(k) }
end
# It processes the profile configuring the firewalld zones that match
# better with the SuSEFirewall2 ones.
def import
if profile.empty?
log.info "The profile is empty, there is nothing to import"
return true
end
completely_supported? ? warn_supported : report_unsupported
zones.each { |z| process_zone(z) }
if ipsec_trust_zone
zone = firewalld.find_zone(zone.equivalent(ipsec_trust_zone))
(zone.services << "ipsec") if zone
end
firewalld.log_denied_packets = log_denied_packets
true
end
private
# Convenience method for reporting a warning message to the user
# recommending the use of firewalld schema.
def warn_supported
Yast::Report.Warning(
_(
"The profile in use is based on SuSEFirewall2 configuration.\n\n" \
"Although all the declared properties are supported, it is recommended \n" \
"the use of the new 'firewalld' schema. \n\n" \
"Please, check carefully the configuration applied once the installation \n" \
"is finished."
)
)
end
# Convenience method for reporting an error message to the user with the
# unsupported SuSEFirewall2 properties.
def report_unsupported
Yast::Report.Error(
_(
"Unfortunately, these SuSEFirewall2 properties are not supported:\n\n%s\n\n" \
"Check carefully the configuration applied once the installation \n" \
"is finished."
) % wrap_text(unsupported_properties.join(", "))
)
end
# Given a SuSEFirewall2 zone name it process the profile's configuration
# corresponding to that zone configuring the equivalent firewalld zone
# object.
# @param name [String] SuSEFirewall2 zone name
def process_zone(name)
log.info "Processing zone #{name}"
zone = firewalld.find_zone(zone_equivalent(name))
if !zone
log.error "There is no zone for #{name}"
return
end
if interfaces(name)
zone.interfaces = interfaces(name)
firewalld.default_zone = zone_equivalent(name) if default_zone?(name)
end
zone.services = services(name) if services(name)
zone.protocols = protocols(name) if protocols(name)
zone.ports = ports(name) if ports(name)
end
# Obtain the services for the given SuSEFIrewall2 zone name from the
# profile.
#
# @param zone_name [String]
def services(zone_name)
services = profile["FW_CONFIGURATIONS_#{zone_name}"]
services ? map_services(services.split) : nil
end
# Obtain the interfaces for the given SuSEFIrewall2 zone name from the
# profile. It removes 'any' from the list of interfaces as it is an
# especial case for SuSEFirewall2.
#
# @param zone_name [String]
# @return [Array<String>, nil] return the list of interfaces without
# especial wildcards like 'any' or nil in case the key is not defined
def interfaces(zone_name)
interfaces = profile["FW_DEV_#{zone_name}"]
interfaces ? interfaces.split.reject { |i| i == "any" } : nil
end
# Return whether the given zone name is the default one.
#
# @param zone_name [String]
# @return [Boolean] true if the zone name is the default one; false
# otherwise
def default_zone?(zone_name)
profile.fetch("FW_DEV_#{zone_name}", []).include?("any")
end
# Obtain the protocols for the given SuSEFIrewall2 zone name from the
# profile.
#
# @param zone [String]
# @return [Array<String>, nil]
def protocols(zone)
protocols = profile["FW_SERVICES_#{zone}_IP"]
protocols ? protocols.split : nil
end
# Obtain the ports for the given SuSEFIrewall2 zone name from the
# profile.
#
# @param zone [String]
# @return [Array<String>, nil]
def ports(zone)
return nil unless rpc_ports(zone) || tcp_ports(zone) || udp_ports(zone)
[rpc_ports(zone), tcp_ports(zone), udp_ports(zone)].compact.flatten
end
# Map over the given list of SuSEFIrewall2 service names converting the
# ones that are known have changed using the corresponding firewalld
# service name.
#
# @param services [Array<String>] list of SuSEFirewall2 services names.
# @return [Array<String>] list of given services converted to firewalld
# when known.
def map_services(services)
services.map do |service|
SERVICE_MAP[service] || service
end.flatten.compact.uniq
end
# Obtain the TCP ports for the given SuSEFIrewall2 zone name from the
# profile.
#
# @param zone [String]
# @return [Array<Strint>, nil] list of configured TCP ports; nil if no
# configured
def tcp_ports(zone)
ports = profile["FW_SERVICES_#{zone}_TCP"]
ports ? ports.split.map { |p| "#{p.sub(":", "-")}/tcp" } : nil
end
# Obtain the UDP ports for the given SuSEFIrewall2 zone name from the
# profile.
#
# @param zone [String]
# @return [Array<Strint>, nil] list of configured UDP ports; nil if no
# configured
def udp_ports(zone)
ports = profile["FW_SERVICES_#{zone}_UDP"]
ports ? ports.split.map { |p| "#{p.sub(":", "-")}/udp" } : nil
end
# Obtain the RPC ports for the given SuSEFIrewall2 zone name from the
# profile.
#
# @param zone [String]
# @return [Array<String>, nil] list of configured RPC ports; nil if no
# configured
def rpc_ports(zone)
ports = profile["FW_SERVICES_#{zone}_RPC"]
ports ? ports.split.map { |p| ["#{p}/udp", "#{p}/tcp"] }.flatten : nil
end
# Given a SuSEFirewall2 zone name return the firewalld zone equivalent
# name. It takes in account whether masquerade is enable or not.
#
# @param name [String] SuSEFirewall2 zone name
# @return [String] equivalent firewalld zone name
def zone_equivalent(name)
case name.upcase
when "INT"
trusted? ? "trusted" : "internal"
when "EXT"
masquerade? ? "external" : "public"
when "DMZ"
"dmz"
end
end
# Return whether internal network is trusted or not
#
# @return [Boolean] true if trusted; false otherwise
def trusted?
profile.fetch("FW_PROTECT_FROM_INT", "no") == "no"
end
# Return whether masquerade is configured or not
#
# @return [Boolean] true if configured; false otherwise
def masquerade?
profile.fetch("FW_MASQUERADE", "no") == "yes"
end
# Return the ipsec trust zone name if configured or nil
#
# @return [Boolean] true if configured; false otherwise
def ipsec_trust_zone
zone_name = profile.fetch("FW_IPSEC_TRUST", "no").downcase
return if zone_name == "no"
return "int" if zone_name == "yes"
zone_name
end
# Return which denied packets to log that match better with the
# SuSEFirewall logging config.
#
# @return [String] all, unicast or off depending on the log config
def log_denied_packets
return "all" if profile.fetch("FW_LOG_DROP_ALL", "no") == "yes"
return "unicast" if profile.fetch("FW_LOG_DROP_CRIT", "no") == "yes"
"off"
end
# Convenience method which return an instance of Y2Firewall::Firewalld
#
# @return [Y2Firewall::Firewalld] a firewalld instance
def firewalld
Y2Firewall::Firewalld.instance
end
end
end
end