org.osbuild.grub2.iso: Add support for optional fips menu
On RHEL 9.7+ and on RHEL 10.1+ we need to be able to include a menu that boots the installer environment with fips=1 on the cmdline. This adds an optional menu entry controlled by the "fips" boolean. This also includes a new test for the menus with and without fips included. Related: RHEL-104075
This commit is contained in:
parent
2344226178
commit
9a563f8306
3 changed files with 197 additions and 5 deletions
|
|
@ -37,6 +37,7 @@ menuentry 'Test this media & install ${product} ${version}' --class fedora --cla
|
|||
linux ${kernelpath} ${root} rd.live.check quiet
|
||||
initrd ${initrdpath}
|
||||
}
|
||||
$fipsentry
|
||||
submenu 'Troubleshooting -->' {
|
||||
menuentry 'Install ${product} ${version} in basic graphics mode' --class fedora --class gnu-linux --class gnu --class os {
|
||||
linux ${kernelpath} ${root} nomodeset quiet
|
||||
|
|
@ -49,6 +50,14 @@ submenu 'Troubleshooting -->' {
|
|||
}
|
||||
"""
|
||||
|
||||
# Optional FIPS menu entry
|
||||
FIPS_ENTRY_TEMPLATE = """
|
||||
menuentry 'Install ${product} ${version} in FIPS mode' --class fedora --class gnu-linux --class gnu --class os {
|
||||
linux ${kernelpath} ${root} quiet fips=1
|
||||
initrd ${initrdpath}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def main(root, options):
|
||||
name = options["product"]["name"]
|
||||
|
|
@ -60,6 +69,7 @@ def main(root, options):
|
|||
kopts = options["kernel"].get("opts")
|
||||
cfg = options.get("config", {})
|
||||
timeout = cfg.get("timeout", 60)
|
||||
fips = options.get("fips", False)
|
||||
|
||||
efidir = os.path.join(root, "EFI", "BOOT")
|
||||
os.makedirs(efidir)
|
||||
|
|
@ -83,18 +93,25 @@ def main(root, options):
|
|||
shutil.copy2("/usr/share/grub/unicode.pf2", fontdir)
|
||||
|
||||
print(f"kernel dir at {kdir}")
|
||||
|
||||
tplt = string.Template(GRUB2_EFI_CFG_TEMPLATE)
|
||||
data = tplt.safe_substitute({
|
||||
tplt_variables = {
|
||||
"version": version,
|
||||
"product": name,
|
||||
"kernelpath": os.path.join(kdir, "vmlinuz"),
|
||||
"initrdpath": os.path.join(kdir, "initrd.img"),
|
||||
"isolabel": isolabel,
|
||||
"root": " ".join(kopts),
|
||||
"timeout": timeout
|
||||
})
|
||||
"timeout": timeout,
|
||||
"fipsentry": ""
|
||||
}
|
||||
|
||||
# Insert optional fips menu entry
|
||||
if fips:
|
||||
fips_tmpl = string.Template(FIPS_ENTRY_TEMPLATE)
|
||||
fipsentry = fips_tmpl.safe_substitute(tplt_variables)
|
||||
tplt_variables["fipsentry"] = fipsentry
|
||||
|
||||
tplt = string.Template(GRUB2_EFI_CFG_TEMPLATE)
|
||||
data = tplt.safe_substitute(tplt_variables)
|
||||
config = os.path.join(efidir, "grub.cfg")
|
||||
with open(config, "w", encoding="utf8") as cfg:
|
||||
cfg.write(data)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@
|
|||
"vendor": {
|
||||
"type": "string"
|
||||
},
|
||||
"fips": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"config": {
|
||||
"description": "Configuration options for grub itself",
|
||||
"type": "object",
|
||||
|
|
|
|||
172
stages/test/test_grub2_iso.py
Normal file
172
stages/test/test_grub2_iso.py
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import os.path
|
||||
from unittest.mock import call, patch
|
||||
|
||||
import pytest
|
||||
|
||||
STAGE_NAME = "org.osbuild.grub2.iso"
|
||||
|
||||
CONFIG_PART_1 = """
|
||||
function load_video {
|
||||
insmod efi_gop
|
||||
insmod efi_uga
|
||||
insmod video_bochs
|
||||
insmod video_cirrus
|
||||
insmod all_video
|
||||
}
|
||||
|
||||
load_video
|
||||
set gfxpayload=keep
|
||||
insmod gzio
|
||||
insmod part_gpt
|
||||
insmod ext2
|
||||
|
||||
set timeout=60
|
||||
### END /etc/grub.d/00_header ###
|
||||
|
||||
search --no-floppy --set=root -l 'Fedora-42-Everything-x86_64'
|
||||
|
||||
### BEGIN /etc/grub.d/10_linux ###
|
||||
menuentry 'Install Fedora 42' --class fedora --class gnu-linux --class gnu --class os {
|
||||
linux /images/pxeboot/vmlinuz inst.stage2=hd:LABEL=Fedora-42-Everything-x86_64 quiet
|
||||
initrd /images/pxeboot/initrd.img
|
||||
}
|
||||
menuentry 'Test this media & install Fedora 42' --class fedora --class gnu-linux --class gnu --class os {
|
||||
linux /images/pxeboot/vmlinuz inst.stage2=hd:LABEL=Fedora-42-Everything-x86_64 rd.live.check quiet
|
||||
initrd /images/pxeboot/initrd.img
|
||||
}
|
||||
"""
|
||||
|
||||
CONFIG_PART_2 = """
|
||||
submenu 'Troubleshooting -->' {
|
||||
menuentry 'Install Fedora 42 in basic graphics mode' --class fedora --class gnu-linux --class gnu --class os {
|
||||
linux /images/pxeboot/vmlinuz inst.stage2=hd:LABEL=Fedora-42-Everything-x86_64 nomodeset quiet
|
||||
initrd /images/pxeboot/initrd.img
|
||||
}
|
||||
menuentry 'Rescue a Fedora system' --class fedora --class gnu-linux --class gnu --class os {
|
||||
linux /images/pxeboot/vmlinuz inst.stage2=hd:LABEL=Fedora-42-Everything-x86_64 inst.rescue quiet
|
||||
initrd /images/pxeboot/initrd.img
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
CONFIG_FIPS = """
|
||||
menuentry 'Install Fedora 42 in FIPS mode' --class fedora --class gnu-linux --class gnu --class os {
|
||||
linux /images/pxeboot/vmlinuz inst.stage2=hd:LABEL=Fedora-42-Everything-x86_64 quiet fips=1
|
||||
initrd /images/pxeboot/initrd.img
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@patch("shutil.copy2")
|
||||
@pytest.mark.parametrize("test_data,expected_conf", [
|
||||
# default
|
||||
({}, CONFIG_PART_1 + CONFIG_PART_2),
|
||||
# fips menu enable
|
||||
({"fips": True}, CONFIG_PART_1 + CONFIG_FIPS + CONFIG_PART_2)
|
||||
])
|
||||
def test_grub2_iso(mocked_copy2, tmp_path, stage_module, test_data, expected_conf):
|
||||
treedir = tmp_path / "tree"
|
||||
treedir.mkdir(parents=True, exist_ok=True)
|
||||
efidir = treedir / "EFI/BOOT"
|
||||
confpath = efidir / "grub.cfg"
|
||||
|
||||
# from fedora-ostree-bootiso-xz.json
|
||||
options = {
|
||||
"product": {
|
||||
"name": "Fedora",
|
||||
"version": "42"
|
||||
},
|
||||
"kernel": {
|
||||
"dir": "/images/pxeboot",
|
||||
"opts": [
|
||||
"inst.stage2=hd:LABEL=Fedora-42-Everything-x86_64"
|
||||
]
|
||||
},
|
||||
"isolabel": "Fedora-42-Everything-x86_64",
|
||||
"architectures": [
|
||||
"X64"
|
||||
],
|
||||
"vendor": "fedora"
|
||||
}
|
||||
options.update(test_data)
|
||||
|
||||
stage_module.main(treedir, options)
|
||||
|
||||
assert os.path.exists(confpath)
|
||||
assert confpath.read_text() == expected_conf
|
||||
assert mocked_copy2.call_args_list == [
|
||||
call("/boot/efi/EFI/fedora/shimx64.efi", os.fspath(efidir / "BOOTX64.EFI")),
|
||||
call("/boot/efi/EFI/fedora/mmx64.efi", os.fspath(efidir / "mmx64.efi")),
|
||||
call("/boot/efi/EFI/fedora/gcdx64.efi", os.fspath(efidir / "grubx64.efi")),
|
||||
call("/usr/share/grub/unicode.pf2", os.fspath(efidir / "fonts"))
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_data,expected_err", [
|
||||
# bad
|
||||
(
|
||||
{}, ["'isolabel' is a required property", "'kernel' is a required property", "'product' is a required property"]
|
||||
),
|
||||
(
|
||||
{
|
||||
"isolabel": "an-isolabel",
|
||||
"product": {
|
||||
"name": "a-name",
|
||||
"version": "a-version",
|
||||
},
|
||||
"kernel": {},
|
||||
}, ["'dir' is a required property"],
|
||||
),
|
||||
(
|
||||
{
|
||||
"isolabel": "an-isolabel",
|
||||
"product": {},
|
||||
"kernel": {
|
||||
"dir": "/path/to",
|
||||
},
|
||||
}, ["'name' is a required property", "'version' is a required property"],
|
||||
),
|
||||
# good
|
||||
(
|
||||
{
|
||||
"isolabel": "an-isolabel",
|
||||
"product": {
|
||||
"name": "a-name",
|
||||
"version": "a-version",
|
||||
},
|
||||
"kernel": {
|
||||
"dir": "/path/to",
|
||||
},
|
||||
}, "",
|
||||
),
|
||||
# good + fips
|
||||
(
|
||||
{
|
||||
"isolabel": "an-isolabel",
|
||||
"product": {
|
||||
"name": "a-name",
|
||||
"version": "a-version",
|
||||
},
|
||||
"kernel": {
|
||||
"dir": "/path/to",
|
||||
},
|
||||
"fips": True,
|
||||
}, "",
|
||||
),
|
||||
])
|
||||
def test_schema_validation(stage_schema, test_data, expected_err):
|
||||
test_input = {
|
||||
"type": STAGE_NAME,
|
||||
"options": {},
|
||||
}
|
||||
test_input["options"].update(test_data)
|
||||
res = stage_schema.validate(test_input)
|
||||
|
||||
if expected_err == "":
|
||||
assert res.valid is True, f"err: {[e.as_dict() for e in res.errors]}"
|
||||
else:
|
||||
assert res.valid is False
|
||||
err_msgs = sorted([e.as_dict()["message"] for e in res.errors])
|
||||
assert err_msgs == expected_err
|
||||
Loading…
Add table
Add a link
Reference in a new issue