Pull request #61: add devtool generate-uki command

Merge in ICO/coreos from feat/devtool-kernel to master

* commit '1c8f7e9163ba5fec161bb858643a57274ea07882':
  feat(devtool): add a generate-uki command
  fix(coreos-image-uki.bbclass): use APPENDS to set the kernel arguments
  refactor: use black to format python code in vscode
This commit is contained in:
Samuel Dolt 2023-03-08 16:29:30 +01:00
commit e2a53121a5
3 changed files with 328 additions and 11 deletions

View File

@ -4,5 +4,6 @@
"**/build/downloads/**": true,
"**/build/sstate-cache/**": true,
"**/build/tmp/**": true
}
},
"python.formatting.provider": "black"
}

View File

@ -13,7 +13,9 @@ COREOS_KERNEL_EXT ??= ".efi"
COREOS_KERNEL0_NAME ??= "kernel0-${MACHINE}"
COREOS_KERNEL1_NAME ??= "kernel1-${MACHINE}"
COREOS_KERNEL0_FILENAME ??= "${COREOS_KERNEL0_NAME}${COREOS_KERNEL_EXT}"
COREOS_KERNEL0 ??= "${DEPLOY_DIR_IMAGE}/${COREOS_KERNEL0_FILENAME}"
COREOS_KERNEL1_FILENAME ??= "${COREOS_KERNEL1_NAME}${COREOS_KERNEL_EXT}"
COREOS_KERNEL1 ??= "${DEPLOY_DIR_IMAGE}/${COREOS_KERNEL1_FILENAME}"
# Kernel command line
# ==============================================================================
@ -24,12 +26,17 @@ COREOS_PLATFORM1_ROOT ??= "PARTLABEL=platform1"
COREOS_KERNEL0_CMDLINE ??= "root=${COREOS_PLATFORM0_ROOT} ${APPEND}"
COREOS_KERNEL1_CMDLINE ??= "root=${COREOS_PLATFORM0_ROOT} ${APPEND}"
COREOS_UKI_PART_KERNEL_FILENAME ??= "${KERNEL_IMAGETYPE}-${MACHINE}${KERNEL_IMAGE_BIN_EXT}"
COREOS_UKI_PART_KERNEL ??= "${DEPLOY_DIR_IMAGE}/${COREOS_UKI_PART_KERNEL_FILENAME}"
COREOS_UKI_PART_STUB_FILENAME ??= "kernel-stub${EFI_ARCH}.efi"
COREOS_UKI_PART_STUB ??= "${STAGING_LIBDIR}/efibootguard/${COREOS_UKI_PART_STUB_FILENAME}"
# UKI Generation
# ==============================================================================
do_bundle_uki() {
deployDir="${DEPLOY_DIR_IMAGE}"
kernel=${KERNEL_IMAGETYPE}-${MACHINE}${KERNEL_IMAGE_BIN_EXT}
# Create an array with device tree if any
DTB_PARAMS=""
@ -40,21 +47,23 @@ do_bundle_uki() {
DTB_PARAMS="${DTB_PARAMS} --dtb=${deployDir}/${dtb}"
done
echo "kernel: ${kernel}"
echo "kernel: ${COREOS_UKI_PART_KERNEL_FILENAME}"
echo "dtb: ${DTB_PARAMS}"
echo "cmdline0: ${COREOS_KERNEL0_CMDLINE}"
echo "cmdline1: ${COREOS_KERNEL1_CMDLINE}"
bg_gen_unified_kernel \
"${STAGING_LIBDIR}/efibootguard/kernel-stub${EFI_ARCH}.efi" \
"${deployDir}/${kernel}" \
"${deployDir}/${COREOS_KERNEL0_FILENAME}" \
--cmdline "console=ttyS0,115200 root=${COREOS_PLATFORM0_ROOT} rootwait " \
"${COREOS_UKI_PART_STUB}" \
"${COREOS_UKI_PART_KERNEL}" \
"${COREOS_KERNEL0}" \
--cmdline "${COREOS_KERNEL0_CMDLINE}" \
${DTB_PARAMS}
bg_gen_unified_kernel \
"${STAGING_LIBDIR}/efibootguard/kernel-stub${EFI_ARCH}.efi" \
"${deployDir}/${kernel}" \
"${deployDir}/${COREOS_KERNEL1_FILENAME}" \
--cmdline "console=ttyS0,115200 root=${COREOS_PLATFORM1_ROOT} rootwait " \
"${COREOS_UKI_PART_STUB}" \
"${COREOS_UKI_PART_KERNEL}" \
"${COREOS_KERNEL1}" \
--cmdline "${COREOS_KERNEL1_CMDLINE}" \
${DTB_PARAMS}
coreos_efi_secureboot_sign_app "${deployDir}/${COREOS_KERNEL0_FILENAME}"

View File

@ -0,0 +1,307 @@
#!/usr/bin/env python
# SPDX-License-Identifier: GPL-2.0-only
# This scripts use bitbake api, so it has to be under the GPL-2.0 license
"""
devtool wrapper for the coreos-device script
"""
import subprocess
import textwrap
import os
import sys
import subprocess
from devtool import setup_tinfoil, parse_recipe
from dataclasses import dataclass
from pathlib import Path
from shlex import quote
def register_commands(subparsers, context):
"""Register devtool subcommands from this plugin"""
subparsers.add_subparser_group("efibootguard", "Developers tools for efibootguard")
cmd_uki = subparsers.add_parser(
"generate-uki",
help="Generate signed UKI kernel images",
description="""
Helper to generate Unified Kernel Image using bg_generate_uki. The UKI is
signed and contain a EFI bootstraper, a kernel image, the kernel command
line and one or multiple device tree
Before calling this function, theses recipes should be build by bitbake:
`virtual/kernel efibootguard efibootguard-native`
""",
group="efibootguard",
)
cmd_uki.add_argument("imagename", help="Image recipe used to get the configuration")
cmd_uki.add_argument(
"--cmdline-append",
default="",
help="string to append to the kernel command line",
)
cmd_uki.add_argument(
"--cmdline",
default=None,
help="""
Replace the common part of the cmdline. root= parameter will be added
automatically if --cmdline-no-root is not used
""",
)
cmd_uki.add_argument(
"--cmdline-no-root",
default=False,
action="store_true",
help="Don't automatically append root= if --cmdline is used",
)
cmd_uki.add_argument(
"--dtb",
default=None,
help="Embedded the given dtb instead of the default set",
)
cmd_uki.add_argument(
"--kernel",
default=None,
help="Use a custom kernel binary",
)
cmd_uki.set_defaults(
func=efibootguard_generate_uki, fixed_setup=context.fixed_setup
)
def run(cmd):
return subprocess.run(cmd).returncode
class InvalidImage(Exception):
pass
@dataclass
class UKIGeneratorArgs:
"""
Class used to generate and run commands needed to build and sign
the UKI image
"""
kernel: str
output: str
cmdline: str
dtb: str
stub: list
root: str
build_binary: str
keydir: str
def process_args(self, args):
"""
This apply args passed to the program to override or modify the
parameters that we received from the bitbake context
"""
if args.dtb:
self.dtb = [args.dtb]
if args.kernel:
self.kernel = args.kernel
if args.cmdline:
self.cmdline = args.cmdline
if not args.cmdline_no_root:
self.cmdline = f"{self.cmdline} root={self.root}"
if args.cmdline_append:
self.cmdline = f"{self.cmdline} {args.cmdline_append}"
def __str__(self):
return (
f"stub: {self.stub}\n"
f"kernel: {self.kernel}\n"
f"cmdline: {self.cmdline}\n"
f"dtb: {self.dtb}\n"
f"output: {self.output}\n"
f"keydir: {self.keydir}\n"
)
def get_buid_cmd(self) -> list[str]:
"""
Return the command needed to build the UKI as an array
"""
args = [
self.build_binary,
self.stub,
self.kernel,
self.output,
"--cmdline",
self.cmdline,
]
for dtb in self.dtb:
args += ["--dtb", dtb]
return args
def get_sign_cmd(self) -> list[str]:
"""
Return the command needed to sign the UKI in place as an array
"""
return [
"sbsign",
"--key",
os.path.join(self.keydir, "db.key"),
"--cert",
os.path.join(self.keydir, "db.crt"),
self.output,
"--output",
self.output,
]
def print_cmd(self, cmd):
"""
Pretty print the command in the terminal with indentation and line
splitting using standard \ keyword
"""
first = True
previous_was_an_option = False
for index, arg in enumerate(cmd):
# Quote the string as needed, so that copy past from the printed
# command works.
arg = quote(arg)
end = " \\\n"
if index == len(cmd) - 1:
# For the last
end = " \n"
if first:
# only print the command name not the full path
print(os.path.basename(arg), end=end)
first = False
continue
if previous_was_an_option:
print(arg, end=end)
previous_was_an_option = False
continue
if arg.startswith("-"):
previous_was_an_option = True
printi(arg, 1, end=" ")
else:
printi(arg, 1, end=end)
def build_and_sign(self) -> int:
"""
Run the sign and build command and return the sum of both commands
exit status
"""
build_cmd = self.get_buid_cmd()
sign_cmd = self.get_sign_cmd()
self.print_cmd(build_cmd)
r = run(build_cmd)
self.print_cmd(sign_cmd)
r += run(sign_cmd)
return r
def printi(txt, indent=1, *, iprefix=" ", end="\n"):
"""
Helper function to print multiples lines with some indentation
"""
print(textwrap.indent(txt, iprefix * indent), end=end)
def dtb_to_list(dtb, deploy_dir) -> list[str]:
"""
Convert the string array from bitbake into a python list. Only the basename
of the dtb is taken and DEPLOY_DIR is appended
"""
return list(
map(
lambda s: os.path.join(deploy_dir, os.path.basename(s)),
filter(lambda s: s.strip() != "", dtb.split(" ")),
)
)
def efibootguard_generate_uki(args, config, basepath, workspace):
"""
Entrypoint for devtool generate-uki
"""
image = args.imagename
tinfoil = setup_tinfoil(basepath=basepath)
rd = parse_recipe(config, tinfoil, image, True)
if not rd:
# Error already shown
return 1
if not bb.data.inherits_class("coreos-image", rd):
raise InvalidImage("Image should be based on the coreos-image class")
if rd.getVar("COREOS_IMAGE_GENERATE_UKI") != "1":
raise InvalidImage("COREOS_IMAGE_GENERATE_UKI should be set to '1'")
deploy_dir = os.path.relpath(rd.getVar("DEPLOY_DIR_IMAGE"))
kernel = os.path.relpath(rd.getVar("COREOS_UKI_PART_KERNEL"))
dtb = dtb_to_list(rd.getVar("KERNEL_DEVICETREE"), deploy_dir)
rd_efibootguard = parse_recipe(config, tinfoil, "efibootguard", True)
stub = os.path.relpath(
os.path.join(
rd_efibootguard.getVar("WORKDIR"),
"package/usr/lib/efibootguard",
rd.getVar("COREOS_UKI_PART_STUB_FILENAME"),
)
)
build_binary = os.path.join(
rd.getVar("COMPONENTS_DIR"),
rd.getVar("BUILD_ARCH"),
"efibootguard-native",
rd.getVar("bindir_native").lstrip(os.path.sep),
"bg_gen_unified_kernel",
)
keydir = os.path.relpath(rd.getVar("COREOS_EFI_SECUREBOOT_KEYDIR"))
uki0 = UKIGeneratorArgs(
kernel=kernel,
output=os.path.relpath(rd.getVar("COREOS_KERNEL0")),
cmdline=rd.getVar("COREOS_KERNEL0_CMDLINE"),
dtb=dtb,
stub=stub,
root=rd.getVar("COREOS_PLATFORM0_ROOT"),
build_binary=build_binary,
keydir=keydir,
)
uki1 = UKIGeneratorArgs(
kernel=kernel,
output=os.path.relpath(rd.getVar("COREOS_KERNEL1")),
cmdline=rd.getVar("COREOS_KERNEL1_CMDLINE"),
dtb=dtb,
stub=stub,
root=rd.getVar("COREOS_PLATFORM1_ROOT"),
build_binary=build_binary,
keydir=keydir,
)
print(f"Applying passed parameters...")
uki0.process_args(args)
uki1.process_args(args)
print(f"KERNEL0 image will be generated with the following settings:")
printi(f"{uki0}", 1)
print()
print(f"KERNEL1 image will be generated with the following settings:")
printi(f"{uki1}", 1)
print()
print(f"Generating the files...")
r = uki0.build_and_sign()
r += uki1.build_and_sign()
return r