Skip to content

Commit d8b06b6

Browse files
committed
Merge: redhat/kernel.spec: add uki_addons to create UKI kernel cmdline addons
MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/4603 ``` JIRA: https://issues.redhat.com/browse/RHEL-45159 JIRA: https://issues.redhat.com/browse/RHEL-45160 We want to enable kernel.spec to optionally ship UKI addons defined in a common config file in redhat folder. The folder redhat/uki_addons will contain all addons configs specifying the UKI kernel cmdline addons to be created in the next build. An addon config is simply a .addon plain text file, where any line is taken as kernel cmdline, except for the ones starting with '#', which will be automatically ignored. redhat/scripts/uki_create_json.py will take care of parsing all configs and folders in redhat/uki_addons and produce a json file 'uki_addons.json' that is then added into the SRPM. When building the RPM, kernel.spec will then call uki_create_addons.py to parse the json and call 'ukify' for each addon matching the given distro, arch and uki provided in input. The output addon filename will be a concatenation of all folders in redhat/uki_addons that are part of the addon config path. The folder hierarchy inside of redhat/uki_addons is similar to redhat/configs: distro/distro/distro/UKI_NAME/%arch. It is also possible to add .sbat to all the generated addons, by populating redhat/uki_addons/distro/distro/distro/UKI_NAME/%arch/sbat/sbat.conf. Syntax is same as the addons config. As an example of this feature, add the fips addon to optionally enable fips (https://issues.redhat.com/browse/RHEL-45160). Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com> ``` Approved-by: Tony Camuso <tcamuso@redhat.com> Approved-by: Vitaly Kuznetsov <vkuznets@redhat.com> Approved-by: Jarod Wilson <jarod@redhat.com> Approved-by: CKI KWF Bot <cki-ci-bot+kwf-gitlab-com@redhat.com> Merged-by: Scott Weaver <scweaver@redhat.com>
2 parents e423e0f + fa54780 commit d8b06b6

File tree

7 files changed

+290
-0
lines changed

7 files changed

+290
-0
lines changed

redhat/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ kabi/Module.kabi_*
66
kabi/kabi-current
77
kabi/kabi-rhel*
88
kabi/kabi-rhel*/*
9+
scripts/uki_addons/uki_addons.json

redhat/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ sources-rh: $(TARBALL) generate-testpatch-tmp setup-source dist-configs-check
625625
@sed -e "s/%%SPECKVERSION%%/$(SPECKVERSION)/" \
626626
-e "s/%%SPECKPATCHLEVEL%%/$(SPECKPATCHLEVEL)/" \
627627
rpminspect.yaml > $(SOURCES)/rpminspect.yaml
628+
@$(REDHAT)/scripts/uki_addons/uki_create_json.py $(REDHAT)/scripts/uki_addons/uki_addons.json
628629
@cp cpupower.* \
629630
keys/rhel*.x509 \
630631
keys/nvidia*.x509 \
@@ -639,6 +640,8 @@ sources-rh: $(TARBALL) generate-testpatch-tmp setup-source dist-configs-check
639640
mod-partner.list \
640641
mod-kvm.list \
641642
mod-sign.sh \
643+
scripts/uki_addons/uki_create_addons.py \
644+
scripts/uki_addons/uki_addons.json \
642645
configs/flavors \
643646
configs/generate_all_configs.sh \
644647
configs/merge.pl \

redhat/kernel.spec.template

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,8 @@ BuildRequires: lvm2
785785
BuildRequires: systemd-boot-unsigned
786786
# For systemd-pcrphase
787787
BuildRequires: systemd-udev >= 252-1
788+
# For UKI kernel cmdline addons
789+
BuildRequires: systemd-ukify
788790
# For TPM operations in UKI initramfs
789791
BuildRequires: tpm2-tools
790792
# For Azure CVM specific udev rules
@@ -929,6 +931,9 @@ Source105: nvidiagpuoot001.x509
929931

930932
Source150: dracut-virt.conf
931933

934+
Source151: uki_create_addons.py
935+
Source152: uki_addons.json
936+
932937
Source200: check-kabi
933938

934939
Source201: Module.kabi_aarch64
@@ -1504,6 +1509,11 @@ Provides: kernel-%{?1:%{1}-}uname-r = %{KVERREL}%{uname_suffix %{?1:%{1}}}\
15041509
Requires: kernel%{?1:-%{1}}-modules-core-uname-r = %{KVERREL}%{uname_suffix %{?1:%{1}}}\
15051510
Requires(pre): %{kernel_prereq}\
15061511
Requires(pre): systemd >= 252-20\
1512+
%package %{?1:%{1}-}uki-virt-addons\
1513+
Summary: %{variant_summary} unified kernel image addons for virtual machines\
1514+
Provides: installonlypkg(kernel)\
1515+
Requires: kernel%{?1:-%{1}}-uki-virt = %{version}-%{release}\
1516+
Requires(pre): systemd >= 252-20\
15071517
%endif\
15081518
%endif\
15091519
%if "%{1}" == "rt" || "%{1}" == "rt-debug"\
@@ -1624,8 +1634,14 @@ input and output, etc.
16241634
%description debug-uki-virt
16251635
Prebuilt debug unified kernel image for virtual machines.
16261636

1637+
%description debug-uki-virt-addons
1638+
Prebuilt debug unified kernel image addons for virtual machines.
1639+
16271640
%description uki-virt
16281641
Prebuilt default unified kernel image for virtual machines.
1642+
1643+
%description uki-virt-addons
1644+
Prebuilt default unified kernel image addons for virtual machines.
16291645
%endif
16301646

16311647
%if %{with_ipaclones}
@@ -2466,6 +2482,10 @@ BuildKernel() {
24662482
--kernel-cmdline 'console=tty0 console=ttyS0' \
24672483
$KernelUnifiedImage
24682484

2485+
KernelAddonsDirOut="$KernelUnifiedImage.extra.d"
2486+
mkdir -p $KernelAddonsDirOut
2487+
python3 %{SOURCE151} %{SOURCE152} $KernelAddonsDirOut virt %{primary_target} %{_target_cpu}
2488+
24692489
%if %{signkernel}
24702490

24712491
%if 0%{?centos}
@@ -2482,6 +2502,12 @@ BuildKernel() {
24822502
fi
24832503
mv $KernelUnifiedImage.signed $KernelUnifiedImage
24842504

2505+
for addon in "$KernelAddonsDirOut"/*; do
2506+
%pesign -s -i $addon -o $addon.signed -a %{secureboot_ca_0} -c %{secureboot_key_0} -n %{pesign_name_0}
2507+
rm -f $addon
2508+
mv $addon.signed $addon
2509+
done
2510+
24852511
# signkernel
24862512
%endif
24872513

@@ -3692,6 +3718,9 @@ fi
36923718
/lib/modules/%{KVERREL}%{?3:+%{3}}/modules.builtin*\
36933719
/lib/modules/%{KVERREL}%{?3:+%{3}}/%{?-k:%{-k*}}%{!?-k:vmlinuz}-virt.efi\
36943720
%ghost /%{image_install_path}/efi/EFI/Linux/%{?-k:%{-k*}}%{!?-k:*}-%{KVERREL}%{?3:+%{3}}.efi\
3721+
%{expand:%%files %{?3:%{3}-}uki-virt-addons}\
3722+
/lib/modules/%{KVERREL}%{?3:+%{3}}/%{?-k:%{-k*}}%{!?-k:vmlinuz}-virt.efi.extra.d/ \
3723+
/lib/modules/%{KVERREL}%{?3:+%{3}}/%{?-k:%{-k*}}%{!?-k:vmlinuz}-virt.efi.extra.d/*.addon.efi\
36953724
%endif\
36963725
%endif\
36973726
%if %{?3:1} %{!?3:0}\
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/usr/bin/env python3
2+
#
3+
# This script inspects a given json proving a list of addons, and
4+
# creates an addon for each key/value pair matching the given uki, distro and
5+
# arch provided in input.
6+
#
7+
# Usage: python uki_create_addons.py input_json out_dir uki distro arch
8+
#
9+
# This tool requires the systemd-ukify and systemd-boot packages.
10+
#
11+
# Addon file
12+
#-----------
13+
# Each addon terminates with .addon
14+
# Each addon contains only two types of lines:
15+
# Lines beginning with '#' are description and thus ignored
16+
# All other lines are command line to be added.
17+
# The name of the end resulting addon is taken from the json hierarchy.
18+
# For example, and addon in json['virt']['rhel']['x86_64']['hello.addon'] will
19+
# result in an UKI addon file generated in out_dir called
20+
# hello-virt.rhel.x86_64.addon.efi
21+
#
22+
# The common key, present in any sub-dict in the provided json (except the leaf dict)
23+
# is used as place for default addons when the same addon is not defined deep
24+
# in the hierarchy. For example, if we define test.addon (text: 'test1\n') in
25+
# json['common']['test.addon'] = ['test1\n'] and another test.addon (text: test2) in
26+
# json['virt']['common']['test.addon'] = ['test2'], any other uki except virt
27+
# will have a test.addon.efi with text "test1", and virt will have a
28+
# test.addon.efi with "test2"
29+
#
30+
# sbat.conf
31+
#----------
32+
# This dict is containing the sbat string for *all* addons being created.
33+
# This dict is optional, but when used has to be put in a sub-dict with
34+
# { 'sbat' : { 'sbat.conf' : ['your text here'] }}
35+
# It follows the same syntax as the addon files, meaning '#' is comment and
36+
# the rest is taken as sbat string and feed to ukify.
37+
38+
import os
39+
import sys
40+
import json
41+
import collections
42+
import subprocess
43+
44+
45+
UKIFY_PATH = '/usr/lib/systemd/ukify'
46+
47+
def usage(err):
48+
print(f'Usage: {os.path.basename(__file__)} input_json output_dir uki distro arch')
49+
print(f'Error:{err}')
50+
sys.exit(1)
51+
52+
def check_clean_arguments(input_json, out_dir):
53+
# Remove end '/'
54+
if out_dir[-1:] == '/':
55+
out_dir = out_dir[:-1]
56+
if not os.path.isfile(input_json):
57+
usage(f'input_json {input_json} is not a file, or does not exist!')
58+
if not os.path.isdir(out_dir):
59+
usage(f'out_dir_dir {out_dir} is not a dir, or does not exist!')
60+
return out_dir
61+
62+
UKICmdlineAddon = collections.namedtuple('UKICmdlineAddon', ['name', 'cmdline'])
63+
uki_addons_list = []
64+
uki_addons = {}
65+
addon_sbat_string = None
66+
67+
def parse_lines(lines, rstrip=True):
68+
cmdline = ''
69+
for l in lines:
70+
l = l.lstrip()
71+
if not l:
72+
continue
73+
if l[0] == '#':
74+
continue
75+
# rstrip is used only for addons cmdline, not sbat.conf, as it replaces
76+
# return lines with spaces.
77+
if rstrip:
78+
l = l.rstrip() + ' '
79+
cmdline += l
80+
if cmdline == '':
81+
return ''
82+
return cmdline
83+
84+
def parse_all_addons(in_obj):
85+
global addon_sbat_string
86+
87+
for el in in_obj.keys():
88+
# addon found: copy it in our global dict uki_addons
89+
if el.endswith('.addon'):
90+
uki_addons[el] = in_obj[el]
91+
92+
if 'sbat' in in_obj and 'sbat.conf' in in_obj['sbat']:
93+
# sbat.conf found: override sbat with the most specific one found
94+
addon_sbat_string = parse_lines(in_obj['sbat']['sbat.conf'], rstrip=False)
95+
96+
def recursively_find_addons(in_obj, folder_list):
97+
# end of recursion, leaf directory. Search all addons here
98+
if len(folder_list) == 0:
99+
parse_all_addons(in_obj)
100+
return
101+
102+
# first, check for common folder
103+
if 'common' in in_obj:
104+
parse_all_addons(in_obj['common'])
105+
106+
# second, check if there is a match with the searched folder
107+
if folder_list[0] in in_obj:
108+
folder_next = in_obj[folder_list[0]]
109+
folder_list = folder_list[1:]
110+
recursively_find_addons(folder_next, folder_list)
111+
112+
def parse_in_json(in_json, uki_name, distro, arch):
113+
with open(in_json, 'r') as f:
114+
in_obj = json.load(f)
115+
recursively_find_addons(in_obj, [uki_name, distro, arch])
116+
117+
for addon_name, cmdline in uki_addons.items():
118+
addon_name = addon_name.replace(".addon","")
119+
addon_full_name = f'{addon_name}-{uki_name}.{distro}.{arch}.addon.efi'
120+
cmdline = parse_lines(cmdline).rstrip()
121+
if cmdline:
122+
uki_addons_list.append(UKICmdlineAddon(addon_full_name, cmdline))
123+
124+
def create_addons(out_dir):
125+
for uki_addon in uki_addons_list:
126+
out_path = os.path.join(out_dir, uki_addon.name)
127+
cmd = [
128+
f'{UKIFY_PATH}', 'build',
129+
f'--cmdline="{uki_addon.cmdline}"',
130+
f'--output={out_path}']
131+
if addon_sbat_string:
132+
cmd.append('--sbat="' + addon_sbat_string.rstrip() +'"')
133+
134+
subprocess.check_call(cmd, text=True)
135+
136+
if __name__ == "__main__":
137+
argc = len(sys.argv) - 1
138+
if argc != 5:
139+
usage('too few or too many parameters!')
140+
141+
input_json = sys.argv[1]
142+
out_dir = sys.argv[2]
143+
uki_name = sys.argv[3]
144+
distro = sys.argv[4]
145+
arch = sys.argv[5]
146+
147+
out_dir = check_clean_arguments(input_json, out_dir)
148+
parse_in_json(input_json, uki_name, distro, arch)
149+
create_addons(out_dir)
150+
151+
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env python3
2+
#
3+
# This script recursively inspects the 'redhat/uki_addons' directory,
4+
# and creates a json file containing name and description of each addon found.
5+
#
6+
# Usage: python uki_create_json.py output_file
7+
#
8+
# Addon file
9+
#-----------
10+
# The addon files are contained into the 'redhat/uki_addons' folder.
11+
# Each addon terminates with .addon.
12+
# Each addon contains only two types of lines:
13+
# Lines beginning with '#' are description and thus ignored
14+
# All other lines are command line to be added.
15+
# This script just parses the folder structure and creates a json reflecting
16+
# all addons and their line found.
17+
# For example, if we define test.addon (text: 'test1\n') in
18+
# redhat/uki_addons/virt/rhel/x86_64, the resulting output_file will contain
19+
# { 'virt' : { 'rhel' : { 'x86_64' : { 'test.addon' : ['test1\n'] }}}}
20+
#
21+
# The name of the end resulting addon is taken from the folder hierarchy, but this
22+
# is handled by uki_create_addons.py when building the rpm. This script only
23+
# prepares the json file to be added in the srpm. For more information about
24+
# the folder hierarchy, what the 'common' and 'sbat' folder are, look at
25+
# uki_create_addons.py.
26+
#
27+
# The common folder, present in any folder under redhat/uki_addons
28+
# (except the leaf folders) is used as place for default addons when the same
29+
# addon is not defined deep in the hierarchy.
30+
#
31+
# How to extend the script and kernel.spec with a new arch or uki or distro
32+
#--------------------------------------------------------------------------
33+
# A new distro has to be added by creating the folder in redhat/uki_addons.
34+
# See uki_create_addons.py to how the directory hierarchy in redhat/uki_addons
35+
# is expected to be.
36+
# After that, if the distro is a different arch from the one already supported,
37+
# one needs to extend the %define with_efiuki in kernel.spec.template.
38+
# If a new UKI has to be created with a different name from the existing ones,
39+
# the logic to create the addons and call this script has to be implemented too
40+
# in kernel.spec.template. As an example, see how the 'virt' UKI addons are
41+
# created.
42+
43+
import os
44+
import sys
45+
import json
46+
import subprocess
47+
48+
def usage(err):
49+
print(f'Usage: {os.path.basename(__file__)} dest_file')
50+
print(f'Error:{err}')
51+
sys.exit(1)
52+
53+
def find_addons():
54+
cmd = ['/usr/bin/find', 'uki_addons', "(", '-name', '*.addon', '-o', '-name', 'sbat.conf', ")"]
55+
proc_out = subprocess.run(cmd, check=True, capture_output=True, text=True)
56+
if proc_out.returncode == 0:
57+
return proc_out.stdout
58+
return None
59+
60+
def add_keys_to_obj(ret_data, keys, value):
61+
if len(keys) == 0:
62+
return
63+
key = keys[0]
64+
val = {}
65+
if len(keys) == 1:
66+
val = value
67+
if not key in ret_data:
68+
ret_data[key] = val
69+
ret_data = ret_data[key]
70+
add_keys_to_obj(ret_data, keys[1:], value)
71+
72+
def create_json(addons):
73+
obj = {}
74+
for el in addons:
75+
print(f'Processing {el} ...')
76+
with open(el, 'r') as f:
77+
lines = f.readlines()
78+
dirs, name = os.path.split(el)
79+
keys = dirs.split('/')
80+
if keys[0] == 'uki_addons':
81+
keys = keys[1:]
82+
keys.append(name)
83+
add_keys_to_obj(obj, keys, lines)
84+
print(f'Processing {el} completed')
85+
return obj
86+
87+
def write_json(obj, dest_file):
88+
with open(dest_file, 'w') as f:
89+
json.dump(obj , f, indent=4)
90+
print(f'Processed addons files are in {dest_file}')
91+
92+
if __name__ == "__main__":
93+
argc = len(sys.argv) - 1
94+
if argc != 1:
95+
usage('too few or too many parameters!')
96+
dest = sys.argv[1]
97+
98+
output = find_addons()
99+
if output is None:
100+
usage('error finding the addons')
101+
addons_list = output.split()
102+
obj = create_json(addons_list)
103+
write_json(obj, dest)
104+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fips=0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fips=1

0 commit comments

Comments
 (0)