|
| 1 | +# SUSE's openQA tests |
| 2 | +# |
| 3 | +# Copyright 2025 SUSE LLC |
| 4 | +# |
| 5 | +# SUSE's openQA tests |
| 6 | +# Package: selinux-policy |
| 7 | +# Summary: test selinux-policy migration |
| 8 | +# Maintainer: Gayane Osipyan <[email protected]> |
| 9 | + |
| 10 | +use base 'selinuxtest'; |
| 11 | +use testapi; |
| 12 | +use serial_terminal 'select_serial_terminal'; |
| 13 | +use utils; |
| 14 | +use version_utils qw(is_sle is_microos is_leap is_tumbleweed is_sle_micro has_selinux); |
| 15 | +use transactional qw(process_reboot trup_call); |
| 16 | +use Utils::Architectures; |
| 17 | + |
| 18 | +sub run { |
| 19 | + select_serial_terminal; |
| 20 | + initial_state(); |
| 21 | + check_dir(); |
| 22 | + my $rollback_number = create_snapshot(); |
| 23 | + update_system(); |
| 24 | + check_paths(); |
| 25 | + check_after_update(); |
| 26 | + check_custom_policy(); |
| 27 | + rollback_and_verify_state($rollback_number); |
| 28 | + cleanup_test_artifacts(); |
| 29 | +} |
| 30 | + |
| 31 | +# set up the initial test environment with packages, users, and custom policies |
| 32 | +sub initial_state { |
| 33 | + record_info('Verifying SELinux status and installing packages.'); |
| 34 | + assert_script_run('sestatus | grep "SELinux status:" | awk "{print $3}" | grep -q "enabled"'); |
| 35 | + # install selinux packages |
| 36 | + if (is_microos) { |
| 37 | + trup_call('pkg install selinux-policy-targeted selinux-policy-targeted-gaming policycoreutils policycoreutils-python-utils setools-console podman'); |
| 38 | + process_reboot(trigger => 1); |
| 39 | + } |
| 40 | + else { |
| 41 | + zypper_call('install selinux-policy-targeted selinux-policy-targeted-gaming policycoreutils policycoreutils-python-utils setools-console podman'); |
| 42 | + } |
| 43 | + record_info('Creating test user and custom policy.'); |
| 44 | + assert_script_run('useradd testselinux'); |
| 45 | + assert_script_run('echo testselinux:testpasswd | chpasswd'); |
| 46 | + assert_script_run('semanage login -a -s staff_u testselinux'); |
| 47 | + assert_script_run('semanage port -a -t http_port_t -p tcp 8888'); |
| 48 | + assert_script_run('semanage port -l | grep -E "(^SELinux Port Type|http_port_t).*8888"'); |
| 49 | + assert_script_run('podman run -d --name test1 busybox sleep infinity'); |
| 50 | + assert_script_run('podman ps --filter name=test1'); |
| 51 | + create_custom_module('mycustom', 'httpd_t', 'tcp_socket name_connect', 'allow httpd_t self:tcp_socket name_connect;'); |
| 52 | + enable_boolean('httpd_can_network_connect_db'); |
| 53 | + capture_current_state(); |
| 54 | +} |
| 55 | + |
| 56 | +# create a new SELinux module from a template |
| 57 | +sub create_custom_module { |
| 58 | + my ($name, $type, $class_perm, $policy_rule) = @_; |
| 59 | + record_info("Creating and installing custom module: $name"); |
| 60 | + my $te_content = "module $name 1.0;\n\nrequire {\n type $type;\n class $class_perm;\n};\n\n$policy_rule"; |
| 61 | + assert_script_run("echo '$te_content' > ${name}.te"); |
| 62 | + assert_script_run("checkmodule -M -m -o ${name}.mod ${name}.te"); |
| 63 | + assert_script_run("semodule_package -o ${name}.pp -m ${name}.mod"); |
| 64 | + assert_script_run("semodule -i ${name}.pp"); |
| 65 | + assert_script_run("semodule -l | grep -q '$name'"); |
| 66 | +} |
| 67 | + |
| 68 | +# enable a specific SELinux boolean |
| 69 | +sub enable_boolean { |
| 70 | + my ($boolean_name) = @_; |
| 71 | + record_info("Enabling boolean: $boolean_name"); |
| 72 | + assert_script_run("setsebool -P $boolean_name on"); |
| 73 | + assert_script_run("getsebool $boolean_name | grep -q 'on'"); |
| 74 | +} |
| 75 | + |
| 76 | +# save the current system state for later verification |
| 77 | +sub capture_current_state { |
| 78 | + record_info('Capturing system state before update.'); |
| 79 | + script_run('semodule -l > semodule_list_before_migration.txt'); |
| 80 | + script_run('semanage boolean -l > semanage_booleans_before_migration.txt'); |
| 81 | + script_run('semanage login -l > semanage_login_before_migration.txt'); |
| 82 | + script_run('semanage port -l > semanage_ports_before_migration.txt'); |
| 83 | + script_run('semanage fcontext -l > semanage_fcontexts_before_migration.txt'); |
| 84 | +} |
| 85 | + |
| 86 | +# create a snapper snapshot |
| 87 | +sub create_snapshot { |
| 88 | + record_info('Creating snapshot for rollback.'); |
| 89 | + my $rollback_number = script_output('snapper create -d "Before SELinux update" -p'); |
| 90 | + script_output('snapper list'); |
| 91 | + return $rollback_number; |
| 92 | +} |
| 93 | + |
| 94 | +# SELinux policy package update |
| 95 | +sub update_system { |
| 96 | + record_info('Updating environment'); |
| 97 | + zypper_call("--gpg-auto-import-keys ref"); |
| 98 | + if (is_microos) { |
| 99 | + validate_script_output('sestatus', sub { m/SELinux status: .*enabled/ && m/Current mode: .*enforcing/ }, fail_message => 'SELinux is NOT enabled and set to enforcing'); |
| 100 | + trup_call('dup', timeout => 600); |
| 101 | + process_reboot(trigger => 1); |
| 102 | + } |
| 103 | + else { |
| 104 | + zypper_call('dup --force-resolution --allow-vendor-change --no-confirm'); |
| 105 | + } |
| 106 | + zypper_call('info selinux-policy selinux-policy-targeted selinux-policy-targeted-gaming libsemanage-conf libsemanage2 policycoreutils policycoreutils-python-utils setools-console container-selinux'); |
| 107 | + record_info('Adding a second custom module after update.'); |
| 108 | + create_custom_module('mycustom2', 'sshd_t', 'process setrlimit', 'allow sshd_t self:process setrlimit;'); |
| 109 | + check_cleanoldspoldir_service(); |
| 110 | +} |
| 111 | + |
| 112 | +# check the system state after the update |
| 113 | +sub check_after_update { |
| 114 | + record_info('Verifying system state after update.'); |
| 115 | + script_run('diff -q semodule_list_before_migration.txt <(semodule -l)'); |
| 116 | + script_run('diff -q semanage_booleans_before_migration.txt <(semanage boolean -l)'); |
| 117 | + script_run('diff -q semanage_login_before_migration.txt <(semanage login -l)'); |
| 118 | + my $module_list = script_output('semodule -l'); |
| 119 | + for my $m (qw(mycustom mycustom2)) { |
| 120 | + if ($module_list =~ /^\Q$m\E\b/m) { |
| 121 | + print "$m present"; |
| 122 | + } else { |
| 123 | + record_info("Module '$m' not found in semodule -l output\n", result => "fail"); |
| 124 | + } |
| 125 | + } |
| 126 | + check_gaming_boolean(); |
| 127 | +} |
| 128 | + |
| 129 | +# roll back the system to the pre-update state and verify |
| 130 | +sub rollback_and_verify_state { |
| 131 | + my ($rollback_number) = @_; |
| 132 | + record_info("Rolling back to snapshot $rollback_number."); |
| 133 | + assert_script_run("snapper rollback $rollback_number"); |
| 134 | + record_info('Verifying system state after rollback.'); |
| 135 | + my $module_list = script_output('semodule -l'); |
| 136 | + # check that 'mycustom' present |
| 137 | + for my $m (qw(mycustom)) { |
| 138 | + if ($module_list =~ /^\Q$m\E\b/m) { |
| 139 | + print "$m present"; |
| 140 | + } else { |
| 141 | + record_info("Module '$m' not found in semodule -l output\n", result => "fail"); |
| 142 | + } |
| 143 | + } |
| 144 | + assert_script_run('semanage boolean -l | grep -q "httpd_can_network_connect_db.* on"'); |
| 145 | + assert_script_run('semanage login -l | grep -q "testselinux.*staff_u"'); |
| 146 | + assert_script_run('semanage port -l | grep -E "(^SELinux Port Type|http_port_t).*8888"'); |
| 147 | + assert_script_run('podman ps --filter name=test1'); |
| 148 | + if (script_run('ps -eZ | grep $(podman inspect -f "{{.State.Pid}}" test1) | grep -q "container_t"') == 0) { |
| 149 | + record_info("test1 has correct selinux label"); |
| 150 | + } |
| 151 | + else { |
| 152 | + record_info("wrong selinux label", result => "fail"); |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +# test selinux-policy-targeted-gaming boolean |
| 157 | +sub check_gaming_boolean { |
| 158 | + record_info('Verify gaming boolean'); |
| 159 | + zypper_call('in selinux-policy-targeted-gaming'); |
| 160 | + for my $boolean (qw(selinuxuser_execstack selinuxuser_execmod)) { |
| 161 | + my $out = script_output("getsebool $boolean"); |
| 162 | + if ($out =~ /on$/) { |
| 163 | + record_info($boolean, "Is on"); |
| 164 | + } else { |
| 165 | + die "$boolean, Is off or missing"; |
| 166 | + } |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +# no packages install in /var/lib/selinux |
| 171 | +sub check_paths { |
| 172 | + my @packages = @_; |
| 173 | + @packages = qw(selinux-policy selinux-policy-targeted selinux-policy-targeted-gaming libsemanage-conf libsemanage2 policycoreutils policycoreutils-python-utils setools-console) unless @packages; |
| 174 | + foreach my $package (@packages) { |
| 175 | + if (script_run("rpm -qvl $package | grep -q '/var/lib/selinux'") == 0) { |
| 176 | + record_info("[FAIL]", "$package contain /var/lib/selinux paths", result => "fail"); |
| 177 | + } |
| 178 | + else { |
| 179 | + script_run('echo "[PASS] no /var/lib/selinux paths found"'); |
| 180 | + record_info("[PASS] no /var/lib/selinux paths found"); |
| 181 | + } |
| 182 | + } |
| 183 | +} |
| 184 | + |
| 185 | +## check /var/lib/selinux deleted |
| 186 | +sub check_dir { |
| 187 | + if (script_run("grep -rq '/var/lib/selinux/' /.snapshots/*") == 0) { |
| 188 | + record_info("[FAIL]", "/var/lib/selinux exist in old snapshots", result => "fail"); |
| 189 | + } |
| 190 | + elsif (-d "/var/lib/selinux") { |
| 191 | + record_info("[FAIL]", "/var/lib/selinux not deleted", result => "fail"); |
| 192 | + } |
| 193 | + else { |
| 194 | + record_info("[PASS]", "/var/lib/selinux not present"); |
| 195 | + } |
| 196 | +} |
| 197 | + |
| 198 | +# check cleanoldsepoldir.service |
| 199 | +sub check_cleanoldspoldir_service { |
| 200 | + if (script_run("systemctl list-unit-files --type=service | grep -qw cleanoldsepoldir.service") == 0 && script_output("systemctl is-enabled cleanoldsepoldir.service"=="enabled")){ |
| 201 | + record_info("[PASS]", "cleanoldsepoldir.service enabled", result => "fail"); |
| 202 | + } else { |
| 203 | + record_info("[FAIL]", "cleanoldsepoldir.service not detected", result => "fail"); |
| 204 | + } |
| 205 | +} |
| 206 | + |
| 207 | +# add custom modules |
| 208 | +sub check_custom_policy { |
| 209 | + # get list of SELinux specific modules |
| 210 | + my $modules = script_output('zypper -n se -s | awk "{print \$2}" | grep -E -- "-selinux$" | sort -u', timeout => 300); |
| 211 | + my @packages; |
| 212 | + foreach my $module (split /\n/, $modules) { |
| 213 | + next unless $module; |
| 214 | + next if ($module eq 'forgejo-selinux' || $module eq 'rke2-selinux'); |
| 215 | + push @packages, $module; |
| 216 | + } |
| 217 | + return unless @packages; |
| 218 | + print(@packages); |
| 219 | + my $package_list = join(' ', @packages); |
| 220 | + record_info("Installing packages", $package_list); |
| 221 | + if (is_microos) { |
| 222 | + trup_call("pkg install $package_list"); |
| 223 | + process_reboot(trigger => 1); |
| 224 | + } |
| 225 | + else { |
| 226 | + script_run("zypper -n in -y $package_list", timeout => 600); |
| 227 | + } |
| 228 | + check_paths(@packages); |
| 229 | +} |
| 230 | + |
| 231 | +# clean up all temporary files, users, and policy modules. |
| 232 | +sub cleanup_test_artifacts { |
| 233 | + record_info('Cleaning up test environment.'); |
| 234 | + script_run('semodule -r mycustom || true'); |
| 235 | + script_run('semodule -r mycustom2 || true'); |
| 236 | + script_run('userdel -r testselinux || true'); |
| 237 | + script_run('semanage login -d testselinux || true'); |
| 238 | + script_run('rm -f mycustom*.{te,mod,pp}'); |
| 239 | + script_run('rm -f semodule_list_before_migration.txt semanage_booleans_before_migration.txt semanage_login_before_migration.txt'); |
| 240 | + script_run('rm -f semanage_ports_before_migration.txt semanage_fcontexts_before_migration.txt'); |
| 241 | + script_run('snapper list | grep "Before SELinux update" | awk "{print \$1}" | xargs -r snapper delete'); |
| 242 | +} |
| 243 | +1; |
0 commit comments