-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathtest_generic.py
More file actions
1394 lines (1141 loc) · 53.8 KB
/
test_generic.py
File metadata and controls
1394 lines (1141 loc) · 53.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import os
import re
import time
import pytest
from packaging import version
from test_suite.generic import helpers
from lib import console_lib
from lib import test_lib
@pytest.fixture(scope='class')
def check_kdump_fix_condition(host):
arch = host.system_info.arch
if arch == 'aarch64':
get_ram_size = host.check_output("free -h | awk '/^Mem:/ {print $2}'")
ram_size = (float(get_ram_size[:-2]))
kernel_version = host.check_output('uname -r').split("-")[0]
# BugZilla 2214235
if version.parse(kernel_version) < version.parse('4.18.0'):
pytest.skip(f'Skip on arm64 with kernel version {kernel_version}')
if version.parse(kernel_version) > version.parse('4.18.0') and ram_size <= 4.0:
pytest.skip('Skip on arm64 if kernel version higher than 4.18.0 '
'while ram size is smaller than 4Gib')
@pytest.mark.order(1)
class TestsGeneric:
@pytest.mark.run_on(['all'])
def test_no_avc_denials(self, host, instance_data):
"""
Check there is no avc denials (selinux).
"""
helpers.check_avc_denials(host)
@pytest.mark.run_on(['all'])
def test_bash_history_is_empty(self, host):
users = [host.user().name, 'root']
for u in users:
file_path = f'/home/{u}/.bash_history'
bash_history_file = host.file(file_path)
if bash_history_file.exists:
file_content_length = len(bash_history_file.content_string)
assert file_content_length == 0, f'{file_path} must be empty or nonexistent'
@pytest.mark.run_on(['rhel'])
def test_blocklist(self, host, instance_data):
"""
Check that a list of modules are disabled - not loaded.
"""
modules = ['nouveau', 'amdgpu']
if instance_data['cloud'] == 'azure':
modules.extend(['acpi_cpufreq', 'floppy', 'intel_uncore', 'intel_cstate', 'skylake-edac'])
# blocklist_conf = '/usr/lib/modprobe.d/blacklist-{module}.conf'
# files_to_check = [blocklist_conf.format(module=modules[x]) for x in range(len(modules))]
# blocklist_conf_strings = ['blacklist ' + x for x in modules]
loaded_modules = []
with host.sudo():
for module in modules:
if host.run(f'lsmod | grep -w {module}').stdout:
loaded_modules.append(module)
assert not loaded_modules, \
f"The following modules should not be loaded: {', '.join(loaded_modules)}"
# ToDo: CLOUDX-1518
# for file, str_to_check in zip(files_to_check, blocklist_conf_strings):
# assert host.file(file).exists, f'file "{file}" does not exist'
# assert host.file(file).contains(str_to_check), \
# f'{str_to_check} is not blocklisted in "{file}"'
# TODO: Confirm if this test should be run in non-RHEL images
@pytest.mark.run_on(['rhel'])
def test_username(self, host, instance_data):
for user in ['fedora', 'cloud-user']:
with host.sudo():
assert not host.user(user).exists, 'Unexpected username in instance'
assert host.check_output('whoami') == instance_data['username']
@pytest.mark.run_on(['rhel'])
def test_cmdline_console_and_params(self, host, instance_data):
"""
Verify the console and other required parameters in the kernel command line.
"""
file_to_check = '/proc/cmdline'
expected_config = []
# Define expected console based on architecture and platform
if host.system_info.arch == 'aarch64' and instance_data['cloud'] == 'azure':
expected_config.append('console=ttyAMA0')
else:
expected_config.append('console=ttyS0')
# Add Azure-specific parameters
if host.system_info.arch == 'x86_64' and instance_data['cloud'] == 'azure':
expected_config.extend(['earlyprintk=ttyS0', 'rootdelay=300'])
# Add RHEL 9.6+ specific parameter
if version.parse(host.system_info.release) >= version.parse('9.6') and \
instance_data['cloud'] == 'azure':
expected_config.append('nvme_core.io_timeout=240')
with host.sudo():
for item in expected_config:
assert host.file(file_to_check).contains(item), \
f'{item} was expected in {file_to_check}'
# TODO: does this apply to fedora and centos
@pytest.mark.run_on(['rhel'])
def test_crashkernel_is_enabled_rhel(self, host):
"""
Check that crashkernel is enabled in image.
"""
system_release = version.parse(host.system_info.release)
if system_release < version.parse('9.0'):
expected_content = 'crashkernel=auto'
else:
with host.sudo():
expected_content = str(host.check_output('kdumpctl showmem 2>&1 | grep -oP "[0-9]*"'))
with host.sudo():
print(console_lib.print_debug({"expected_content": expected_content,
"/proc/cmdline content": host.file("/proc/cmdline").content_string,
"kdumpctl showmem": host.check_output("kdumpctl showmem 2>&1"),
"kexec-tools version": host.package("kexec-tools").version}))
assert host.file('/proc/cmdline').contains(expected_content), \
'crashkernel must be enabled'
@pytest.mark.run_on(['all'])
def test_cpu_flags_are_correct(self, host, instance_data):
"""
Check various CPU flags for x86_64 instances.
BugZilla 1061348
"""
current_arch = host.system_info.arch
if current_arch != 'x86_64':
pytest.skip(f'Not applicable to {current_arch}')
expected_flags = [
'avx',
'xsave',
]
if instance_data['cloud'] == 'azure':
expected_flags.append('pcid')
with host.sudo():
for flag in expected_flags:
assert host.file('/proc/cpuinfo').contains(flag), \
f'Expected CPU flag "{flag}" not set'
@pytest.mark.run_on(['all'])
def test_rhgb_quiet_not_present_in_cmdline(self, host):
"""
Check that there is no "rhgb" or "quiet" in /proc/cmdline.
BugZilla 1122300
"""
excluded_settings = [
'rhgb',
'quiet',
]
with host.sudo():
for setting in excluded_settings:
assert not host.file('/proc/cmdline').contains(setting), \
f'{setting} must not be present in cmdline'
@pytest.mark.run_on(['all'])
def test_numa_settings(self, host):
"""
Check if NUMA is enabled on supported image.
"""
with host.sudo():
assert host.run_test('dmesg | grep -i numa'), \
'There is no NUMA information available'
lscpu_numa_nodes = host.check_output("lscpu | grep -i 'NUMA node(s)' | awk -F' ' '{print $NF}'")
dmesg_numa_nodes = host.check_output("dmesg | grep -i 'No NUMA'|wc -l")
if int(lscpu_numa_nodes) > 1:
assert dmesg_numa_nodes > 1, \
f'NUMA seems to be disabled, when it should be enabled (NUMA nodes: {lscpu_numa_nodes})'
@pytest.mark.run_on(['rhel'])
def test_cert_product_version_is_correct(self, host):
"""
BugZilla 1938930
Issue RHELPLAN-60817
"""
system_release = version.parse(host.system_info.release)
rpm_to_check = 'redhat-release'
with host.sudo():
host.run_test(f'rpm -q {rpm_to_check}')
cert_version = host.check_output('rct cat-cert /etc/pki/product-default/*.pem | grep Version')
assert f'Version: {system_release}' in cert_version, \
'Inconsistent version in pki certificate'
@pytest.mark.run_on(['all'])
def test_inittab_and_systemd(self, host):
"""
Check default runlevel or systemd target.
"""
kernel_release = host.check_output('uname -r')
print(f'Kernel release: {kernel_release}')
with host.sudo():
if host.package('systemd').is_installed:
assert '/lib/systemd/system/multi-user.target' in \
host.check_output('readlink -f /etc/systemd/system/default.target'), \
'Unexpected systemd default target'
else:
assert 'id:3:initdefault' in host.check_output("grep '^id:' /etc/inittab"), \
'Unexpected default inittab "id"'
if 'el5' in kernel_release:
assert 'si::sysinit:/etc/rc.d/rc.sysinit' in host.check_output("grep '^si:' /etc/inittab"), \
'Unexpected default inittab "id"'
@pytest.mark.run_on(['rhel', 'fedora'])
def test_release_version(self, host):
"""
Check if release package version matches /etc/redhat-release
"""
system_release = version.parse(host.system_info.release)
release_file = 'redhat-release'
if host.system_info.distribution == 'fedora':
release_file = 'fedora-release'
with host.sudo():
command_to_run = "rpm -q --qf '%{VERSION}' --whatprovides " + release_file
package_release = version.parse(host.check_output(command_to_run))
assert system_release == package_release, \
f'Product version ({system_release}) does not match package release version'
@pytest.mark.run_on(['rhel'])
def test_root_is_locked(self, host):
"""
Check if root account is locked
"""
with host.sudo():
if version.parse(host.system_info.release).major >= 10:
result = host.run('passwd -S root | grep -q L').rc
else:
result = host.run('passwd -S root | grep -q LK').rc
assert result == 0, 'Root account should be locked'
@pytest.mark.run_on(['all'])
def test_bash_in_shell_config(self, host):
"""
Check for bash/nologin shells in /etc/shells
"""
assert host.file('/etc/shells').contains('/bin/bash'), \
'/bin/bash is not declared in /etc/shells'
# TODO: create test case for aarch64 grub config file
@pytest.mark.run_on(['rhel'])
def test_grub_config(self, host):
current_arch = host.system_info.arch
if current_arch != 'x86_64':
pytest.skip(f'Not applicable to {current_arch}')
grub2_file = '/boot/grub2/grubenv'
linked_to = grub2_file
with host.sudo():
if host.file('/sys/firmware/efi').exists:
if version.parse(host.system_info.release) < version.parse('8.0'):
linked_to = '/boot/efi/EFI/redhat/grubenv'
assert host.file(grub2_file).linked_to == linked_to
@pytest.mark.run_on(['all'])
def test_boot_mount_presence(self, host, instance_data):
"""
The /boot mount exists if
* 8.y and aarch64
* 9.y
* 10.y and Azure and LVM
* Fedora
In all other cases the /boot mount doesn't exist on a system.
If /boot exists it should be at least 960Mib (lower threshold of 1024MiB)
JIRA: CLOUDX-930, CLOUDX-980
"""
release_major = version.parse(host.system_info.release).major
is_aarch64 = host.system_info.arch == 'aarch64'
is_azure = instance_data['cloud'] == 'azure'
lvm_check = host.run("lsblk -f | grep LVM").rc == 0
is_fedora = host.system_info.distribution == 'fedora'
if (
(release_major == 8 and is_aarch64)
or (release_major == 9)
or (release_major >= 10 and is_azure and lvm_check)
or is_fedora
):
assert host.mount_point("/boot").exists, "/boot mount is missing"
result = host.run("df --block-size=1 /boot | tail -1")
parts = result.stdout.split()
total_bytes = int(parts[1])
min_size_mib = 960
required_size = min_size_mib * 1024 * 1024 # 960MiB
assert total_bytes >= required_size, \
f'Partition /boot is too small: {total_bytes} bytes'
else:
assert not host.mount_point("/boot").exists, "/boot mount is detected"
@pytest.mark.run_on(['rhel'])
def test_net_ifnames_usage(self, host, instance_data):
"""
CLOUDX-979, RHELPLAN-103894 drop net.ifnames=0 kernel boot parameter on RHEL10 and later
BZ1859926 ifnames should be specified on AWS for RHEL9 and earlier releases
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking-ena.html
"""
kernel_boot_param = 'net.ifnames=0'
cmdline_file = '/proc/cmdline'
grub_default_file = '/etc/default/grub'
system_release_major = version.parse(host.system_info.release).major
if system_release_major >= 10:
assert not host.file(cmdline_file).contains(kernel_boot_param), \
f'There is unexpected {kernel_boot_param} in kernel real-time boot parameters.'
assert not host.file(grub_default_file).contains(kernel_boot_param), \
f'{kernel_boot_param} is found in {grub_default_file}!'
else:
if instance_data['cloud'] == 'aws':
with host.sudo():
assert host.file(cmdline_file).contains(kernel_boot_param), \
'ifnames expected to be specified'
@pytest.mark.run_on(['rhel'])
def test_tty0_config(self, host):
"""
BugZilla 1103344
Check that "/etc/init/ttyS0.conf" and its backup file do not exist.
"""
with host.sudo():
assert not host.file('/etc/init/ttyS0.conf').exists, 'ttyS0.conf file should not exist'
assert not host.file('/etc/init/ttyS0.bak').exists, 'ttyS0.conf backup file should not exist'
@pytest.mark.run_on(['rhel'])
def test_selinux_mode(self, host):
"""
BugZilla 1960628
SELinux should be in enforcing/targeted mode
"""
if test_lib.is_rhel_saphaus(host):
expected_mode = 'Permissive'
else:
expected_mode = 'Enforcing'
expected_file_config = [
f'SELINUX={expected_mode.lower()}',
'SELINUXTYPE=targeted'
]
selinux_config_file = '/etc/sysconfig/selinux'
with host.sudo():
assert host.check_output('getenforce') == expected_mode, \
f'SELinux should be in {expected_mode} mode'
for conf in expected_file_config:
assert host.file(selinux_config_file).contains(conf), \
f'Expected "{conf}" to be in {selinux_config_file}'
@pytest.mark.run_on(['all'])
def test_rpm_v_unsatisfied_dependencies(self, host):
"""
Check unsatisfied dependencies of pkgs.
"""
with host.sudo():
assert 'Unsatisfied' not in host.run('rpm -Va').stdout, \
'There are unsatisfied dependencies'
@pytest.mark.run_on(['all'])
def test_no_sshkeys_knownhosts(self, host):
"""
Verify no extra files under /root/.ssh/ except authorized_keys
"""
with host.sudo():
ssh_files = host.file('/root/.ssh/').listdir()
assert 'authorized_keys' in ssh_files, 'authorized_keys is not in /root/.ssh/'
assert len(ssh_files) == 1, 'there are extra files in /root/.ssh/'
@pytest.mark.run_on(['all'])
def test_no_extra_public_keys(self, host):
"""
Verify there is only one key in /root/.ssh/authorized_keys
BugZilla 2127969
"""
with host.sudo():
debug = host.file('/root/.ssh/authorized_keys').content_string
print(debug)
authorized_keys_lines = host.check_output('cat /root/.ssh/authorized_keys | wc -l')
assert authorized_keys_lines == '1', 'There is more than one public key in authorized_keys'
@pytest.mark.run_on(['rhel'])
def test_dnf_conf(self, host, instance_data):
"""
Check /etc/dnf/dnf.conf
"""
local_file = 'data/generic/dnf.conf'
file_to_check = '/etc/dnf/dnf.conf'
if instance_data['cloud'] == 'gcloud':
local_file = 'data/google/dnf.conf'
assert test_lib.compare_local_and_remote_file(host, local_file, file_to_check), \
f'{file_to_check} has unexpected content'
@pytest.mark.run_on(['rhel'])
def test_langpacks_conf(self, host):
"""
Verify /etc/yum/pluginconf.d/langpacks.conf
"""
file_to_check = '/etc/yum/pluginconf.d/langpacks.conf'
with host.sudo():
assert not host.file(file_to_check).exists, \
f'{file_to_check} should not exist in RHEL-8 and above'
@pytest.mark.run_on(['all'])
def test_timezone_is_utc(self, host):
"""
Check that the default timezone is set to UTC.
BugZilla 1187669
"""
timezone = host.check_output('date +%Z').strip()
assert timezone == 'UTC', f'Unexpected timezone: {timezone}. Expected to be UTC'
@pytest.mark.run_on(['>=rhel9.6', 'rhel10'])
def test_bootc_installed(self, host):
"""
Verify the system-reinstall-bootc package is installed
JIRA: CLOUDX-1267
"""
with host.sudo():
print('rpm -q output: ')
print(host.run('rpm -q system-reinstall-bootc').stdout)
print('yum search output: ')
print(host.run('yum search system-reinstall-bootc').stdout)
assert host.package("system-reinstall-bootc").is_installed, \
'System-reinstall-bootc package expected to be installed in RHEL >= 9.6, 10.0'
@pytest.mark.run_on(['rhel'])
def test_logging_cfg(self, host):
"""
Check /etc/cloud/cloud.cfg.d/05_logging.cfg
"""
file_to_check = '/etc/cloud/cloud.cfg.d/05_logging.cfg'
local_file = 'data/generic/05_logging.cfg'
assert test_lib.compare_local_and_remote_file(host, local_file, file_to_check), \
f'{file_to_check} has unexpected content'
@pytest.mark.run_on(['rhel'])
def test_authconfig_file(self, host):
"""
Verify no /etc/sysconfig/authconfig file in RHEL8 and later
"""
file_to_check = '/etc/sysconfig/authconfig'
assert not host.file(file_to_check).exists, \
f'{file_to_check} should not exist in RHEL 8 and later'
@pytest.mark.run_on(['all'])
@pytest.mark.exclude_on(['fedora'])
def test_services_running(self, host, instance_data):
"""
Verify the necessary services are running
"""
service_list = [
'cloud-init-local', 'cloud-init',
'cloud-config', 'cloud-final', 'sshd',
]
if instance_data['cloud'] == 'azure':
service_list.extend(['waagent', 'hypervkvpd'])
with host.sudo():
for service in service_list:
assert host.service(service).is_running
# TODO: verify logic, think if we should divide
@pytest.mark.run_on(['rhel'])
def test_auditd(self, host):
"""
- Service should be running
- Config files should have the correct MD5 checksums
"""
checksums_by_version = {
'9.4+': {
'/etc/audit/auditd.conf': 'fd5c639b8b1bd57c486dab75985ad9af',
'/etc/audit/audit.rules': '795528bd4c7b4131455c15d5d49991bb'
},
'8.10+': {
'/etc/audit/auditd.conf': 'fd5c639b8b1bd57c486dab75985ad9af',
'/etc/audit/audit.rules': '795528bd4c7b4131455c15d5d49991bb'
},
'8.6+': {
'/etc/audit/auditd.conf': 'f87a9480f14adc13605b7b14b7df7dda',
'/etc/audit/audit.rules': '795528bd4c7b4131455c15d5d49991bb'
}
}
auditd_service = 'auditd'
assert host.service(
auditd_service).is_running, f'{auditd_service} expected to be running'
system_release = version.parse(host.system_info.release)
if system_release >= version.parse('9.4'):
checksums = checksums_by_version['9.4+']
elif version.parse('9.0') > system_release >= version.parse('8.10'):
checksums = checksums_by_version['8.10+']
else:
checksums = checksums_by_version['8.6+']
with host.sudo():
for path, md5 in checksums.items():
assert md5 in host.check_output(
f'md5sum {path}'), f'Unexpected checksum for {path}'
@pytest.mark.run_on(['rhel'])
@pytest.mark.usefixtures('rhel_aws_marketplace_only')
def test_ha_specific_script(self, host, instance_data):
"""
Verify HA functionality on RHEL HA and RHEL SAP HA and US images
Skip AWS 3p amis since they don't have billing codes and
and therefore no RHUI access in stage.
"""
# Run on HA or SAP+HA images only
is_ha = test_lib.is_rhel_high_availability(host)
is_sap_ha = test_lib.is_rhel_saphaus(host)
if not (is_ha or is_sap_ha):
pytest.skip("Not a HA or SAP+HA image.")
cloud = instance_data['cloud'].lower()
local_file_path = f'scripts/rhel-ha-{cloud}-check.sh'
expected_success_message = "HA check passed successfully."
result = None
try:
result = test_lib.run_local_script_in_host(host, local_file_path)
finally:
if result and result.rc != 0:
print(f"Script stdout:\n{result.stdout}")
print(f"Script stderr:\n{result.stderr}")
assert result is not None, "HA check script did not return a result."
assert result.rc == 0, \
f"HA check script for cloud '{cloud}' failed with rc={result.rc}"
assert expected_success_message in result.stdout, \
"There is no the expected success message in the script stdout."
@pytest.mark.pub
@pytest.mark.run_on(['all'])
def test_pkg_signature_and_gpg_keys(self, host):
"""
Checks that packages have a valid GPG signature,
either SIGPGP or RSAHEADER, and that a single GPG key is used.
"""
with host.sudo():
# Query all installed RPMs and their GPG signature status
rpm_signature_query_cmd = (
"rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE} "
"SIGPGP:%{SIGPGP:pgpsig} RSAHEADER:%{RSAHEADER:pgpsig}\\n'"
)
filter_gpg_pubkey = f"{rpm_signature_query_cmd} | grep -v gpg-pubkey"
# Get all lines for software packages
package_signature_lines = host.check_output(filter_gpg_pubkey).splitlines()
unsigned_packages = []
for line in package_signature_lines:
if 'SIGPGP:(none)' in line and 'RSAHEADER:(none)' in line:
unsigned_packages.append(line)
# Construct a detailed error message if unsigned packages are found.
error_message = (
"ERROR: The following software packages were found to be installed "
"without a valid GPG signature:\n"
f"{chr(10).join(unsigned_packages)}\n"
"This indicates that signature verification might be disabled, or "
"packages were installed with '--nogpgcheck'. "
"Ensure 'gpgcheck=1' is set for all enabled repositories and "
"packages are from trusted sources."
)
# Assert that no unsigned packages were found.
assert not unsigned_packages, error_message
# check use only one keyid
rpm_signatures_cmd = (
"rpm -qa --qf '%{NAME} %{SIGPGP:pgpsig} %{RSAHEADER:pgpsig}\\n'"
)
key_ids_command = ' '.join([rpm_signatures_cmd,
"| grep -vE '(gpg-pubkey|rhui)'",
"| awk '{if ($2 != \"(none)\") print $2; else if ($3 != \"(none)\") print $3}'",
"| awk -F'Key ID ' '{print $2}'",
"| sort | uniq | wc -l"])
assert int(host.check_output(key_ids_command)) == 1, \
'Number of key IDs for rhui pkgs should be 1'
@pytest.mark.order(3)
class TestsServices:
@pytest.mark.run_on(['all'])
def test_sshd(self, host):
"""
Verify that SSH password authentication is disabled.
By default, the configuration is in /etc/ssh/sshd_config
Starting from Fedora 38, the configuration is in /etc/ssh/sshd_config.d/50-cloud-init.conf
Starting from RHEL 9.3 and 8.9, the configuration is in /etc/ssh/sshd_config.d/50-redhat.conf
JIRA: CLOUDX-484, COMPOSER-1959
"""
# cloud-init >= 22.3 puts extra sshd configs into a separate file
# see https://github.com/canonical/cloud-init/pull/1618
possible_sshd_auth_config_settings = [
{
'path': '/etc/ssh/sshd_config',
'config_key': 'PasswordAuthentication',
'config_value': 'no'
},
{
'path': '/etc/ssh/sshd_config.d/50-cloud-init.conf',
'config_key': 'PasswordAuthentication',
'config_value': 'no'
},
{
'path': '/etc/ssh/sshd_config.d/50-redhat.conf',
'config_key': 'ChallengeResponseAuthentication',
'config_value': 'no'
},
]
with host.sudo():
print(f' - openssh-server version: {host.run("rpm -qa | grep openssh-server").stdout}')
sshd = host.service('sshd')
if not sshd.is_running:
print(f' - journalctl -u sshd.service: {host.check_output("journalctl -u sshd.service")}')
pytest.fail('ssh.service is not running')
is_sshd_auth_forced_to_disabled = False
for possible_config in possible_sshd_auth_config_settings:
file_path = possible_config['path']
config_key = possible_config['config_key']
config_value = possible_config['config_value']
if host.file(file_path).exists and host.file(file_path).contains(f'^{config_key} {config_value}'):
print(host.run(f'ls -l {file_path}').stdout)
print('SSH password authentication config found:')
print(f'\tFile path:\t{file_path}')
print(f'\tConfig key:\t{config_key}')
print(f'\tConfig value:\t{config_value}')
print('-' * 50)
is_sshd_auth_forced_to_disabled = True
assert is_sshd_auth_forced_to_disabled, 'Password authentication via ssh must be disabled.'
@pytest.mark.run_on(['rhel', 'centos'])
def test_sysconfig_kernel(self, host):
"""
UPDATEDEFAULT=yes and DEFAULTKERNEL=kernel should be set in /etc/sysconfig/kernel
"""
kernel_config = '/etc/sysconfig/kernel'
with host.sudo():
assert host.file(kernel_config).contains('UPDATEDEFAULT=yes'), \
f'UPDATEDEFAULT should be set to `yes` in {kernel_config}'
assert host.file(kernel_config).contains('DEFAULTKERNEL=kernel'), \
f'DEFAULTKERNEL should be set to `kernel` in {kernel_config}'
@pytest.mark.run_on(['all'])
@pytest.mark.usefixtures('check_kdump_fix_condition')
def test_no_fail_service(self, host):
"""
Verify no failed service
"""
with host.sudo():
result = host.run('systemctl list-units | grep -i fail')
print(result.stdout)
failing_services = []
failing_service_regex = r'^● (?P<service>.*).service\s+'
failing_services_lines = result.stdout.split('\n')
for line in failing_services_lines:
regex_match = re.match(failing_service_regex, line)
if regex_match:
failing_service_data = regex_match.groupdict()
service_name = failing_service_data['service']
failing_services.append(service_name)
test_lib.print_host_command_output(host, f'systemctl status {service_name}')
test_lib.print_host_command_output(
host,
f'rpm -qf "$(systemctl show --value --property=FragmentPath {service_name})"'
)
failed_services_after_restart = []
if len(failing_services) > 0:
for service in failing_services:
print(f"{service} is failing, attempting restart...")
host.run(f"systemctl restart {service}")
time.sleep(5)
result = host.run(f"systemctl is-failed {service}")
if 'fail' in result.stdout:
print(f"Service {service} is still failing after restart")
failed_services_after_restart.append(service)
assert len(failed_services_after_restart) == 0, \
f'There are failing services: {",".join(failed_services_after_restart)}'
@pytest.mark.pub
@pytest.mark.run_on(['rhel'])
@pytest.mark.usefixtures('rhel_aws_marketplace_only')
class TestsSubscriptionManager:
def test_subscription_manager_auto(self, host, instance_data):
"""
BugZilla 8.4: 1932802, 1905398
BugZilla 7.9: 2077086, 2077085
"""
if instance_data['cloud'] == 'aws':
region = instance_data['availability_zone'][:-1]
# Refer to "Supported AWS AutoReg Regions" Google Spreadsheet (RHUI Team)
# https://docs.google.com/spreadsheets/d/15bcP0a9fBaxHVbk6tXiBL8Hn5fLjkHx9GjNf06ctISI/
unsupported_aws_regions = [
'ap-south-2',
'ap-southeast-4',
'eu-south-2',
'eu-central-2',
'us-gov-east-1',
'us-gov-west-1',
'cn-northwest-1',
'cn-north-1'
]
if region in unsupported_aws_regions:
pytest.skip(f'The {region} AWS region is not supported for auto-registration yet.')
with host.sudo():
assert host.service(
'rhsmcertd').is_enabled, 'rhsmcertd service must be enabled'
assert host.run_test('subscription-manager config --rhsmcertd.auto_registration_interval=1'), \
'Error while changing auto_registration_interval from 60min to 1min'
assert host.run_test(
'systemctl restart rhsmcertd'), 'Error while restarting rhsmcertd service'
start_time = time.time()
timeout = 360
interval = 30
while True:
assert host.file('/var/log/rhsm/rhsmcertd.log').exists
assert host.file('/var/log/rhsm/rhsm.log').exists
assert host.run_test('subscription-manager identity')
assert host.run_test('subscription-manager list --installed')
subscription_status = host.run(
'subscription-manager status').stdout
if 'Red Hat Enterprise Linux' in subscription_status or \
'Simple Content Access' in subscription_status:
print('Subscription auto-registration completed successfully')
if not host.run_test('insights-client --register'):
pytest.fail('insights-client command did not succeed after auto-registration completed')
break
end_time = time.time()
if end_time - start_time > timeout:
assert host.run_test('insights-client --register'), \
'insights-client could not register successfully'
pytest.fail(
f'Timeout ({timeout}s) while waiting for subscription auto-registration')
print(f'Waiting {interval}s for auto-registration to succeed...')
time.sleep(interval)
def test_subscription_manager_auto_config(self, host):
"""
BugZilla: 1932802, 1905398
Verify that auto_registration is enabled in the image
"""
expected_config = [
'auto_registration = 1',
'manage_repos = 0'
]
file_to_check = '/etc/rhsm/rhsm.conf'
with host.sudo():
for item in expected_config:
assert host.file(file_to_check).contains(item), \
f'{file_to_check} has unexpected content'
assert host.service('rhsmcertd').is_enabled, \
'rhsmcertd service is expected to be enabled'
@pytest.mark.order(1)
class TestsCloudInit:
@pytest.mark.run_on(['all'])
def test_growpart_is_present_in_config(self, host, instance_data):
"""
Make sure there is "growpart" in cloud_init_modules group in "/etc/cloud/cloud.cfg".
For Azure instances, make sure there is also "mounts" in the config.
BugZilla 966888
"""
config_to_check = ['- growpart']
if instance_data['cloud'] == 'azure':
config_to_check.append('- mounts')
for config in config_to_check:
assert host.file('/etc/cloud/cloud.cfg').contains(config), \
f'{config} must be present in cloud_init_modules'
@pytest.mark.run_on(['rhel'])
def test_wheel_group_not_set_to_default_user(self, host):
"""
Make sure there is no wheel in default_user's group in "/etc/cloud/cloud.cfg".
BugZilla 1549638, 1785648
Customer Case 01965459
"""
assert not host.file('/etc/cloud/cloud.cfg').contains('wheel'), \
'wheel should not be configured as default_user group'
@pytest.mark.run_on(['rhel'])
def test_cloud_configs(self, host):
"""
Verify files /etc/cloud/cloud.cfg and
/etc/cloud/cloud.cfg.d/* are not changed
JIRA: CLOUDX-812
"""
cloud_cfg = '/etc/cloud/cloud.cfg'
verify_cmd = f'rpm -Vf {cloud_cfg} | grep -e "^S.5.*{cloud_cfg}"'
with host.sudo():
assert not host.run(verify_cmd).stdout, \
f'There should not be changes in {cloud_cfg} or {cloud_cfg}.d/'
@pytest.mark.run_on(['>=rhel9.0'])
def test_cloud_cfg_netdev_rhel9(self, host):
"""
Verify _netdev is in cloud.cfg
"""
with host.sudo():
assert host.file('/etc/cloud/cloud.cfg').contains('_netdev'), \
'_netdev is expected in cloud.cfg for RHEL 9.x'
@pytest.mark.pub
@pytest.mark.order(3)
@pytest.mark.usefixtures('rhel_aws_marketplace_only')
class TestsYum:
# TODO: confirm if this test needs to be deprecated
@pytest.mark.run_on(['rhel', 'fedora'])
def test_yum_repoinfo(self, host):
yum_command = 'yum repoinfo'
with host.sudo():
assert host.run_test(yum_command), 'Error while getting repo info'
if host.system_info.distribution != 'fedora':
assert 'Repo-pkgs : 0' not in host.check_output(yum_command), \
'Unexpected number of repo pkgs (0)'
@pytest.mark.run_on(['rhel'])
def test_yum_package_install(self, host):
with host.sudo():
if 'rhui' not in host.check_output('rpm -qa'):
pytest.skip('Not applicable to non-RHUI images')
assert \
host.run('yum clean all') and \
host.run_test('yum repolist'), \
'Could not get repo list correctly'
return_code = host.run('yum check-update').rc
assert return_code == 0 or return_code == 100, \
'Could not check for yum updates'
assert \
host.run_test('yum search zsh') and \
host.run_test('yum -y install zsh') and \
host.run_test(r"rpm -q --queryformat '%{NAME}' zsh") and \
host.run_test('rpm -e zsh'), \
'yum packages installation failed'
@pytest.mark.order(1)
class TestsNetworking:
# TODO: redo test with test infra module
@pytest.mark.run_on(['all'])
def test_dns_resolving_works(self, host):
"""
Check if DNS resolving works.
"""
assert host.run_test('ping -c 5 google-public-dns-a.google.com'), \
'Public DNS resolution did not work'
@pytest.mark.run_on(['all'])
def test_ipv_localhost(self, host):
"""
Check that localhost ipv6 and ipv4 are set in /etc/hosts.
"""
expected_hosts = ['127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4',
'::1 localhost localhost.localdomain localhost6 localhost6.localdomain6']
with host.sudo():
for expected_host in expected_hosts:
assert host.file('/etc/hosts').contains(expected_host), \
'/etc/hosts does not contain ipv4 or ipv6 localhost'
@pytest.mark.run_on(['rhel', 'centos'])
def test_eth0_network_adapter_setup(self, host, instance_data):
"""
Make sure that eht0 default adapter is correctly setup:
1. NETWORKING=yes in /etc/sysconfig/network
2. For major_release<10: eth0 in /etc/sysconfig/network-scripts/ifcfg-eth0
3. For major_release>=10: exists /etc/NetworkManager/system-connections/*.nmconnection
Does not apply to >fedora35: https://fedoramagazine.org/converting-networkmanager-from-ifcfg-to-keyfiles/
"""
if instance_data['cloud'] == 'azure' and \
version.parse(host.system_info.release).major == 9:
pytest.skip('Skipping due to cloud-init known issue in RHEL-9.x. See COMPOSER-2437 for details.')
device_name = 'eth0'
device_config_path = f'/etc/sysconfig/network-scripts/ifcfg-{device_name}'
keyfile_plugin = '/etc/NetworkManager/system-connections/*.nmconnection'
with host.sudo():
assert host.file('/etc/sysconfig/network').contains('^NETWORKING=yes'), \
'Invalid networking setup'
release_major = version.parse(host.system_info.release).major
if release_major < 10:
assert host.file(device_config_path).contains(f'^DEVICE=[{device_name}|\"{device_name}\"]'), \
f'Unexpected device name. Expected: "{device_name}"'
else:
keyfile = host.check_output(f"ls {keyfile_plugin} 2>/dev/null || true")
assert keyfile != "", \
f'There is no keyfile plugin as "{keyfile_plugin}"'
@pytest.mark.run_on(['rhel'])
def test_network_manager_cloud_setup(self, host, instance_data):
"""
BugZilla 1822853
>=8.5: check NetworkManager-cloud-setup is installed and nm-cloud-setup.timer is setup for Azure and enabled
"""
cloud_setup_base_path = '/usr/lib/systemd/system/nm-cloud-setup.service.d/'
files_and_configs_by_cloud = {
'aws': {
'file_to_check': os.path.join(cloud_setup_base_path, '10-rh-enable-for-ec2.conf'),