• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / yast-security / 7348696296

28 Dec 2023 02:47PM UTC coverage: 39.237% (-0.4%) from 39.615%
7348696296

push

github

jreidinger
rubocop: enable Layout/LineLength - manual fixes

3 of 111 new or added lines in 3 files covered. (2.7%)

418 existing lines in 13 files now uncovered.

1378 of 3512 relevant lines covered (39.24%)

5.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.6
/src/modules/Security.rb
1
# ------------------------------------------------------------------------------
2
# Copyright (c) 2006-2012 Novell, Inc. All Rights Reserved.
3
#
4
#
5
# This program is free software; you can redistribute it and/or modify it under
6
# the terms of version 2 of the GNU General Public License as published by the
7
# Free Software Foundation.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License along with
14
# this program; if not, contact Novell, Inc.
15
#
16
# To contact Novell about this file by physical or electronic mail, you may find
17
# current contact information at www.novell.com.
18
# ------------------------------------------------------------------------------
19

20
# File:        modules/Security.ycp
21
# Package:        Security configuration
22
# Summary:        Data for the security configuration
23
# Authors:        Michal Svec <msvec@suse.cz>
24
#
25
# $Id$
26
require "yast"
1✔
27
require "yast2/systemd/service"
1✔
28
require "cfa/sysctl_config"
1✔
29
require "cfa/shadow_config"
1✔
30
require "yaml"
1✔
31
require "security/ctrl_alt_del_config"
1✔
32
require "security/display_manager"
1✔
33
require "y2security/autoinst/lsm_config_reader"
1✔
34
require "y2security/security_policies"
1✔
35
require "y2security/security_policies/unknown_rule"
1✔
36
require "y2security/autoinst_profile"
1✔
37

38
module Yast
1✔
39
  class SecurityClass < Module # rubocop:disable Metrics/ClassLength
1✔
40
    DEFAULT_ENCRYPT_METHOD = "sha512".freeze
1✔
41
    private_constant :DEFAULT_ENCRYPT_METHOD
1✔
42

43
    include Yast::Logger
1✔
44
    include ::Security::CtrlAltDelConfig
1✔
45

46
    SYSCTL_VALUES_TO_BOOLEAN = {
1✔
47
      "yes" => true,
48
      "no"  => false
49
    }.freeze
50
    SYSCTL_VALUES_TO_INTSTRING = {
51
      "yes" => "1",
1✔
52
      "no"  => "0"
53
    }.freeze
54

55
    SHADOW_ATTRS = [
56
      "FAIL_DELAY",
1✔
57
      "GID_MAX",
58
      "GID_MIN",
59
      "PASS_MAX_DAYS",
60
      "PASS_MIN_DAYS",
61
      "PASS_WARN_AGE",
62
      "UID_MAX",
63
      "UID_MIN",
64
      "SYS_UID_MAX",
65
      "SYS_UID_MIN",
66
      "SYS_GID_MAX",
67
      "SYS_GID_MIN",
68
      "USERADD_CMD",
69
      "USERDEL_PRECMD",
70
      "USERDEL_POSTCMD"
71
    ].freeze
72

73
    attr_reader :display_manager
1✔
74

75
    def main
1✔
76
      import_modules
75✔
77

78
      textdomain "security"
75✔
79

80
      init_settings
75✔
81
    end
82

83
    def import_modules
1✔
84
      Yast.import "UI"
75✔
85
      Yast.import "FileUtils"
75✔
86
      Yast.import "Package"
75✔
87
      Yast.import "Pkg"
75✔
88
      Yast.import "Pam"
75✔
89
      Yast.import "Progress"
75✔
90
      Yast.import "Service"
75✔
91
      Yast.import "Directory"
75✔
92
      Yast.import "Report"
75✔
93
      Yast.import "PackagesProposal"
75✔
94
      Yast.include self, "security/levels.rb"
75✔
95
    end
96

97
    def init_settings
1✔
98
      # Services to check
99
      srv_file = Directory.find_data_file("security/services.yml")
85✔
100
      srv_lists = if srv_file
85✔
101
        begin
102
          YAML.load_file(srv_file)
85✔
103
        rescue StandardError => e
UNCOV
104
          log.warn "Failed to load #{srv_file}. Error: #{e.inspect}"
×
105
          {}
×
106
        end
107
      else
UNCOV
108
        {}
×
109
      end
110

111
      # These must be running
112
      @mandatory_services = srv_lists["mandatory_services"] || []
85✔
113
      # It must be an array of arrays (meaning [ [ || ] && && ])
114
      @mandatory_services.map! { |s| s.is_a?(::String) ? [s] : s }
425✔
115
      # These can be ignored (if they are running it's OK)
116
      @optional_services = srv_lists["optional_services"] || []
85✔
117
      # All other services should be turned off
118

119
      @display_manager = ::Security::DisplayManager.current
85✔
120

121
      # systemd target, defining ctrl-alt-del behavior
122
      @ctrl_alt_del_file = ::Security::CtrlAltDelConfig::SYSTEMD_FILE
85✔
123

124
      # encryption methods supported by pam_unix (bnc#802006)
125
      @encryption_methods = ["des", "md5", "sha256", "sha512"]
85✔
126

127
      # All security settings
128
      @Settings = {
129
        "CONSOLE_SHUTDOWN"                          => ::Security::CtrlAltDelConfig.default,
85✔
130
        "CRACKLIB_DICT_PATH"                        => "/usr/lib/cracklib_dict",
131
        "DISPLAYMANAGER_REMOTE_ACCESS"              => "no",
132
        "kernel.sysrq"                              => "0",
133
        "net.ipv4.tcp_syncookies"                   => true,
134
        "net.ipv4.ip_forward"                       => false,
135
        "net.ipv6.conf.all.forwarding"              => false,
136
        "FAIL_DELAY"                                => "3",
137
        "GID_MAX"                                   => "60000",
138
        "GID_MIN"                                   => "1000",
139
        "HIBERNATE_SYSTEM"                          => "active_console",
140
        "PASSWD_ENCRYPTION"                         => "sha512",
141
        "PASSWD_USE_PWQUALITY"                      => "yes",
142
        "PASS_MAX_DAYS"                             => "99999",
143
        "PASS_MIN_DAYS"                             => "0",
144
        "PASS_MIN_LEN"                              => "5",
145
        "PASS_WARN_AGE"                             => "7",
146
        "PERMISSION_SECURITY"                       => "secure",
147
        "DISABLE_RESTART_ON_UPDATE"                 => "no",
148
        "DISABLE_STOP_ON_REMOVAL"                   => "no",
149
        "RUN_UPDATEDB_AS"                           => "nobody",
150
        "UID_MAX"                                   => "60000",
151
        "UID_MIN"                                   => "500",
152
        "SYS_UID_MAX"                               => "499",
153
        "SYS_UID_MIN"                               => "100",
154
        "SYS_GID_MAX"                               => "499",
155
        "SYS_GID_MIN"                               => "100",
156
        "USERADD_CMD"                               => "/usr/sbin/useradd.local",
157
        "USERDEL_PRECMD"                            => "/usr/sbin/userdel-pre.local",
158
        "USERDEL_POSTCMD"                           => "/usr/sbin/userdel-post.local",
159
        "PASSWD_REMEMBER_HISTORY"                   => "0",
160
        "SYSLOG_ON_NO_ERROR"                        => "yes",
161
        "DISPLAYMANAGER_ROOT_LOGIN_REMOTE"          => "no",
162
        "DISPLAYMANAGER_XSERVER_TCP_PORT_6000_OPEN" => "no",
163
        "SMTPD_LISTEN_REMOTE"                       => "no",
164
        "MANDATORY_SERVICES"                        => "yes",
165
        "EXTRA_SERVICES"                            => "no"
166
      }
167

168
      @Settings.merge!(@display_manager.default_settings) if @display_manager
85✔
169

170
      # List of missing mandatory services
171
      @missing_mandatory_services = []
85✔
172
      # List of enabled services not included in mandatory or optional lists
173
      @extra_services = []
85✔
174

175
      # the original settings
176
      @Settings_bak = deep_copy(@Settings)
85✔
177

178
      # keys that should not be tested against predefined levels:
179
      # - *_SERVICES have different syntax, are not saved in current form
180
      @do_not_test = [
181
        "MANDATORY_SERVICES",
85✔
182
        "EXTRA_SERVICES"
183
      ]
184

185
      # Security settings locations
186
      @Locations = {
187
        ".sysconfig.security" => ["PERMISSION_SECURITY"],
85✔
188
        ".sysconfig.services" => [
189
          "DISABLE_RESTART_ON_UPDATE",
190
          "DISABLE_STOP_ON_REMOVAL"
191
        ],
192
        ".sysconfig.locate"   => ["RUN_UPDATEDB_AS"],
193
        ".sysconfig.cron"     => ["SYSLOG_ON_NO_ERROR"],
194
        ".sysconfig.mail"     => ["SMTPD_LISTEN_REMOTE"]
195
      }
196

197
      @Locations.merge!(@display_manager.default_locations) if @display_manager
85✔
198

199
      # Default values for /etc/sysctl.conf keys
200
      @sysctl = {
201
        "kernel.sysrq"                 => "0",
85✔
202
        "net.ipv4.tcp_syncookies"      => true,
203
        "net.ipv4.ip_forward"          => false,
204
        "net.ipv6.conf.all.forwarding" => false
205
      }
206

207
      # Mapping of /etc/sysctl.conf keys to old (obsoleted) sysconfig ones
208
      # (used during autoYaST import)
209
      @sysctl2sysconfig = {
210
        "kernel.sysrq"                 => "ENABLE_SYSRQ",
85✔
211
        "net.ipv4.tcp_syncookies"      => "IP_TCP_SYNCOOKIES",
212
        "net.ipv4.ip_forward"          => "IP_FORWARD",
213
        "net.ipv6.conf.all.forwarding" => "IPV6_FORWARD"
214
      }
215

216
      # Mapping of /etc/login.defs keys to old (obsoleted) ones
217
      # (used during autoYaST import)
218
      @obsolete_login_defs = {
219
        "SYS_UID_MAX" => "SYSTEM_UID_MAX",
85✔
220
        "SYS_UID_MIN" => "SYSTEM_UID_MIN",
221
        "SYS_GID_MAX" => "SYSTEM_GID_MAX",
222
        "SYS_GID_MIN" => "SYSTEM_GID_MIN"
223
      }
224

225
      # mapping of internal YaST values to values needed for
226
      # org.freedesktop.upower.hibernate privilege
227
      @ycp2polkit = {
228
        "active_console" => "auth_admin:auth_admin:yes",
85✔
229
        "auth_admin"     => "auth_admin:auth_admin:auth_admin",
230
        "anyone"         => "yes:yes:yes"
231
      }
232

233
      # Remaining settings:
234
      # - PASSWD_ENCRYPTION (/etc/pam?)
235
      # - MANDATORY_SERVICES
236
      # - EXTRA_SERVICES
237

238
      # Number of sigificant characters in the password
239
      @PasswordMaxLengths = {
85✔
240
        "des"    => 8,
241
        "md5"    => 127,
242
        "sha256" => 127,
243
        "sha512" => 127
244
      }
245

246
      # Abort function
247
      # return boolean return true if abort
248
      @AbortFunction = nil
85✔
249

250
      # Data was modified?
251
      @modified = false
85✔
252

253
      @proposal_valid = false
85✔
254
      @write_only = false
85✔
255

256
      # Force reading of sysctl configuration
257
      @sysctl_config = nil
85✔
258

259
      @activation_mapping = {
260
        "DHCPD_RUN_CHROOTED"           => "/usr/bin/systemctl try-restart dhcpd.service",
85✔
261
        "DHCPD_RUN_AS"                 => "/usr/bin/systemctl try-restart dhcpd.service",
262
        # restart sendmail or postfix - whatever is installed
263
        "SMTPD_LISTEN_REMOTE"          => "/usr/bin/systemctl try-restart sendmail postfix",
264
        "net.ipv4.tcp_syncookies"      => "/usr/bin/systemctl try-restart network",
265
        "net.ipv4.ip_forward"          => "/usr/bin/systemctl try-restart network",
266
        "net.ipv6.conf.all.forwarding" => "/usr/bin/systemctl try-restart network"
267
      }
268

269
      @shadow_config = nil
85✔
270
    end
271

272
    # List of missing mandatory services
273
    def MissingMandatoryServices
1✔
274
      @missing_mandatory_services
5✔
275
    end
276

277
    # List of enabled services that are neither mandatory nor optional
278
    def ExtraServices
1✔
279
      @extra_services
5✔
280
    end
281

282
    # Check for pending Abort press
283
    # @return true if pending abort
284
    def PollAbort
1✔
UNCOV
285
      UI.PollInput == :abort
×
286
    end
287

288
    # Abort function
289
    # @return blah blah lahjk
290
    def Abort
1✔
291
      return Builtins.eval(@AbortFunction) == true if !@AbortFunction.nil?
14✔
292

293
      false
14✔
294
    end
295

296
    # Function which returns if the settings were modified
297
    # @return [Boolean]  settings were modified
298
    def GetModified
1✔
UNCOV
299
      @modified
×
300
    end
301

302
    # Function sets internal variable, which indicates, that any
303
    # settings were modified, to "true"
304
    def SetModified
1✔
UNCOV
305
      @modified = true
×
306

307
      nil
308
    end
309

310
    # Data was modified?
311
    # @return true if modified
312
    def Modified
1✔
UNCOV
313
      Builtins.y2debug("modified=%1", @modified)
×
UNCOV
314
      @modified
×
315
    end
316

317
    def ReadServiceSettings
1✔
318
      read_missing_mandatory_services
5✔
319
      setting = MissingMandatoryServices() == [] ? "secure" : "insecure"
5✔
320
      @Settings["MANDATORY_SERVICES"] = setting
5✔
321
      read_extra_services
5✔
322
      setting = ExtraServices() == [] ? "secure" : "insecure"
5✔
323
      @Settings["EXTRA_SERVICES"] = setting
5✔
324

325
      nil
326
    end
327

328
    # Read the information about ctrl+alt+del behavior
329
    # See bug 742783 for description
330
    def ReadConsoleShutdown
1✔
331
      @Settings["CONSOLE_SHUTDOWN"] =
10✔
332
        ::Security::CtrlAltDelConfig.current || ::Security::CtrlAltDelConfig.default
333
    end
334

335
    # Read the settings from the files included in @Locations
336
    def read_from_locations
1✔
337
      # NOTE: the call to #sort is only needed to satisfy the old testsuite
338
      @Locations.sort.each do |file, vars|
10✔
339
        vars.each do |var|
68✔
340
          val = ""
98✔
341
          filename = nil
98✔
342
          if file.include?("sysconfig")
98✔
343
            filename = "/etc" + file.tr(".", "/")
90✔
344
            log.info "filename=#{filename}"
90✔
345
          end
346
          if filename.nil? || SCR.Read(path(".target.size"), filename) > 0
98✔
347
            val = SCR.Read(path("#{file}.#{var}"))
98✔
348
            log.debug "Reading: #{file}.#{var} (#{val})"
98✔
349
          end
350
          @Settings[var] = val unless val.nil?
98✔
351
        end
352
      end
353

354
      log.debug "Settings (after #{__callee__}): #{@Settings}"
10✔
355
    end
356

357
    # Reads login.defs configuration
358
    def read_shadow_config
1✔
359
      SHADOW_ATTRS.each do |attr|
2✔
360
        value = shadow_config.public_send(attr.downcase)
30✔
361
        next if value.nil?
30✔
362

363
        @Settings[attr] = shadow_config.public_send(attr.downcase)
1✔
364
      end
365
      log.debug "Settings (after #{__callee__}): #{@Settings}"
2✔
366
    end
367

368
    # Read the settings from sysctl.conf
369
    def read_kernel_settings
1✔
370
      # NOTE: the call to #sort is only needed to satisfy the old testsuite
371
      @sysctl.sort.each do |key, default_value|
7✔
372
        val = read_sysctl_value(key)
28✔
373
        val = default_value if val.nil? || val == ""
28✔
374
        @Settings[key] = val
28✔
375
      end
376

377
      log.debug "Settings (after #{__callee__}): #{@Settings}"
7✔
378
    end
379

380
    # Reads the Linux Security Module configuration
381
    def read_lsm_config
1✔
382
      lsm_config.read
1✔
383
    end
384

385
    def read_encryption_method
1✔
386
      method = shadow_config.encrypt_method.to_s.downcase
3✔
387

388
      method = "sha512" if !@encryption_methods.include?(method)
3✔
389

390
      @Settings["PASSWD_ENCRYPTION"] = method
3✔
391
    end
392

393
    def read_pam_settings
1✔
394
      read_encryption_method
3✔
395

396
      # pwquality and pwhistory settings (default values)
397
      @Settings["PASS_MIN_LEN"] = "5"
3✔
398
      @Settings["PASSWD_REMEMBER_HISTORY"] = "0"
3✔
399
      @Settings["CRACKLIB_DICT_PATH"] = "/usr/lib/cracklib_dict"
3✔
400

401
      pam_pwquality = Pam.Query(pwquality_module) || {}
3✔
402
      @Settings["PASSWD_USE_PWQUALITY"] = pam_pwquality.empty? ? "no" : "yes"
3✔
403

404
      pam_pwquality.fetch("password", []).each do |entry|
3✔
405
        key, value = entry.split("=")
2✔
406
        if value
2✔
407
          @Settings["CRACKLIB_DICT_PATH"] = value if key == "dictpath"
1✔
408
          @Settings["PASS_MIN_LEN"]       = value if key == "minlen"
1✔
409
        end
410
      end
411

412
      pam_history = Pam.Query("pwhistory") || {}
3✔
413
      pam_history.fetch("password", []).each do |entry|
3✔
414
        key, value = entry.split("=")
1✔
415
        @Settings["PASSWD_REMEMBER_HISTORY"] = value if key == "remember" && value
1✔
416
      end
417
      log.debug "Settings (after #{__callee__}): #{@Settings}"
3✔
418
    end
419

420
    def read_permissions
1✔
421
      # Removing "local" from the string
422
      permissions = @Settings["PERMISSION_SECURITY"].to_s.split
4✔
423
      @Settings["PERMISSION_SECURITY"] = permissions.delete_if do |p|
4✔
424
        p == "local"
6✔
425
      end.join(" ")
426

427
      # default value
428
      @Settings["PERMISSION_SECURITY"] = "secure" if @Settings["PERMISSION_SECURITY"].empty?
4✔
429

430
      log.debug "PERMISSION_SECURITY (after #{__callee__}): " \
4✔
431
                "#{@Settings["PERMISSION_SECURITY"]}"
432

433
      @Settings["PERMISSION_SECURITY"]
4✔
434
    end
435

436
    def read_polkit_settings
1✔
437
      action = "org.freedesktop.upower.hibernate"
3✔
438

439
      hibernate = SCR.Read(Builtins.add(path(".etc.polkit-default-privs_local"), action)).to_s
3✔
440

441
      @Settings["HIBERNATE_SYSTEM"] = case hibernate
3✔
442
      when "auth_admin:auth_admin:auth_admin"
443
        "auth_admin"
1✔
444
      when "yes:yes:yes"
445
        "anyone"
1✔
446
      else
447
        "active_console"
1✔
448
      end
449
      log.debug "HIBERNATE_SYSTEM (after #{__callee__}): " \
3✔
450
                "#{@Settings["HIBERNATE_SYSTEM"]}"
451
    end
452

453
    # The name of the PAM module to deal with password quality. Either
454
    # "pwquality" or "cracklib". See bug #1171318 why this is needed.
455
    def pwquality_module
1✔
456
      return @mod_name if @mod_name
9✔
457

458
      # Both pwquality and cracklib can be installed. in that case
459
      # cracklib seems to be a non-functional deprecated module. So
460
      # prefer pwquality.
461
      @mod_name = Pam.List.include?("pwquality") ? "pwquality" : "cracklib"
1✔
462
    end
463

464
    # Read all security settings
465
    #
466
    # @raise [Exception] if there is an issue while reading the settings
467
    # @return [Boolean] true on success
468
    def Read
1✔
469
      @Settings = {}
1✔
470
      @modified = false
1✔
471

472
      # Read security settings
473
      read_from_locations
1✔
474
      read_shadow_config
1✔
475

476
      ReadConsoleShutdown()
1✔
477

478
      log.debug "Settings (after read console shutdown): #{@Settings}"
1✔
479

480
      # Read runlevel setting
481
      ReadServiceSettings()
1✔
482

483
      read_pam_settings
1✔
484

485
      # Local permissions hack
486
      read_permissions
1✔
487

488
      read_polkit_settings
1✔
489

490
      read_kernel_settings
1✔
491

492
      read_lsm_config
1✔
493

494
      # remember the read values
495
      @Settings_bak = deep_copy(@Settings)
1✔
496

497
      log.info "Settings after Read: #{@Settings}"
1✔
498
      true
1✔
499
    end
500

501
    # Reads all security settings without raising exceptions
502
    #
503
    # This method saves any error produced while reading the settings instead of raising an
504
    # exception. This is needed when the module is used from a perl module because the exceptions
505
    # are not propagated to perl code.
506
    #
507
    # @return [Boolean] true on success. When false, the error message can be check by calling to
508
    #   Security#read_error.
509
    def SafeRead
1✔
510
      @read_error = nil
5✔
511
      self.Read
5✔
512
      true
3✔
513
    rescue StandardError => e
514
      @read_error = e.message
2✔
515
      false
2✔
516
    end
517

518
    # Write the value of ctrl-alt-delete behavior
519
    def write_console_shutdown(ca)
1✔
520
      case ca
1✔
521
      when "reboot"
UNCOV
522
        SCR.Execute(path(".target.remove"), @ctrl_alt_del_file)
×
523
      when "halt"
UNCOV
524
        SCR.Execute(
×
525
          path(".target.bash"),
526
          Builtins.sformat(
527
            "ln -s -f /usr/lib/systemd/system/poweroff.target %1",
528
            @ctrl_alt_del_file
529
          )
530
        )
531
      else
532
        SCR.Execute(
1✔
533
          path(".target.bash"),
534
          Builtins.sformat("ln -s -f /dev/null %1", @ctrl_alt_del_file)
535
        )
536
      end
537
      true
1✔
538
    end
539

540
    # Write the settings from @Locations to the corresponding files
541
    def write_to_locations
1✔
542
      commitlist = []
5✔
543
      # NOTE: the call to #sort is only needed to satisfy the old testsuite
544
      @Locations.sort.each do |file, vars|
5✔
545
        vars.each do |var|
33✔
546
          val = @Settings[var]
46✔
547
          if val && val != SCR.Read(path("#{file}.#{var}"))
46✔
548
            SCR.Write(path("#{file}.#{var}"), val)
7✔
549
            commitlist << file unless commitlist.include?(file)
7✔
550
          end
551
        end
552
      end
553
      commitlist.each do |file|
5✔
554
        SCR.Write(path(file), nil)
7✔
555
      end
556
    end
557

558
    # Write login.defs configuration
559
    def write_shadow_config
1✔
560
      SHADOW_ATTRS.each do |attr|
3✔
561
        # bsc#1208492 shadow config uses login.defs attr formatting
562
        # like <Key><space>*<Value>, so empty value is not supported
563
        # and moreover can cause crash in login.defs lens
564
        next if @Settings[attr].nil? || @Settings[attr].empty?
45✔
565

566
        shadow_config.public_send("#{attr.to_s.downcase}=", @Settings[attr])
44✔
567
      end
568
      encr = @Settings.fetch("PASSWD_ENCRYPTION", default_encrypt_method)
3✔
569
      shadow_config.encrypt_method = encr if encr != @Settings_bak["PASSWD_ENCRYPTION"]
3✔
570
      shadow_config.save
3✔
571
    end
572

573
    # Writes the current Linux Security Module Configuration
574
    #
575
    # @see Y2Security:.LSM::Config#save
576
    # @return [Boolean] whether the configuration was saved or not
577
    def write_lsm_config
1✔
578
      lsm_config.save
2✔
579
    end
580

581
    # Write settings related to PAM behavior
582
    def write_pam_settings
1✔
583
      # use pwquality?
584
      if @Settings["PASSWD_USE_PWQUALITY"] == "yes"
1✔
585
        Pam.Add(pwquality_module)
1✔
586
        pth = @Settings["CRACKLIB_DICT_PATH"]
1✔
587
        Pam.Add(pwquality_module + "-dictpath=#{pth}") if pth && pth != "/usr/lib/cracklib_dict"
1✔
588
      else
UNCOV
589
        Pam.Remove(pwquality_module)
×
590
      end
591

592
      # save min pass length
593
      min_len = @Settings["PASS_MIN_LEN"]
1✔
594
      if min_len && min_len != "5" && @Settings["PASSWD_USE_PWQUALITY"] == "yes"
1✔
UNCOV
595
        Pam.Add(pwquality_module) # minlen is part of pwquality
×
596
        Pam.Add(pwquality_module + "-minlen=#{min_len}")
×
597
      else
598
        Pam.Remove(pwquality_module + "-minlen")
1✔
599
      end
600

601
      # save "remember" value (number of old user passwords to not allow)
602
      remember_history = @Settings["PASSWD_REMEMBER_HISTORY"]
1✔
603
      if remember_history && remember_history != "0"
1✔
UNCOV
604
        Pam.Add("pwhistory")
×
605
        Pam.Add("pwhistory-remember=#{remember_history}")
×
606
      else
607
        Pam.Remove("pwhistory-remember")
1✔
608
      end
609
    end
610

611
    # Write settings related to sysctl.conf and sysrq
612
    def write_kernel_settings
1✔
613
      # write sysctl.conf
614
      written = false
7✔
615
      # NOTE: the call to #sort is only needed to satisfy the old testsuite
616
      @sysctl.sort.each do |key, default_value|
7✔
617
        val = @Settings.fetch(key, default_value)
28✔
618
        int_val = begin
619
          Integer(val)
28✔
620
        rescue StandardError
621
          nil
23✔
622
        end
623
        if int_val.nil? && ![TrueClass, FalseClass].include?(val.class)
28✔
624
          log.error "value #{val} for #{key} has wrong type, not writing"
3✔
625
        elsif val != read_sysctl_value(key)
25✔
626
          write_sysctl_value(key, val)
3✔
627
          written = true
3✔
628
        end
629
      end
630

631
      # In case of modified, always write the changes (bsc#1167234)
632
      sysctl_config.save if written
7✔
633
      written
7✔
634
    end
635

636
    # Write local PolicyKit configuration
637
    def write_polkit_settings
1✔
638
      if @Settings.fetch("HIBERNATE_SYSTEM", "") !=
1✔
639
          @Settings_bak.fetch("HIBERNATE_SYSTEM", "")
640
        # allow writing any value (different from predefined ones)
641
        ycp_value = @Settings.fetch("HIBERNATE_SYSTEM", "active_console")
×
UNCOV
642
        hibernate = @ycp2polkit.fetch(ycp_value, ycp_value)
×
UNCOV
643
        action = "org.freedesktop.upower.hibernate"
×
UNCOV
644
        SCR.Write(
×
645
          path(".etc.polkit-default-privs_local") + action,
646
          hibernate
647
        )
648
      end
649
    end
650

651
    # Apply sysctl settings from all the sysctl configuration files
652
    def apply_sysctl_changes
1✔
653
      # Reports if there are conflict when the configuration is applied
654
      sysctl_config.conflict?
3✔
655

656
      Yast::Execute.on_target("/usr/sbin/sysctl", "--system")
3✔
657
    end
658

659
    # Ensures that sysctl changes, file permissions and PolicyKit privileges
660
    # are applied
661
    #
662
    # @param sysctl [Boolean] whether sysctl changes should be applied or not
663
    def apply_new_settings(sysctl: false)
1✔
664
      # Apply sysctl changes to the running system (bsc#1167234)
665
      apply_sysctl_changes if sysctl
5✔
666
      # apply all current permissions as they are now
667
      # (what SuSEconfig --module permissions would have done)
668
      SCR.Execute(path(".target.bash"), "/usr/bin/chkstat --system")
5✔
669

670
      # ensure polkit privileges are applied (bnc #541393)
671
      polkit_exec = "/sbin/set_polkit_default_privs"
5✔
672
      SCR.Execute(path(".target.bash"), polkit_exec) if FileUtils.Exists(polkit_exec)
5✔
673
    end
674

675
    # Executes the corresponding activation command for the settings that have
676
    # an entry in @activation_mapping and have changed
677
    def activate_changes
1✔
678
      # NOTE: the call to #sort is only needed to satisfy the old testsuite
679
      @activation_mapping.sort.each do |setting, action|
1✔
680
        next if @Settings[setting] == @Settings_bak[setting]
6✔
681

UNCOV
682
        log.info(
×
683
          "Option #{setting} has been modified, "\
684
          "activating the change: #{action}"
685
        )
UNCOV
686
        res = SCR.Execute(path(".target.bash"), action)
×
UNCOV
687
        log.error "Activation failed" if res != 0
×
688
      end
689
    end
690

691
    # Write all security settings
692
    # @return true on success
693
    def Write
1✔
694
      return true if !@modified
2✔
695

696
      log.info "Writing configuration"
2✔
697

698
      # Security read dialog caption
699
      caption = _("Saving Security Configuration")
2✔
700
      steps = 5
2✔
701

702
      Progress.New(
2✔
703
        caption,
704
        " ",
705
        steps,
706
        [
707
          # Progress stage 1/5
708
          _("Write security settings"),
709
          # Progress stage 2/5
710
          _("Write shutdown settings"),
711
          # Progress stage 3/5
712
          _("Write PAM settings"),
713
          # Progress stage 4/5
714
          _("Update system settings"),
715
          # Progress stage 5/5
716
          _("Write SELinux settings")
717
        ],
718
        [
719
          # Progress step 1/6
720
          _("Writing security settings..."),
721
          # Progress step 2/6
722
          _("Writing shutdown settings..."),
723
          # Progress step 3/6
724
          _("Writing PAM settings..."),
725
          # Progress step 4/6
726
          _("Updating system settings..."),
727
          # Progress step 5/6
728
          _("Writing  settings..."),
729
          # Progress step 6/6
730
          _("Finished")
731
        ],
732
        ""
733
      )
734

735
      log.debug "Settings=#{@Settings}"
2✔
736

737
      # Write security settings
738
      return false if Abort()
2✔
739

740
      Progress.NextStage
2✔
741
      if !@Settings["PERMISSION_SECURITY"].include?("local")
2✔
742
        @Settings["PERMISSION_SECURITY"] << " local"
2✔
743
      end
744
      write_to_locations
2✔
745
      write_shadow_config
2✔
746

747
      # Write shutdown settings
748
      return false if Abort()
2✔
749

750
      Progress.NextStage
2✔
751
      write_console_shutdown(@Settings.fetch("CONSOLE_SHUTDOWN", "ignore"))
2✔
752

753
      # Write authentication and privileges settings
754
      return false if Abort()
2✔
755

756
      Progress.NextStage
2✔
757
      write_pam_settings
2✔
758
      write_polkit_settings
2✔
759
      sysctl_modified = write_kernel_settings
2✔
760

761
      # Finish him
762
      return false if Abort()
2✔
763

764
      Progress.NextStage
2✔
765
      apply_new_settings(sysctl: sysctl_modified)
2✔
766

767
      return false if Abort()
2✔
768

769
      Progress.NextStage
2✔
770
      activate_changes
2✔
771

772
      return false if Abort()
2✔
773

774
      Progress.NextStage
2✔
775
      write_lsm_config
2✔
776

777
      return false if Abort()
2✔
778

779
      @modified = false
2✔
780
      true
2✔
781
    end
782

783
    # Get all security settings from the first parameter
784
    # (For use by autoinstallation.)
785
    # @param [Hash] settings The YCP structure to be imported.
786
    # @return [Boolean] True on success
787
    def Import(settings)
1✔
788
      settings = deep_copy(settings)
12✔
789
      settings["kernel.sysrq"] = settings.delete("KERNEL.SYSRQ") if settings.key?("KERNEL.SYSRQ")
12✔
790
      if settings.key?("NET.IPV4.TCP_SYNCOOKIES")
12✔
791
        settings["net.ipv4.tcp_syncookies"] = settings.delete("NET.IPV4.TCP_SYNCOOKIES")
×
792
      end
793
      if settings.key?("NET.IPV4.IP_FORWARD")
12✔
794
        settings["net.ipv4.ip_forward"] = settings.delete("NET.IPV4.IP_FORWARD")
×
795
      end
796
      if settings.key?("NET.IPV6.CONF.ALL.FORWARDING")
12✔
797
        settings["net.ipv6.conf.all.forwarding"] = settings.delete("NET.IPV6.CONF.ALL.FORWARDING")
×
798
      end
799

800
      # conversion to true/false
801
      ["net.ipv4.tcp_syncookies", "net.ipv4.ip_forward",
12✔
802
       "net.ipv6.conf.all.forwarding"].each do |key|
803
        settings[key] = settings[key] == "1" if settings.key?(key) && settings[key].is_a?(::String)
36✔
804
      end
805

806
      if settings.key?("PASSWD_USE_CRACKLIB")
12✔
UNCOV
807
        settings["PASSWD_USE_PWQUALITY"] = settings.delete("PASSWD_USE_CRACKLIB")
×
808
      end
809

810
      settings["lsm_select"] = settings.delete("LSM_SELECT") if settings.key?("LSM_SELECT")
12✔
811
      settings["selinux_mode"] = settings.delete("SELINUX_MODE") if settings.key?("SELINUX_MODE")
12✔
812
      if settings.key?("SECURITY_POLICY")
12✔
813
        settings["security_policy"] = settings.delete("SECURITY_POLICY")
3✔
814
      end
815

816
      section = Y2Security::AutoinstProfile::SecuritySection.new_from_hashes(settings)
12✔
817
      import_lsm_config(section)
12✔
818
      import_security_policy(section.security_policy)
12✔
819

820
      return true if settings == {}
12✔
821

822
      @modified = true
11✔
823
      tmpSettings = {}
11✔
824
      @Settings.each do |k, v|
11✔
825
        if settings.key?(k)
407✔
826
          tmpSettings[k] = settings[k]
3✔
827
        elsif @sysctl.key?(k) && settings.key?(@sysctl2sysconfig[k])
404✔
828
          val = settings[@sysctl2sysconfig[k]].to_s
1✔
829
          tmpSettings[k] = if @sysctl[k].is_a?(TrueClass) || @sysctl[k].is_a?(FalseClass)
1✔
830
            SYSCTL_VALUES_TO_BOOLEAN.key?(val) ? SYSCTL_VALUES_TO_BOOLEAN[val] : val
1✔
831
          else
UNCOV
832
            SYSCTL_VALUES_TO_INTSTRING.key?(val) ? SYSCTL_VALUES_TO_INTSTRING[val] : val
×
833
          end
834
        # using the old sysconfig AY format
835
        else
836
          # using old login defs settings ?
837
          tmpSettings[k] = settings[@obsolete_login_defs[k]] || v
403✔
838
        end
839
      end
840

841
      @Settings = tmpSettings
11✔
842
      true
11✔
843
    end
844

845
    # Dump the security settings to a single map
846
    # (For use by autoinstallation.)
847
    # @return [Hash] Dumped settings (later acceptable by Import ())
848
    def Export
1✔
849
      settings = deep_copy(@Settings)
4✔
850
      # conversion to 0/1 string
851
      ["net.ipv4.tcp_syncookies", "net.ipv4.ip_forward",
4✔
852
       "net.ipv6.conf.all.forwarding"].each do |key|
853
        if [TrueClass, FalseClass].include?(settings[key].class)
12✔
854
          settings[key] = settings[key] ? "1" : "0"
12✔
855
        end
856
      end
857

858
      if pwquality_module == "cracklib"
4✔
UNCOV
859
        settings["PASSWD_USE_CRACKLIB"] = settings.delete("PASSWD_USE_PWQUALITY")
×
860
      end
861

862
      merged_settings = settings.merge(lsm_config.export)
4✔
863
      security_policy = export_security_policy
4✔
864
      merged_settings.merge!("security_policy" => security_policy) unless security_policy.empty?
4✔
865
      merged_settings
4✔
866
    end
867

868
    # Create a textual summary and a list of unconfigured cards
869
    # @return summary of the current configuration
870
    def Summary
1✔
UNCOV
871
      settings = deep_copy(@Settings)
×
UNCOV
872
      Builtins.foreach(@do_not_test) do |key|
×
UNCOV
873
        settings = Builtins.remove(settings, key)
×
874
      end
875

876
      # Determine current settings
877
      current = :custom
×
UNCOV
878
      Builtins.maplist(@Levels) do |key, level|
×
UNCOV
879
        Builtins.y2debug("%1=%2", key, level)
×
UNCOV
880
        current = key if level == settings
×
881
      end
882
      Builtins.y2debug("%1=%2", current, @Settings)
×
883

884
      # Summary text
UNCOV
885
      summary = _("Current Security Level: Custom settings")
×
886
      if current != :custom
×
887
        # Summary text
UNCOV
888
        summary = Builtins.sformat(
×
889
          _("Current Security Level: %1"),
890
          Ops.get(@LevelsNames, Convert.to_string(current), "")
891
        )
892
      end
893

UNCOV
894
      [summary, []]
×
895
    end
896

897
    # Create an overview table with all configured cards
898
    # @return table items
899
    def Overview
1✔
UNCOV
900
      []
×
901
    end
902

903
    # Expose the default encryption method to other parts of the module
904
    #
905
    # @return [String]
906
    def default_encrypt_method
1✔
907
      DEFAULT_ENCRYPT_METHOD
3✔
908
    end
909

910
    # Convenience method to obtain a Linux Security Module Config instance
911
    #
912
    # @return [Y2Security::LSM::Config]
913
    def lsm_config
1✔
914
      Y2Security::LSM::Config.instance
51✔
915
    end
916

917
    publish variable: :mandatory_services, type: "const list <list <string>>"
1✔
918
    publish variable: :optional_services, type: "const list <string>"
1✔
919
    publish function: :MissingMandatoryServices, type: "list <list <string>> ()"
1✔
920
    publish function: :ExtraServices, type: "list <string> ()"
1✔
921
    publish variable: :Settings, type: "map <string, string>"
1✔
922
    publish variable: :do_not_test, type: "list <string>"
1✔
923
    publish variable: :PasswordMaxLengths, type: "map"
1✔
924
    publish variable: :AbortFunction, type: "block <boolean>"
1✔
925
    publish function: :PollAbort, type: "boolean ()"
1✔
926
    publish function: :Abort, type: "boolean ()"
1✔
927
    publish variable: :modified, type: "boolean"
1✔
928
    publish variable: :proposal_valid, type: "boolean"
1✔
929
    publish variable: :write_only, type: "boolean"
1✔
930
    publish variable: :read_error, type: "string"
1✔
931
    publish function: :GetModified, type: "boolean ()"
1✔
932
    publish function: :SetModified, type: "void ()"
1✔
933
    publish function: :Modified, type: "boolean ()"
1✔
934
    publish function: :ReadServiceSettings, type: "void ()"
1✔
935
    publish function: :Read, type: "boolean ()"
1✔
936
    publish function: :SafeRead, type: "boolean ()"
1✔
937
    publish function: :Write, type: "boolean ()"
1✔
938
    publish function: :Import, type: "boolean (map)"
1✔
939
    publish function: :Export, type: "map ()"
1✔
940
    publish function: :Summary, type: "list ()"
1✔
941
    publish function: :Overview, type: "list ()"
1✔
942

943
  protected
1✔
944

945
    # It sets the LSM configuration according to the one provided in the profile and ensures
946
    # needed patterns for the selected LSM
947
    #
948
    # @param section [SecuritySection] profile security settings to be imported.
949
    def import_lsm_config(section)
1✔
950
      Y2Security::Autoinst::LSMConfigReader.new(section).read
12✔
951
      return unless lsm_config.configurable?
12✔
952

953
      PackagesProposal.SetResolvables("LSM", :pattern, lsm_config.needed_patterns)
11✔
954
    end
955

956
    # It enables the security policy according to the profile
957
    #
958
    # @param section [Y2Security::AutoinstProfile::SecurityPolicySection] security
959
    #   policy section from the AutoYaST profile
960
    def import_security_policy(section)
1✔
961
      return if section.policy.nil?
12✔
962

963
      manager = Y2Security::SecurityPolicies::Manager.instance
3✔
964
      policy = manager.find_policy(section.policy.to_sym)
3✔
965
      if policy.nil?
3✔
966
        log.error "The security policy '#{section.policy}' is unknown."
1✔
967
        return
1✔
968
      end
969
      manager.enabled_policy = policy
2✔
970
      manager.scap_action = section.action.to_sym if section.action
2✔
971
    rescue Y2Security::SecurityPolicies::Manager::UnknownSCAPAction
972
      log.error("SCAP action '#{section.action}' is not valid.")
1✔
973
    end
974

975
    # Export security policy settings
976
    #
977
    # @return [Hash]
978
    def export_security_policy
1✔
979
      Y2Security::AutoinstProfile::SecurityPolicySection.new_from_system
4✔
980
        .to_hashes
981
    end
982

983
    # Sets @missing_mandatory_services honoring the systemd aliases
984
    def read_missing_mandatory_services
1✔
985
      log.info("Checking mandatory services")
5✔
986

987
      @missing_mandatory_services = @mandatory_services.reject do |services|
5✔
988
        enabled = services.any? { |service| Service.enabled?(service) }
52✔
989
        log.info("Mandatory services #{services} are enabled: #{enabled}")
20✔
990
        enabled
20✔
991
      end
992

993
      log.info("Missing mandatory services: #{@missing_mandatory_services}")
5✔
994
    end
995

996
    # Sets @extra_services honoring the systemd aliases
997
    def read_extra_services
1✔
998
      log.info("Searching for extra services")
5✔
999

1000
      enabled_services = Yast2::Systemd::Service.all(names: "Names").select(&:enabled?)
5✔
1001
      # Remove from the list the services that are allowed
1002
      @extra_services = enabled_services.reject do |service|
5✔
1003
        allowed = allowed_service?(service.name)
19✔
1004
        # If the name is not allowed, try the aliases
1005
        if !allowed
19✔
1006
          names = alias_names(service)
4✔
1007
          allowed = names&.any? { |name| allowed_service?(name) }
8✔
1008
        end
1009
        log.info("Found extra service: #{service.name}") unless allowed
19✔
1010
        allowed
19✔
1011
      end
1012
      @extra_services.map!(&:name)
5✔
1013
      log.info("All extra services: #{@extra_services}")
5✔
1014
    end
1015

1016
    # Returns the sysctl configuration
1017
    #
1018
    # @note It memoizes the value until {#main} is called.
1019
    #
1020
    # @return [Yast2::CFA::SysctlConfig]
1021
    def sysctl_config
1✔
1022
      return @sysctl_config if @sysctl_config
59✔
1023

1024
      @sysctl_config = CFA::SysctlConfig.new
7✔
1025
      @sysctl_config.load
7✔
1026
      @sysctl_config
7✔
1027
    end
1028

1029
    # Map sysctl keys to method names from the CFA::SysctlConfig class.
1030
    SYSCTL_KEY_TO_METH = {
1✔
1031
      "kernel.sysrq"                 => :kernel_sysrq,
1032
      "net.ipv4.tcp_syncookies"      => :tcp_syncookies,
1033
      "net.ipv4.ip_forward"          => :forward_ipv4,
1034
      "net.ipv6.conf.all.forwarding" => :forward_ipv6
1035
    }.freeze
1036

1037
    # @param key [String] Key to get the value for
1038
    def read_sysctl_value(key)
1✔
1039
      sysctl_config.public_send(SYSCTL_KEY_TO_METH[key])
53✔
1040
    end
1041

1042
    # @param key    [String] Key to set the value for
1043
    # @param value [String] Value to assign to the given key
1044
    def write_sysctl_value(key, value)
1✔
1045
      sysctl_config.public_send(SYSCTL_KEY_TO_METH[key].to_s + "=", value)
3✔
1046
    end
1047

1048
    def shadow_config
1✔
1049
      @shadow_config ||= CFA::ShadowConfig.load
81✔
1050
    end
1051
  end
1052

1053
  # Checks if the service is allowed (i.e. not considered 'extra')
1054
  #
1055
  # @return [Boolean] true whether the service is expected (mandatory or optional)
1056
  def allowed_service?(name)
1✔
1057
    all_mandatory_services.include?(name) || @optional_services.include?(name)
23✔
1058
  end
1059

1060
  # Flat list of mandatory services
1061
  def all_mandatory_services
1✔
1062
    @all_mandatory_services ||= @mandatory_services.flatten
23✔
1063
  end
1064

1065
  # List of aliases of the service
1066
  #
1067
  # @return [Array<String>] alias names excluding '.service'
1068
  def alias_names(service)
1✔
1069
    names = service.properties.names
4✔
1070
    names&.split&.map { |name| name.sub(/\.service$/, "") }
8✔
1071
  end
1072

1073
  Security = SecurityClass.new
1✔
1074
  Security.main
1✔
1075
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc