diff --git a/xCAT-genesis-builder/oe/export-extension b/xCAT-genesis-builder/oe/export-extension new file mode 100755 index 000000000..dc365a54f --- /dev/null +++ b/xCAT-genesis-builder/oe/export-extension @@ -0,0 +1,105 @@ +#!/bin/bash +set -euo pipefail + +fail() { + printf 'Genesis extension export: %s\n' "$*" >&2 + exit 1 +} + +if (($# != 6)); then + printf '%s\n' \ + 'Usage: export-extension ARCHITECTURE EXTENSION DEPLOY_DIRECTORY PRIVATE_KEY PUBLIC_KEY OUTPUT_DIRECTORY' >&2 + exit 2 +fi + +architecture=$1 +extension=$2 +deploy_dir=${3%/} +private_key=$4 +public_key=$5 +output_dir=${6%/} +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +case "$architecture" in + x86|x86_64|ppc64|ppc64le|armv7hf|aarch64|riscv64) ;; + *) fail "unsupported architecture: $architecture" ;; +esac +[[ $extension =~ ^[a-z0-9][a-z0-9._-]{0,63}$ ]] \ + || fail "invalid extension name: $extension" +[[ -d $deploy_dir && ! -L $deploy_dir ]] \ + || fail "invalid deploy directory: $deploy_dir" +[[ -f $private_key && ! -L $private_key ]] \ + || fail "invalid private key: $private_key" +[[ -f $public_key && ! -L $public_key ]] \ + || fail "invalid public key: $public_key" +[[ ! -e $output_dir && ! -L $output_dir ]] \ + || fail "output already exists: $output_dir" + +machine=xcat-genesis-${architecture//_/-} +machine_dir=$deploy_dir/images/$machine +stem=$extension-$machine + +resolve_artifact() { + local path=$1 resolved + + resolved=$(readlink -f -- "$path") + [[ -f $resolved && ! -L $resolved ]] || fail "missing artifact: $path" + printf '%s\n' "$resolved" +} + +image=$(resolve_artifact "$machine_dir/$stem.squashfs-zst") +manifest=$(resolve_artifact "$machine_dir/$stem.manifest.json") +key_id=$(python3 - "$manifest" <<'PY' +import json +import re +import sys + +with open(sys.argv[1], encoding="utf-8") as source: + value = json.load(source).get("key_id") +if not isinstance(value, str) or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}", value): + raise SystemExit(1) +print(value) +PY +) || fail 'extension manifest has an invalid key ID' + +parent=$(dirname -- "$output_dir") +name=$(basename -- "$output_dir") +install -d -m 0755 "$parent" +staging=$(mktemp -d "$parent/.${name}.XXXXXX") +private_public=$(mktemp "$parent/.${name}.private-public.XXXXXX") +provided_public=$(mktemp "$parent/.${name}.provided-public.XXXXXX") +trap 'rm -rf -- "$staging"; rm -f -- "$private_public" "$provided_public"' EXIT + +openssl pkey -in "$private_key" -pubout -outform DER \ + -out "$private_public" 2>/dev/null \ + || fail 'private key is not a valid signing key' +openssl pkey -pubin -in "$public_key" -outform DER \ + -out "$provided_public" 2>/dev/null \ + || fail 'public key is not valid' +cmp -s -- "$private_public" "$provided_public" \ + || fail 'public key does not match the private key' + +install -d -m 0755 "$staging/extensions" "$staging/extension-keys" +install -m 0644 "$image" "$staging/extensions/$stem.squashfs-zst" +install -m 0644 "$manifest" "$staging/extensions/$stem.manifest.json" +"$script_dir/scripts/sign-extension" \ + "$staging/extensions/$stem.manifest.json" "$private_key" \ + "$staging/extensions/$stem.sig" >/dev/null +install -m 0644 "$public_key" "$staging/extension-keys/$key_id.pem" + +openssl pkeyutl -verify -pubin -inkey "$public_key" -rawin \ + -in "$staging/extensions/$stem.manifest.json" \ + -sigfile "$staging/extensions/$stem.sig" >/dev/null 2>&1 \ + || fail 'generated extension signature did not verify' +( + cd "$staging" + find extension-keys extensions -type f -print \ + | LC_ALL=C sort \ + | xargs sha256sum -- >SHA256SUMS + sha256sum -c SHA256SUMS >/dev/null +) + +mv -- "$staging" "$output_dir" +rm -f -- "$private_public" "$provided_public" +trap - EXIT +printf 'Exported Genesis extension %s to %s\n' "$extension" "$output_dir" diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/files/genesis-sysext b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/files/genesis-sysext index 1431e239e..0e89c817e 100644 --- a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/files/genesis-sysext +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/files/genesis-sysext @@ -167,7 +167,7 @@ load_all() { ((${#manifests[@]} > 0)) || fail "no extension manifests found in: $directory" for manifest in "${manifests[@]}"; do stem=${manifest%.manifest.json} - install_extension "$manifest" "$stem.raw" "$stem.sig" + install_extension "$manifest" "$stem.squashfs-zst" "$stem.sig" done publish_status READY 'Genesis extensions loaded' } diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/xcat-genesis-extensions_1.0.bb b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/xcat-genesis-extensions_1.0.bb index ae4340f76..99980c6e3 100644 --- a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/xcat-genesis-extensions_1.0.bb +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/xcat-genesis-extensions_1.0.bb @@ -13,6 +13,7 @@ RDEPENDS:${PN} = "bash coreutils jq openssl-bin systemd xcat-genesis-init" SYSTEMD_SERVICE:${PN} = "xcat-genesis-extensions.service" SYSTEMD_AUTO_ENABLE = "enable" +XCAT_GENESIS_EXTENSION_BUNDLE ??= "" do_install() { install -d ${D}${libexecdir}/xcat @@ -23,10 +24,27 @@ do_install() { install -m 0644 ${UNPACKDIR}/xcat-genesis-extensions.service \ ${D}${systemd_system_unitdir}/xcat-genesis-extensions.service - install -d ${D}${datadir}/xcat/genesis/extension-keys + install -d ${D}${datadir}/xcat/genesis/extension-keys \ + ${D}${localstatedir}/lib/xcat/genesis/extensions + + if [ -n "${XCAT_GENESIS_EXTENSION_BUNDLE}" ]; then + case "${XCAT_GENESIS_EXTENSION_BUNDLE}" in + *[!A-Za-z0-9._-]*|.|..) bbfatal "Invalid Genesis extension bundle name" ;; + esac + bundle=${UNPACKDIR}/${XCAT_GENESIS_EXTENSION_BUNDLE} + test -d "$bundle/extension-keys" \ + || bbfatal "Genesis extension bundle has no trust store" + test -d "$bundle/extensions" \ + || bbfatal "Genesis extension bundle has no extensions" + cp -R --no-preserve=ownership "$bundle/extension-keys/." \ + ${D}${datadir}/xcat/genesis/extension-keys/ + cp -R --no-preserve=ownership "$bundle/extensions/." \ + ${D}${localstatedir}/lib/xcat/genesis/extensions/ + fi } FILES:${PN} = "${libexecdir}/xcat/genesis-sysext \ ${systemd_system_unitdir}/xcat-genesis-extensions.service \ ${datadir}/xcat/genesis/extension-keys \ + ${localstatedir}/lib/xcat/genesis/extensions \ "