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

yast / yast-security / 3656380626

pending completion
3656380626

push

github

Unknown Committer
Unknown Commit Message

14 of 14 new or added lines in 2 files covered. (100.0%)

1367 of 3467 relevant lines covered (39.43%)

5.67 hits per line

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

90.41
/src/modules/Security.rb
1
# encoding: utf-8
2

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

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

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

45
    include Yast::Logger
1✔
46
    include ::Security::CtrlAltDelConfig
1✔
47

48
    SYSCTL_VALUES_TO_BOOLEAN = {
1✔
49
      "yes" => true,
50
      "no"  => false
51
    }
52
    SYSCTL_VALUES_TO_INTSTRING = {
53
      "yes" => "1",
1✔
54
      "no"  => "0"
55
    }
56

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

75
    attr_reader :display_manager
1✔
76

77
    def main
1✔
78
      import_modules
74✔
79

80
      textdomain "security"
74✔
81

82
      init_settings
74✔
83
    end
84

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

99
    def init_settings
1✔
100
      # Services to check
101
      srv_file = Directory.find_data_file("security/services.yml")
84✔
102
      if srv_file
84✔
103
        srv_lists = YAML.load_file(srv_file) rescue {}
84✔
104
      else
105
        srv_lists = {}
×
106
      end
107

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

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

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

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

124
      # All security settings
125
      @Settings = {
126
        "CONSOLE_SHUTDOWN"                          => ::Security::CtrlAltDelConfig.default,
84✔
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
        "USERADD_CMD"                               => "/usr/sbin/useradd.local",
154
        "USERDEL_PRECMD"                            => "/usr/sbin/userdel-pre.local",
155
        "USERDEL_POSTCMD"                           => "/usr/sbin/userdel-post.local",
156
        "PASSWD_REMEMBER_HISTORY"                   => "0",
157
        "SYSLOG_ON_NO_ERROR"                        => "yes",
158
        "DISPLAYMANAGER_ROOT_LOGIN_REMOTE"          => "no",
159
        "DISPLAYMANAGER_XSERVER_TCP_PORT_6000_OPEN" => "no",
160
        "SMTPD_LISTEN_REMOTE"                       => "no",
161
        "MANDATORY_SERVICES"                        => "yes",
162
        "EXTRA_SERVICES"                            => "no"
163
      }
164

165
      @Settings.merge!(@display_manager.default_settings) if @display_manager
84✔
166

167
      # List of missing mandatory services
168
      @missing_mandatory_services = []
84✔
169
      # List of enabled services not included in mandatory or optional lists
170
      @extra_services = []
84✔
171

172
      # the original settings
173
      @Settings_bak = deep_copy(@Settings)
84✔
174

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

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

194
      @Locations.merge!(@display_manager.default_locations) if @display_manager
84✔
195

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

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

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

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

230
      # Remaining settings:
231
      # - PASSWD_ENCRYPTION (/etc/pam?)
232
      # - MANDATORY_SERVICES
233
      # - EXTRA_SERVICES
234

235
      # Number of sigificant characters in the password
236
      @PasswordMaxLengths = {
84✔
237
        "des"    => 8,
238
        "md5"    => 127,
239
        "sha256" => 127,
240
        "sha512" => 127
241
      }
242

243
      # Abort function
244
      # return boolean return true if abort
245
      @AbortFunction = nil
84✔
246

247
      # Data was modified?
248
      @modified = false
84✔
249

250
      @proposal_valid = false
84✔
251
      @write_only = false
84✔
252

253
      # Force reading of sysctl configuration
254
      @sysctl_config = nil
84✔
255

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

266
      @shadow_config = nil
84✔
267
    end
268

269
    # List of missing mandatory services
270
    def MissingMandatoryServices
1✔
271
      @missing_mandatory_services
5✔
272
    end
273

274
    # List of enabled services that are neither mandatory nor optional
275
    def ExtraServices
1✔
276
      @extra_services
5✔
277
    end
278

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

285
    # Abort function
286
    # @return blah blah lahjk
287
    def Abort
1✔
288
      return Builtins.eval(@AbortFunction) == true if @AbortFunction != nil
14✔
289

290
      false
14✔
291
    end
292

293
    # Function which returns if the settings were modified
294
    # @return [Boolean]  settings were modified
295
    def GetModified
1✔
296
      @modified
×
297
    end
298

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

304
      nil
305
    end
306

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

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

322
      nil
323
    end
324

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

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

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

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

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

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

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

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

381
    def read_encryption_method
1✔
382
      method = shadow_config.encrypt_method.to_s.downcase
3✔
383

384
      method = "sha512" if !@encryption_methods.include?(method)
3✔
385

386
      @Settings["PASSWD_ENCRYPTION"] = method
3✔
387
    end
388

389
    def read_pam_settings
1✔
390
      read_encryption_method
3✔
391

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

397
      pam_pwquality = Pam.Query(pwquality_module) || {}
3✔
398
      @Settings["PASSWD_USE_PWQUALITY"] = pam_pwquality.size > 0 ? "yes" : "no"
3✔
399

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

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

418
    def read_permissions
1✔
419
      # Removing "local" from the string
420
      permissions = @Settings["PERMISSION_SECURITY"].to_s.split(" ")
4✔
421
      @Settings["PERMISSION_SECURITY"] = permissions.delete_if {|p|
4✔
422
        p == "local" }.join(" ")
6✔
423

424
      # default value
425
      @Settings["PERMISSION_SECURITY"] = "secure" if @Settings["PERMISSION_SECURITY"].empty?
4✔
426

427
      log.debug "PERMISSION_SECURITY (after #{__callee__}): " \
4✔
428
        "#{@Settings['PERMISSION_SECURITY']}"
429

430
      @Settings['PERMISSION_SECURITY']
4✔
431
    end
432

433
    def read_polkit_settings
1✔
434
      action = "org.freedesktop.upower.hibernate"
3✔
435

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

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

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

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

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

469
      # Read security settings
470
      read_from_locations
1✔
471
      read_shadow_config
1✔
472

473
      ReadConsoleShutdown()
1✔
474

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

477
      # Read runlevel setting
478
      ReadServiceSettings()
1✔
479

480
      read_pam_settings
1✔
481

482
      # Local permissions hack
483
      read_permissions
1✔
484

485
      read_polkit_settings
1✔
486

487
      read_kernel_settings
1✔
488

489
      read_lsm_config
1✔
490

491
      # remember the read values
492
      @Settings_bak = deep_copy(@Settings)
1✔
493

494
      log.info "Settings after Read: #{@Settings}"
1✔
495
      true
1✔
496
    end
497

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

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

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

554
    # Write login.defs configuration
555
    def write_shadow_config
1✔
556
      SHADOW_ATTRS.each do |attr|
2✔
557
        shadow_config.public_send("#{attr.to_s.downcase}=", @Settings[attr])
30✔
558
      end
559
      encr = @Settings.fetch("PASSWD_ENCRYPTION", default_encrypt_method)
2✔
560
      shadow_config.encrypt_method = encr if encr != @Settings_bak["PASSWD_ENCRYPTION"]
2✔
561
      shadow_config.save
2✔
562
    end
563

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

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

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

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

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

620
      # In case of modified, always write the changes (bsc#1167234)
621
      sysctl_config.save if written
7✔
622
      written
7✔
623
    end
624

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

640
    # Apply sysctl settings from all the sysctl configuration files
641
    def apply_sysctl_changes
1✔
642
      # Reports if there are conflict when the configuration is applied
643
      sysctl_config.conflict?
3✔
644

645
      Yast::Execute.on_target("/usr/sbin/sysctl", "--system")
3✔
646
    end
647

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

659
      # ensure polkit privileges are applied (bnc #541393)
660
      if FileUtils.Exists("/sbin/set_polkit_default_privs")
5✔
661
        SCR.Execute(path(".target.bash"), "/sbin/set_polkit_default_privs")
1✔
662
      end
663
    end
664

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

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

681
    # Write all security settings
682
    # @return true on success
683
    def Write
1✔
684
      return true if !@modified
2✔
685

686
      log.info "Writing configuration"
2✔
687

688
      # Security read dialog caption
689
      caption = _("Saving Security Configuration")
2✔
690
      steps = 5
2✔
691

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

725
      log.debug "Settings=#{@Settings}"
2✔
726

727
      # Write security settings
728
      return false if Abort()
2✔
729

730
      Progress.NextStage
2✔
731
      if !@Settings["PERMISSION_SECURITY"].include?("local")
2✔
732
        @Settings["PERMISSION_SECURITY"] << " local"
2✔
733
      end
734
      write_to_locations
2✔
735
      write_shadow_config
2✔
736

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

740
      Progress.NextStage
2✔
741
      write_console_shutdown(@Settings.fetch("CONSOLE_SHUTDOWN", "ignore"))
2✔
742

743
      # Write authentication and privileges settings
744
      return false if Abort()
2✔
745

746
      Progress.NextStage
2✔
747
      write_pam_settings
2✔
748
      write_polkit_settings
2✔
749
      sysctl_modified = write_kernel_settings
2✔
750

751
      # Finish him
752
      return false if Abort()
2✔
753

754
      Progress.NextStage
2✔
755
      apply_new_settings(sysctl: sysctl_modified)
2✔
756

757
      return false if Abort()
2✔
758

759
      Progress.NextStage
2✔
760
      activate_changes
2✔
761

762
      return false if Abort()
2✔
763

764
      Progress.NextStage
2✔
765
      write_lsm_config
2✔
766

767
      return false if Abort()
2✔
768

769
      @modified = false
2✔
770
      true
2✔
771
    end
772

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

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

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

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

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

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

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

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

840
    # Dump the security settings to a single map
841
    # (For use by autoinstallation.)
842
    # @return [Hash] Dumped settings (later acceptable by Import ())
843
    def Export
1✔
844
      settings = deep_copy(@Settings)
4✔
845
      # conversion to 0/1 string
846
      ["net.ipv4.tcp_syncookies", "net.ipv4.ip_forward", "net.ipv6.conf.all.forwarding"].each do |key|
4✔
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
2✔
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 && 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
66✔
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
    if names
4✔
1065
      names.split.map {|name| name.sub(/\.service$/, "") }
6✔
1066
    else
1067
      nil
1068
    end
1069
  end
1070

1071
  Security = SecurityClass.new
1✔
1072
  Security.main
1✔
1073
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