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

yast / yast-security / 14365539427

09 Apr 2025 07:36PM UTC coverage: 39.047% (-0.2%) from 39.237%
14365539427

push

github

web-flow
Merge pull request #161 from yast/adapt_selinux

adapt selinux behavior according to new jira (jsc#PED-12400)

9 of 9 new or added lines in 1 file covered. (100.0%)

3 existing lines in 3 files now uncovered.

1369 of 3506 relevant lines covered (39.05%)

5.73 hits per line

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

90.41
/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
    ].freeze
69

70
    attr_reader :display_manager
1✔
71

72
    def main
1✔
73
      import_modules
75✔
74

75
      textdomain "security"
75✔
76

77
      init_settings
75✔
78
    end
79

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

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

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

116
      @display_manager = ::Security::DisplayManager.current
85✔
117

118
      # systemd target, defining ctrl-alt-del behavior
119
      @ctrl_alt_del_file = ::Security::CtrlAltDelConfig::SYSTEMD_FILE
85✔
120

121
      # encryption methods supported by pam_unix (bnc#802006)
122
      @encryption_methods = ["des", "md5", "sha256", "sha512"]
85✔
123

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

162
      @Settings.merge!(@display_manager.default_settings) if @display_manager
85✔
163

164
      # List of missing mandatory services
165
      @missing_mandatory_services = []
85✔
166
      # List of enabled services not included in mandatory or optional lists
167
      @extra_services = []
85✔
168

169
      # the original settings
170
      @Settings_bak = deep_copy(@Settings)
85✔
171

172
      # keys that should not be tested against predefined levels:
173
      # - *_SERVICES have different syntax, are not saved in current form
174
      @do_not_test = [
175
        "MANDATORY_SERVICES",
85✔
176
        "EXTRA_SERVICES"
177
      ]
178

179
      # Security settings locations
180
      @Locations = {
181
        ".sysconfig.security" => ["PERMISSION_SECURITY"],
85✔
182
        ".sysconfig.services" => [
183
          "DISABLE_RESTART_ON_UPDATE",
184
          "DISABLE_STOP_ON_REMOVAL"
185
        ],
186
        ".sysconfig.locate"   => ["RUN_UPDATEDB_AS"],
187
        ".sysconfig.cron"     => ["SYSLOG_ON_NO_ERROR"],
188
        ".sysconfig.mail"     => ["SMTPD_LISTEN_REMOTE"]
189
      }
190

191
      @Locations.merge!(@display_manager.default_locations) if @display_manager
85✔
192

193
      # Default values for /etc/sysctl.conf keys
194
      @sysctl = {
195
        "kernel.sysrq"                 => "0",
85✔
196
        "net.ipv4.tcp_syncookies"      => true,
197
        "net.ipv4.ip_forward"          => false,
198
        "net.ipv6.conf.all.forwarding" => false
199
      }
200

201
      # Mapping of /etc/sysctl.conf keys to old (obsoleted) sysconfig ones
202
      # (used during autoYaST import)
203
      @sysctl2sysconfig = {
204
        "kernel.sysrq"                 => "ENABLE_SYSRQ",
85✔
205
        "net.ipv4.tcp_syncookies"      => "IP_TCP_SYNCOOKIES",
206
        "net.ipv4.ip_forward"          => "IP_FORWARD",
207
        "net.ipv6.conf.all.forwarding" => "IPV6_FORWARD"
208
      }
209

210
      # Mapping of /etc/login.defs keys to old (obsoleted) ones
211
      # (used during autoYaST import)
212
      @obsolete_login_defs = {
213
        "SYS_UID_MAX" => "SYSTEM_UID_MAX",
85✔
214
        "SYS_UID_MIN" => "SYSTEM_UID_MIN",
215
        "SYS_GID_MAX" => "SYSTEM_GID_MAX",
216
        "SYS_GID_MIN" => "SYSTEM_GID_MIN"
217
      }
218

219
      # mapping of internal YaST values to values needed for
220
      # org.freedesktop.upower.hibernate privilege
221
      @ycp2polkit = {
222
        "active_console" => "auth_admin:auth_admin:yes",
85✔
223
        "auth_admin"     => "auth_admin:auth_admin:auth_admin",
224
        "anyone"         => "yes:yes:yes"
225
      }
226

227
      # Remaining settings:
228
      # - PASSWD_ENCRYPTION (/etc/pam?)
229
      # - MANDATORY_SERVICES
230
      # - EXTRA_SERVICES
231

232
      # Number of sigificant characters in the password
233
      @PasswordMaxLengths = {
85✔
234
        "des"    => 8,
235
        "md5"    => 127,
236
        "sha256" => 127,
237
        "sha512" => 127
238
      }
239

240
      # Abort function
241
      # return boolean return true if abort
242
      @AbortFunction = nil
85✔
243

244
      # Data was modified?
245
      @modified = false
85✔
246

247
      @proposal_valid = false
85✔
248
      @write_only = false
85✔
249

250
      # Force reading of sysctl configuration
251
      @sysctl_config = nil
85✔
252

253
      @activation_mapping = {
254
        "DHCPD_RUN_CHROOTED"           => "/usr/bin/systemctl try-restart dhcpd.service",
85✔
255
        "DHCPD_RUN_AS"                 => "/usr/bin/systemctl try-restart dhcpd.service",
256
        # restart sendmail or postfix - whatever is installed
257
        "SMTPD_LISTEN_REMOTE"          => "/usr/bin/systemctl try-restart sendmail postfix",
258
        "net.ipv4.tcp_syncookies"      => "/usr/bin/systemctl try-restart network",
259
        "net.ipv4.ip_forward"          => "/usr/bin/systemctl try-restart network",
260
        "net.ipv6.conf.all.forwarding" => "/usr/bin/systemctl try-restart network"
261
      }
262

263
      @shadow_config = nil
85✔
264
    end
265

266
    # List of missing mandatory services
267
    def MissingMandatoryServices
1✔
268
      @missing_mandatory_services
5✔
269
    end
270

271
    # List of enabled services that are neither mandatory nor optional
272
    def ExtraServices
1✔
273
      @extra_services
5✔
274
    end
275

276
    # Check for pending Abort press
277
    # @return true if pending abort
278
    def PollAbort
1✔
279
      UI.PollInput == :abort
×
280
    end
281

282
    # Abort function
283
    # @return blah blah lahjk
284
    def Abort
1✔
285
      return Builtins.eval(@AbortFunction) == true if !@AbortFunction.nil?
14✔
286

287
      false
14✔
288
    end
289

290
    # Function which returns if the settings were modified
291
    # @return [Boolean]  settings were modified
292
    def GetModified
1✔
293
      @modified
×
294
    end
295

296
    # Function sets internal variable, which indicates, that any
297
    # settings were modified, to "true"
298
    def SetModified
1✔
299
      @modified = true
×
300

UNCOV
301
      nil
×
302
    end
303

304
    # Data was modified?
305
    # @return true if modified
306
    def Modified
1✔
307
      Builtins.y2debug("modified=%1", @modified)
×
308
      @modified
×
309
    end
310

311
    def ReadServiceSettings
1✔
312
      read_missing_mandatory_services
5✔
313
      setting = MissingMandatoryServices() == [] ? "secure" : "insecure"
5✔
314
      @Settings["MANDATORY_SERVICES"] = setting
5✔
315
      read_extra_services
5✔
316
      setting = ExtraServices() == [] ? "secure" : "insecure"
5✔
317
      @Settings["EXTRA_SERVICES"] = setting
5✔
318

319
      nil
5✔
320
    end
321

322
    # Read the information about ctrl+alt+del behavior
323
    # See bug 742783 for description
324
    def ReadConsoleShutdown
1✔
325
      @Settings["CONSOLE_SHUTDOWN"] =
10✔
326
        ::Security::CtrlAltDelConfig.current || ::Security::CtrlAltDelConfig.default
327
    end
328

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

348
      log.debug "Settings (after #{__callee__}): #{@Settings}"
10✔
349
    end
350

351
    # Reads login.defs configuration
352
    def read_shadow_config
1✔
353
      SHADOW_ATTRS.each do |attr|
2✔
354
        value = shadow_config.public_send(attr.downcase)
24✔
355
        next if value.nil?
24✔
356

357
        @Settings[attr] = shadow_config.public_send(attr.downcase)
1✔
358
      end
359
      log.debug "Settings (after #{__callee__}): #{@Settings}"
2✔
360
    end
361

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

371
      log.debug "Settings (after #{__callee__}): #{@Settings}"
7✔
372
    end
373

374
    # Reads the Linux Security Module configuration
375
    def read_lsm_config
1✔
376
      lsm_config.read
1✔
377
    end
378

379
    def read_encryption_method
1✔
380
      method = shadow_config.encrypt_method.to_s.downcase
3✔
381

382
      method = "sha512" if !@encryption_methods.include?(method)
3✔
383

384
      @Settings["PASSWD_ENCRYPTION"] = method
3✔
385
    end
386

387
    def read_pam_settings
1✔
388
      read_encryption_method
3✔
389

390
      # pwquality and pwhistory settings (default values)
391
      @Settings["PASS_MIN_LEN"] = "5"
3✔
392
      @Settings["PASSWD_REMEMBER_HISTORY"] = "0"
3✔
393
      @Settings["CRACKLIB_DICT_PATH"] = "/usr/lib/cracklib_dict"
3✔
394

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

398
      pam_pwquality.fetch("password", []).each do |entry|
3✔
399
        key, value = entry.split("=")
2✔
400
        if value
2✔
401
          @Settings["CRACKLIB_DICT_PATH"] = value if key == "dictpath"
1✔
402
          @Settings["PASS_MIN_LEN"]       = value if key == "minlen"
1✔
403
        end
404
      end
405

406
      pam_history = Pam.Query("pwhistory") || {}
3✔
407
      pam_history.fetch("password", []).each do |entry|
3✔
408
        key, value = entry.split("=")
1✔
409
        @Settings["PASSWD_REMEMBER_HISTORY"] = value if key == "remember" && value
1✔
410
      end
411
      log.debug "Settings (after #{__callee__}): #{@Settings}"
3✔
412
    end
413

414
    def read_permissions
1✔
415
      # Removing "local" from the string
416
      permissions = @Settings["PERMISSION_SECURITY"].to_s.split
4✔
417
      @Settings["PERMISSION_SECURITY"] = permissions.delete_if do |p|
4✔
418
        p == "local"
6✔
419
      end.join(" ")
420

421
      # default value
422
      @Settings["PERMISSION_SECURITY"] = "secure" if @Settings["PERMISSION_SECURITY"].empty?
4✔
423

424
      log.debug "PERMISSION_SECURITY (after #{__callee__}): " \
4✔
425
                "#{@Settings["PERMISSION_SECURITY"]}"
426

427
      @Settings["PERMISSION_SECURITY"]
4✔
428
    end
429

430
    def read_polkit_settings
1✔
431
      action = "org.freedesktop.upower.hibernate"
3✔
432

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

435
      @Settings["HIBERNATE_SYSTEM"] = case hibernate
3✔
436
      when "auth_admin:auth_admin:auth_admin"
437
        "auth_admin"
1✔
438
      when "yes:yes:yes"
439
        "anyone"
1✔
440
      else
441
        "active_console"
1✔
442
      end
443
      log.debug "HIBERNATE_SYSTEM (after #{__callee__}): " \
3✔
444
                "#{@Settings["HIBERNATE_SYSTEM"]}"
445
    end
446

447
    # The name of the PAM module to deal with password quality. Either
448
    # "pwquality" or "cracklib". See bug #1171318 why this is needed.
449
    def pwquality_module
1✔
450
      return @mod_name if @mod_name
9✔
451

452
      # Both pwquality and cracklib can be installed. in that case
453
      # cracklib seems to be a non-functional deprecated module. So
454
      # prefer pwquality.
455
      @mod_name = Pam.List.include?("pwquality") ? "pwquality" : "cracklib"
1✔
456
    end
457

458
    # Read all security settings
459
    #
460
    # @raise [Exception] if there is an issue while reading the settings
461
    # @return [Boolean] true on success
462
    def Read
1✔
463
      @Settings = {}
1✔
464
      @modified = false
1✔
465

466
      # Read security settings
467
      read_from_locations
1✔
468
      read_shadow_config
1✔
469

470
      ReadConsoleShutdown()
1✔
471

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

474
      # Read runlevel setting
475
      ReadServiceSettings()
1✔
476

477
      read_pam_settings
1✔
478

479
      # Local permissions hack
480
      read_permissions
1✔
481

482
      read_polkit_settings
1✔
483

484
      read_kernel_settings
1✔
485

486
      read_lsm_config
1✔
487

488
      # remember the read values
489
      @Settings_bak = deep_copy(@Settings)
1✔
490

491
      log.info "Settings after Read: #{@Settings}"
1✔
492
      true
1✔
493
    end
494

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

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

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

552
    # Write login.defs configuration
553
    def write_shadow_config
1✔
554
      SHADOW_ATTRS.each do |attr|
3✔
555
        # bsc#1208492 shadow config uses login.defs attr formatting
556
        # like <Key><space>*<Value>, so empty value is not supported
557
        # and moreover can cause crash in login.defs lens
558
        next if @Settings[attr].nil? || @Settings[attr].empty?
36✔
559

560
        shadow_config.public_send("#{attr.to_s.downcase}=", @Settings[attr])
36✔
561
      end
562
      encr = @Settings.fetch("PASSWD_ENCRYPTION", default_encrypt_method)
3✔
563
      shadow_config.encrypt_method = encr if encr != @Settings_bak["PASSWD_ENCRYPTION"]
3✔
564
      shadow_config.save
3✔
565
    end
566

567
    # Writes the current Linux Security Module Configuration
568
    #
569
    # @see Y2Security:.LSM::Config#save
570
    # @return [Boolean] whether the configuration was saved or not
571
    def write_lsm_config
1✔
572
      lsm_config.save
2✔
573
    end
574

575
    # Write settings related to PAM behavior
576
    def write_pam_settings
1✔
577
      # use pwquality?
578
      if @Settings["PASSWD_USE_PWQUALITY"] == "yes"
1✔
579
        Pam.Add(pwquality_module)
1✔
580
        pth = @Settings["CRACKLIB_DICT_PATH"]
1✔
581
        Pam.Add(pwquality_module + "-dictpath=#{pth}") if pth && pth != "/usr/lib/cracklib_dict"
1✔
582
      else
583
        Pam.Remove(pwquality_module)
×
584
      end
585

586
      # save min pass length
587
      min_len = @Settings["PASS_MIN_LEN"]
1✔
588
      if min_len && min_len != "5" && @Settings["PASSWD_USE_PWQUALITY"] == "yes"
1✔
589
        Pam.Add(pwquality_module) # minlen is part of pwquality
×
590
        Pam.Add(pwquality_module + "-minlen=#{min_len}")
×
591
      else
592
        Pam.Remove(pwquality_module + "-minlen")
1✔
593
      end
594

595
      # save "remember" value (number of old user passwords to not allow)
596
      remember_history = @Settings["PASSWD_REMEMBER_HISTORY"]
1✔
597
      if remember_history && remember_history != "0"
1✔
598
        Pam.Add("pwhistory")
×
599
        Pam.Add("pwhistory-remember=#{remember_history}")
×
600
      else
601
        Pam.Remove("pwhistory-remember")
1✔
602
      end
603
    end
604

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

625
      # In case of modified, always write the changes (bsc#1167234)
626
      sysctl_config.save if written
7✔
627
      written
7✔
628
    end
629

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

645
    # Apply sysctl settings from all the sysctl configuration files
646
    def apply_sysctl_changes
1✔
647
      # Reports if there are conflict when the configuration is applied
648
      sysctl_config.conflict?
3✔
649

650
      Yast::Execute.on_target("/usr/sbin/sysctl", "--system")
3✔
651
    end
652

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

664
      # ensure polkit privileges are applied (bnc #541393)
665
      polkit_exec = "/sbin/set_polkit_default_privs"
5✔
666
      SCR.Execute(path(".target.bash"), polkit_exec) if FileUtils.Exists(polkit_exec)
5✔
667
    end
668

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

676
        log.info(
×
677
          "Option #{setting} has been modified, "\
678
          "activating the change: #{action}"
679
        )
680
        res = SCR.Execute(path(".target.bash"), action)
×
681
        log.error "Activation failed" if res != 0
×
682
      end
683
    end
684

685
    # Write all security settings
686
    # @return true on success
687
    def Write
1✔
688
      return true if !@modified
2✔
689

690
      log.info "Writing configuration"
2✔
691

692
      # Security read dialog caption
693
      caption = _("Saving Security Configuration")
2✔
694
      steps = 5
2✔
695

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

729
      log.debug "Settings=#{@Settings}"
2✔
730

731
      # Write security settings
732
      return false if Abort()
2✔
733

734
      Progress.NextStage
2✔
735
      if !@Settings["PERMISSION_SECURITY"].include?("local")
2✔
736
        @Settings["PERMISSION_SECURITY"] << " local"
2✔
737
      end
738
      write_to_locations
2✔
739
      write_shadow_config
2✔
740

741
      # Write shutdown settings
742
      return false if Abort()
2✔
743

744
      Progress.NextStage
2✔
745
      write_console_shutdown(@Settings.fetch("CONSOLE_SHUTDOWN", "ignore"))
2✔
746

747
      # Write authentication and privileges settings
748
      return false if Abort()
2✔
749

750
      Progress.NextStage
2✔
751
      write_pam_settings
2✔
752
      write_polkit_settings
2✔
753
      sysctl_modified = write_kernel_settings
2✔
754

755
      # Finish him
756
      return false if Abort()
2✔
757

758
      Progress.NextStage
2✔
759
      apply_new_settings(sysctl: sysctl_modified)
2✔
760

761
      return false if Abort()
2✔
762

763
      Progress.NextStage
2✔
764
      activate_changes
2✔
765

766
      return false if Abort()
2✔
767

768
      Progress.NextStage
2✔
769
      write_lsm_config
2✔
770

771
      return false if Abort()
2✔
772

773
      @modified = false
2✔
774
      true
2✔
775
    end
776

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

794
      # conversion to true/false
795
      ["net.ipv4.tcp_syncookies", "net.ipv4.ip_forward",
12✔
796
       "net.ipv6.conf.all.forwarding"].each do |key|
797
        settings[key] = settings[key] == "1" if settings.key?(key) && settings[key].is_a?(::String)
36✔
798
      end
799

800
      if settings.key?("PASSWD_USE_CRACKLIB")
12✔
801
        settings["PASSWD_USE_PWQUALITY"] = settings.delete("PASSWD_USE_CRACKLIB")
×
802
      end
803

804
      settings["lsm_select"] = settings.delete("LSM_SELECT") if settings.key?("LSM_SELECT")
12✔
805
      settings["selinux_mode"] = settings.delete("SELINUX_MODE") if settings.key?("SELINUX_MODE")
12✔
806
      if settings.key?("SECURITY_POLICY")
12✔
807
        settings["security_policy"] = settings.delete("SECURITY_POLICY")
3✔
808
      end
809

810
      section = Y2Security::AutoinstProfile::SecuritySection.new_from_hashes(settings)
12✔
811
      import_lsm_config(section)
12✔
812
      import_security_policy(section.security_policy)
12✔
813

814
      return true if settings == {}
12✔
815

816
      @modified = true
11✔
817
      tmpSettings = {}
11✔
818
      @Settings.each do |k, v|
11✔
819
        if settings.key?(k)
374✔
820
          tmpSettings[k] = settings[k]
3✔
821
        elsif @sysctl.key?(k) && settings.key?(@sysctl2sysconfig[k])
371✔
822
          val = settings[@sysctl2sysconfig[k]].to_s
1✔
823
          tmpSettings[k] = if @sysctl[k].is_a?(TrueClass) || @sysctl[k].is_a?(FalseClass)
1✔
824
            SYSCTL_VALUES_TO_BOOLEAN.key?(val) ? SYSCTL_VALUES_TO_BOOLEAN[val] : val
1✔
825
          else
826
            SYSCTL_VALUES_TO_INTSTRING.key?(val) ? SYSCTL_VALUES_TO_INTSTRING[val] : val
×
827
          end
828
        # using the old sysconfig AY format
829
        else
830
          # using old login defs settings ?
831
          tmpSettings[k] = settings[@obsolete_login_defs[k]] || v
370✔
832
        end
833
      end
834

835
      @Settings = tmpSettings
11✔
836
      true
11✔
837
    end
838

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

852
      if pwquality_module == "cracklib"
4✔
853
        settings["PASSWD_USE_CRACKLIB"] = settings.delete("PASSWD_USE_PWQUALITY")
×
854
      end
855

856
      merged_settings = settings.merge(lsm_config.export)
4✔
857
      security_policy = export_security_policy
4✔
858
      merged_settings.merge!("security_policy" => security_policy) unless security_policy.empty?
4✔
859
      merged_settings
4✔
860
    end
861

862
    # Create a textual summary and a list of unconfigured cards
863
    # @return summary of the current configuration
864
    def Summary
1✔
865
      settings = deep_copy(@Settings)
×
866
      Builtins.foreach(@do_not_test) do |key|
×
867
        settings = Builtins.remove(settings, key)
×
868
      end
869

870
      # Determine current settings
871
      current = :custom
×
872
      Builtins.maplist(@Levels) do |key, level|
×
873
        Builtins.y2debug("%1=%2", key, level)
×
874
        current = key if level == settings
×
875
      end
876
      Builtins.y2debug("%1=%2", current, @Settings)
×
877

878
      # Summary text
879
      summary = _("Current Security Level: Custom settings")
×
880
      if current != :custom
×
881
        # Summary text
882
        summary = Builtins.sformat(
×
883
          _("Current Security Level: %1"),
884
          Ops.get(@LevelsNames, Convert.to_string(current), "")
885
        )
886
      end
887

888
      [summary, []]
×
889
    end
890

891
    # Create an overview table with all configured cards
892
    # @return table items
893
    def Overview
1✔
894
      []
×
895
    end
896

897
    # Expose the default encryption method to other parts of the module
898
    #
899
    # @return [String]
900
    def default_encrypt_method
1✔
901
      DEFAULT_ENCRYPT_METHOD
3✔
902
    end
903

904
    # Convenience method to obtain a Linux Security Module Config instance
905
    #
906
    # @return [Y2Security::LSM::Config]
907
    def lsm_config
1✔
908
      Y2Security::LSM::Config.instance
51✔
909
    end
910

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

937
  protected
1✔
938

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

947
      PackagesProposal.SetResolvables("LSM", :pattern, lsm_config.needed_patterns)
11✔
948
    end
949

950
    # It enables the security policy according to the profile
951
    #
952
    # @param section [Y2Security::AutoinstProfile::SecurityPolicySection] security
953
    #   policy section from the AutoYaST profile
954
    def import_security_policy(section)
1✔
955
      return if section.policy.nil?
12✔
956

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

969
    # Export security policy settings
970
    #
971
    # @return [Hash]
972
    def export_security_policy
1✔
973
      Y2Security::AutoinstProfile::SecurityPolicySection.new_from_system
4✔
974
        .to_hashes
975
    end
976

977
    # Sets @missing_mandatory_services honoring the systemd aliases
978
    def read_missing_mandatory_services
1✔
979
      log.info("Checking mandatory services")
5✔
980

981
      @missing_mandatory_services = @mandatory_services.reject do |services|
5✔
982
        enabled = services.any? { |service| Service.enabled?(service) }
52✔
983
        log.info("Mandatory services #{services} are enabled: #{enabled}")
20✔
984
        enabled
20✔
985
      end
986

987
      log.info("Missing mandatory services: #{@missing_mandatory_services}")
5✔
988
    end
989

990
    # Sets @extra_services honoring the systemd aliases
991
    def read_extra_services
1✔
992
      log.info("Searching for extra services")
5✔
993

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

1010
    # Returns the sysctl configuration
1011
    #
1012
    # @note It memoizes the value until {#main} is called.
1013
    #
1014
    # @return [Yast2::CFA::SysctlConfig]
1015
    def sysctl_config
1✔
1016
      return @sysctl_config if @sysctl_config
59✔
1017

1018
      @sysctl_config = CFA::SysctlConfig.new
7✔
1019
      @sysctl_config.load
7✔
1020
      @sysctl_config
7✔
1021
    end
1022

1023
    # Map sysctl keys to method names from the CFA::SysctlConfig class.
1024
    SYSCTL_KEY_TO_METH = {
1✔
1025
      "kernel.sysrq"                 => :kernel_sysrq,
1026
      "net.ipv4.tcp_syncookies"      => :tcp_syncookies,
1027
      "net.ipv4.ip_forward"          => :forward_ipv4,
1028
      "net.ipv6.conf.all.forwarding" => :forward_ipv6
1029
    }.freeze
1030

1031
    # @param key [String] Key to get the value for
1032
    def read_sysctl_value(key)
1✔
1033
      sysctl_config.public_send(SYSCTL_KEY_TO_METH[key])
53✔
1034
    end
1035

1036
    # @param key    [String] Key to set the value for
1037
    # @param value [String] Value to assign to the given key
1038
    def write_sysctl_value(key, value)
1✔
1039
      sysctl_config.public_send(SYSCTL_KEY_TO_METH[key].to_s + "=", value)
3✔
1040
    end
1041

1042
    def shadow_config
1✔
1043
      @shadow_config ||= CFA::ShadowConfig.load
67✔
1044
    end
1045
  end
1046

1047
  # Checks if the service is allowed (i.e. not considered 'extra')
1048
  #
1049
  # @return [Boolean] true whether the service is expected (mandatory or optional)
1050
  def allowed_service?(name)
1✔
1051
    all_mandatory_services.include?(name) || @optional_services.include?(name)
23✔
1052
  end
1053

1054
  # Flat list of mandatory services
1055
  def all_mandatory_services
1✔
1056
    @all_mandatory_services ||= @mandatory_services.flatten
23✔
1057
  end
1058

1059
  # List of aliases of the service
1060
  #
1061
  # @return [Array<String>] alias names excluding '.service'
1062
  def alias_names(service)
1✔
1063
    names = service.properties.names
4✔
1064
    names&.split&.map { |name| name.sub(/\.service$/, "") }
8✔
1065
  end
1066

1067
  Security = SecurityClass.new
1✔
1068
  Security.main
1✔
1069
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