From 8ea52d9e50a5c33a90fdef8040980be08dd53151 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovskii Date: Tue, 19 Jul 2022 09:40:10 +0200 Subject: [PATCH 1/3] s390x: generate GPG keys for Ignition config protection During `cosa buildextend-secex` a pair of GPG keys is randomly generated, where private key becomes part of `sdboot` image, and public key becomes part of build artifacts. User than can encrypt his Ignition config: ``` gpg --recipient-file /path/to/ignition.gpg.pub --output /path/to/config.ign.gpg --armor --encrypt /path/to/config.ign ``` And attach it to `qemu-kvm` as a disk: ``` -drive if=none,id=ignition,format=raw,file=/path/to/config.ign.gpg,readonly=on \ -device virtio-blk,serial=ignition.gpg,iommu_platform=on,drive=ignition ``` --- src/cmd-buildextend-metal | 7 ++++++- src/cmd-generate-release-meta | 3 +++ src/create_disk.sh | 29 +++++++++++++++++++++-------- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/cmd-buildextend-metal b/src/cmd-buildextend-metal index 300624cbf1..145e881b7e 100755 --- a/src/cmd-buildextend-metal +++ b/src/cmd-buildextend-metal @@ -36,6 +36,7 @@ EOF # Parse options hostkey= genprotimgvm=/data.secex/genprotimgvm.qcow2 +ignition_pubkey= rc=0 build= force= @@ -186,7 +187,8 @@ disk_args=() qemu_args=() # SecureExecution extra stuff if [[ $secure_execution -eq "1" ]]; then - disk_args+=("--with-secure-execution") + ignition_pubkey=$(mktemp -p "${tmp_builddir}") + disk_args+=("--with-secure-execution" "--write-ignition-pubkey-to" "${ignition_pubkey}") if [ -z "${hostkey}" ]; then if [ ! -f "${genprotimgvm}" ]; then fatal "No genprotimgvm provided at ${genprotimgvm}" @@ -291,6 +293,9 @@ json.dump(j, sys.stdout, indent=4) cosa meta --workdir "${workdir}" --build "${build}" --artifact "${image_type}" --artifact-json "$(readlink -f meta.json.new)" /usr/lib/coreos-assembler/finalize-artifact "${img}" "${builddir}/${img}" +if [[ -n "${ignition_pubkey}" ]]; then + /usr/lib/coreos-assembler/finalize-artifact "${ignition_pubkey}" "${builddir}/ignition.gpg.pub" +fi # Quiet for the rest of this so the last thing we see is a success message set +x # clean up the tmpild diff --git a/src/cmd-generate-release-meta b/src/cmd-generate-release-meta index 5ec6b07074..577e7ab7ae 100755 --- a/src/cmd-generate-release-meta +++ b/src/cmd-generate-release-meta @@ -154,6 +154,9 @@ def append_build(out, input_): "bucket": cloud_dict[bucket_field], "url": cloud_dict[url_field] } + # IBM Secure Execution specific additions + if input_.get("images", {}).get("qemu-secex", None) is not None: + arch_dict["media"]["qemu-secex"]["artifacts"]["ignition-gpg-key"] = url_builder(out.get('stream'), out.get('release'), arch, "ignition.gpg.pub") # GCP specific additions if input_.get("gcp", None) is not None: diff --git a/src/create_disk.sh b/src/create_disk.sh index e207a49598..9d2b17a5ca 100755 --- a/src/create_disk.sh +++ b/src/create_disk.sh @@ -45,6 +45,7 @@ disk= platform=metal platforms_json= secure_execution=0 +ignition_pubkey= x86_bios_bootloader=1 extrakargs="" @@ -52,13 +53,14 @@ while [ $# -gt 0 ]; do flag="${1}"; shift; case "${flag}" in - --config) config="${1}"; shift;; - --help) usage; exit;; - --kargs) extrakargs="${extrakargs} ${1}"; shift;; - --no-x86-bios-bootloader) x86_bios_bootloader=0;; - --platform) platform="${1}"; shift;; - --platforms-json) platforms_json="${1}"; shift;; - --with-secure-execution) secure_execution=1;; + --config) config="${1}"; shift;; + --help) usage; exit;; + --kargs) extrakargs="${extrakargs} ${1}"; shift;; + --no-x86-bios-bootloader) x86_bios_bootloader=0;; + --platform) platform="${1}"; shift;; + --platforms-json) platforms_json="${1}"; shift;; + --with-secure-execution) secure_execution=1;; + --write-ignition-pubkey-to) ignition_pubkey="${1}"; shift;; *) echo "${flag} is not understood."; usage; exit 10;; esac; done @@ -449,6 +451,15 @@ chroot_run() { done } +generate_gpgkeys() { + local tmp_home + tmp_home=$(mktemp -d /tmp/gpg-XXXXXX) + gpg --homedir "${tmp_home}" --batch --passphrase '' --yes --quick-gen-key secex default + gpg --homedir "${tmp_home}" --armor --export secex > "${ignition_pubkey}" + gpg --homedir "${tmp_home}" --armor --export-secret-key secex > "/tmp/ignition.asc" + rm -rf "${tmp_home}" +} + # Other arch-specific bootloader changes # shellcheck disable=SC2031 case "$arch" in @@ -485,6 +496,8 @@ s390x) # in case builder itself runs with SecureExecution rdcore_zipl_args+=("--secex-mode=disable") chroot_run /usr/lib/dracut/modules.d/50rdcore/rdcore zipl "${rdcore_zipl_args[@]}" + else + generate_gpgkeys fi ;; esac @@ -574,7 +587,6 @@ if [[ ${secure_execution} -eq 1 ]]; then # set up dm-verity for the rootfs and bootfs create_dmverity root $rootfs create_dmverity boot $rootfs/boot - # We need to run the genprotimg step in a separate step for rhcos release images if [ ! -e /dev/disk/by-id/virtio-genprotimg ]; then echo "Building local Secure Execution Image, running zipl and genprotimg" @@ -582,6 +594,7 @@ if [[ ${secure_execution} -eq 1 ]]; then rdcore_zipl_args+=("--secex-mode=enforce" "--hostkey=/dev/disk/by-id/virtio-hostkey") rdcore_zipl_args+=("--append-karg=rootfs.roothash=$(cat /tmp/root-roothash)") rdcore_zipl_args+=("--append-karg=bootfs.roothash=$(cat /tmp/boot-roothash)") + rdcore_zipl_args+=("--append-file=/tmp/ignition.asc") chroot_run /usr/lib/dracut/modules.d/50rdcore/rdcore zipl "${rdcore_zipl_args[@]}" else echo "Building release Secure Execution Image, zipl and genprotimg will be run later" From 316dfaa7ca2b87418faa2c7dc6e455d81f239a4f Mon Sep 17 00:00:00 2001 From: Nikita Dubrovskii Date: Thu, 9 Feb 2023 15:11:41 +0100 Subject: [PATCH 2/3] s390x: add ignition-gpg-key to schema Signed-off-by: Nikita Dubrovskii --- pkg/builds/cosa_v1.go | 65 ++++++++++++++++++----------------- pkg/builds/schema_doc.go | 11 ++++-- src/cmd-buildextend-metal | 20 +++++++++-- src/cmd-generate-release-meta | 5 +-- src/v1.json | 9 ++++- 5 files changed, 70 insertions(+), 40 deletions(-) diff --git a/pkg/builds/cosa_v1.go b/pkg/builds/cosa_v1.go index 33c4dff6db..18f38598b7 100644 --- a/pkg/builds/cosa_v1.go +++ b/pkg/builds/cosa_v1.go @@ -1,7 +1,7 @@ package builds // generated by 'make schema' -// source hash: 8b3d3f50ea1a036d7209c463bc27cbcbc29707b69d162c900d291e401496a8ce +// source hash: de5dd618a2f1fabd897b6d1f9bd912e46d6d047774c1c9ab941ac28b0f127f54 type AdvisoryDiff []AdvisoryDiffItems @@ -83,37 +83,38 @@ type Build struct { } type BuildArtifacts struct { - Aliyun *Artifact `json:"aliyun,omitempty"` - Aws *Artifact `json:"aws,omitempty"` - Azure *Artifact `json:"azure,omitempty"` - AzureStack *Artifact `json:"azurestack,omitempty"` - Dasd *Artifact `json:"dasd,omitempty"` - DigitalOcean *Artifact `json:"digitalocean,omitempty"` - Exoscale *Artifact `json:"exoscale,omitempty"` - ExtensionsContainer *Artifact `json:"extensions-container,omitempty"` - Gcp *Artifact `json:"gcp,omitempty"` - HyperV *Artifact `json:"hyperv,omitempty"` - IbmCloud *Artifact `json:"ibmcloud,omitempty"` - Initramfs *Artifact `json:"initramfs,omitempty"` - Iso *Artifact `json:"iso,omitempty"` - Kernel *Artifact `json:"kernel,omitempty"` - KubeVirt *Artifact `json:"kubevirt,omitempty"` - LegacyOscontainer *Artifact `json:"legacy-oscontainer,omitempty"` - LiveInitramfs *Artifact `json:"live-initramfs,omitempty"` - LiveIso *Artifact `json:"live-iso,omitempty"` - LiveKernel *Artifact `json:"live-kernel,omitempty"` - LiveRootfs *Artifact `json:"live-rootfs,omitempty"` - Metal *Artifact `json:"metal,omitempty"` - Metal4KNative *Artifact `json:"metal4k,omitempty"` - Nutanix *Artifact `json:"nutanix,omitempty"` - OpenStack *Artifact `json:"openstack,omitempty"` - Ostree Artifact `json:"ostree"` - PowerVirtualServer *Artifact `json:"powervs,omitempty"` - Qemu *Artifact `json:"qemu,omitempty"` - SecureExecutionQemu *Artifact `json:"qemu-secex,omitempty"` - VirtualBox *Artifact `json:"virtualbox,omitempty"` - Vmware *Artifact `json:"vmware,omitempty"` - Vultr *Artifact `json:"vultr,omitempty"` + Aliyun *Artifact `json:"aliyun,omitempty"` + Aws *Artifact `json:"aws,omitempty"` + Azure *Artifact `json:"azure,omitempty"` + AzureStack *Artifact `json:"azurestack,omitempty"` + Dasd *Artifact `json:"dasd,omitempty"` + DigitalOcean *Artifact `json:"digitalocean,omitempty"` + Exoscale *Artifact `json:"exoscale,omitempty"` + ExtensionsContainer *Artifact `json:"extensions-container,omitempty"` + Gcp *Artifact `json:"gcp,omitempty"` + HyperV *Artifact `json:"hyperv,omitempty"` + IbmCloud *Artifact `json:"ibmcloud,omitempty"` + Initramfs *Artifact `json:"initramfs,omitempty"` + Iso *Artifact `json:"iso,omitempty"` + Kernel *Artifact `json:"kernel,omitempty"` + KubeVirt *Artifact `json:"kubevirt,omitempty"` + LegacyOscontainer *Artifact `json:"legacy-oscontainer,omitempty"` + LiveInitramfs *Artifact `json:"live-initramfs,omitempty"` + LiveIso *Artifact `json:"live-iso,omitempty"` + LiveKernel *Artifact `json:"live-kernel,omitempty"` + LiveRootfs *Artifact `json:"live-rootfs,omitempty"` + Metal *Artifact `json:"metal,omitempty"` + Metal4KNative *Artifact `json:"metal4k,omitempty"` + Nutanix *Artifact `json:"nutanix,omitempty"` + OpenStack *Artifact `json:"openstack,omitempty"` + Ostree Artifact `json:"ostree"` + PowerVirtualServer *Artifact `json:"powervs,omitempty"` + Qemu *Artifact `json:"qemu,omitempty"` + SecureExecutionIgnitionPubKey *Artifact `json:"ignition-gpg-key,omitempty"` + SecureExecutionQemu *Artifact `json:"qemu-secex,omitempty"` + VirtualBox *Artifact `json:"virtualbox,omitempty"` + Vmware *Artifact `json:"vmware,omitempty"` + Vultr *Artifact `json:"vultr,omitempty"` } type Cloudartifact struct { diff --git a/pkg/builds/schema_doc.go b/pkg/builds/schema_doc.go index fb549f75bf..5eac4ab2bc 100644 --- a/pkg/builds/schema_doc.go +++ b/pkg/builds/schema_doc.go @@ -1,5 +1,5 @@ // Generated by ./generate-schema.sh -// Source hash: 8b3d3f50ea1a036d7209c463bc27cbcbc29707b69d162c900d291e401496a8ce +// Source hash: de5dd618a2f1fabd897b6d1f9bd912e46d6d047774c1c9ab941ac28b0f127f54 // DO NOT EDIT package builds @@ -478,7 +478,8 @@ var generatedSchemaJSON = `{ "virtualbox", "vmware", "vultr", - "qemu-secex" + "qemu-secex", + "ignition-gpg-key" ], "properties": { "ostree": { @@ -523,6 +524,12 @@ var generatedSchemaJSON = `{ "title": "Secure Execution Qemu", "$ref": "#/definitions/artifact" }, + "ignition-gpg-key": { + "$id": "#/properties/images/properties/ignition-gpg-key", + "type": "object", + "title": "Secure Execution Ignition PubKey", + "$ref": "#/definitions/artifact" + }, "metal": { "$id": "#/properties/images/properties/metal", "type": "object", diff --git a/src/cmd-buildextend-metal b/src/cmd-buildextend-metal index 145e881b7e..e08d8a192f 100755 --- a/src/cmd-buildextend-metal +++ b/src/cmd-buildextend-metal @@ -289,13 +289,27 @@ j['images']['${image_type}${image_suffix}'] = { json.dump(j, sys.stdout, indent=4) " | jq -s add > "meta.json.new" +# one more artifact for Secure Execution +if [[ -n "${ignition_pubkey}" ]]; then + gpg_key=${name}-${build}-ignition-secex-key.gpg.pub + python3 -c " +import sys, json +j = json.load(sys.stdin) +j['images']['ignition-gpg-key'] = { + 'path': '${gpg_key}', + 'sha256': '$(sha256sum_str < "${ignition_pubkey}")', + 'size': $(stat -c '%s' "${ignition_pubkey}") +} +json.dump(j, sys.stdout, indent=4) +" < "meta.json.new" | jq -s add > "key.json" + mv key.json meta.json.new + /usr/lib/coreos-assembler/finalize-artifact "${ignition_pubkey}" "${builddir}/${gpg_key}" +fi + # and now the crucial bits cosa meta --workdir "${workdir}" --build "${build}" --artifact "${image_type}" --artifact-json "$(readlink -f meta.json.new)" /usr/lib/coreos-assembler/finalize-artifact "${img}" "${builddir}/${img}" -if [[ -n "${ignition_pubkey}" ]]; then - /usr/lib/coreos-assembler/finalize-artifact "${ignition_pubkey}" "${builddir}/ignition.gpg.pub" -fi # Quiet for the rest of this so the last thing we see is a success message set +x # clean up the tmpild diff --git a/src/cmd-generate-release-meta b/src/cmd-generate-release-meta index 577e7ab7ae..8e7f98d915 100755 --- a/src/cmd-generate-release-meta +++ b/src/cmd-generate-release-meta @@ -155,8 +155,9 @@ def append_build(out, input_): "url": cloud_dict[url_field] } # IBM Secure Execution specific additions - if input_.get("images", {}).get("qemu-secex", None) is not None: - arch_dict["media"]["qemu-secex"]["artifacts"]["ignition-gpg-key"] = url_builder(out.get('stream'), out.get('release'), arch, "ignition.gpg.pub") + i = input_.get("images", {}).get("ignition-gpg-key", None) + if i is not None: + arch_dict["media"]["qemu-secex"]["ignition-gpg-key"] = artifact(i) # GCP specific additions if input_.get("gcp", None) is not None: diff --git a/src/v1.json b/src/v1.json index 5dcdfb43d3..f6e76a7256 100644 --- a/src/v1.json +++ b/src/v1.json @@ -472,7 +472,8 @@ "virtualbox", "vmware", "vultr", - "qemu-secex" + "qemu-secex", + "ignition-gpg-key" ], "properties": { "ostree": { @@ -517,6 +518,12 @@ "title": "Secure Execution Qemu", "$ref": "#/definitions/artifact" }, + "ignition-gpg-key": { + "$id": "#/properties/images/properties/ignition-gpg-key", + "type": "object", + "title": "Secure Execution Ignition PubKey", + "$ref": "#/definitions/artifact" + }, "metal": { "$id": "#/properties/images/properties/metal", "type": "object", From 788f1c99771d64e12a90df73f8651b3aa5bcc5e5 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovskii Date: Tue, 14 Feb 2023 16:52:03 +0100 Subject: [PATCH 3/3] s390x: support Ignition private key for official builds Signed-off-by: Nikita Dubrovskii --- src/create_disk.sh | 20 +++++++++++++++---- .../genprotimg-script.sh | 8 ++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/create_disk.sh b/src/create_disk.sh index 9d2b17a5ca..7c93c06c14 100755 --- a/src/create_disk.sh +++ b/src/create_disk.sh @@ -452,11 +452,13 @@ chroot_run() { } generate_gpgkeys() { + local pkey + pkey="${1}" local tmp_home tmp_home=$(mktemp -d /tmp/gpg-XXXXXX) gpg --homedir "${tmp_home}" --batch --passphrase '' --yes --quick-gen-key secex default gpg --homedir "${tmp_home}" --armor --export secex > "${ignition_pubkey}" - gpg --homedir "${tmp_home}" --armor --export-secret-key secex > "/tmp/ignition.asc" + gpg --homedir "${tmp_home}" --armor --export-secret-key secex > "${pkey}" rm -rf "${tmp_home}" } @@ -496,8 +498,6 @@ s390x) # in case builder itself runs with SecureExecution rdcore_zipl_args+=("--secex-mode=disable") chroot_run /usr/lib/dracut/modules.d/50rdcore/rdcore zipl "${rdcore_zipl_args[@]}" - else - generate_gpgkeys fi ;; esac @@ -508,6 +508,12 @@ if [ "$arch" != s390x ]; then ostree config --repo $rootfs/ostree/repo set sysroot.bls-append-except-default 'grub_users=""' fi +# For local secex build we create an empty file and later mount-bind real private key to it, +# so rdcore could append it to initrd. Best approach is to teach rdcore how to append file +# with different source and dest- paths. +if [[ ${secure_execution} -eq 1 ]] && [[ ! -e /dev/disk/by-id/virtio-genprotimg ]]; then + touch "${deploy_root}/usr/lib/coreos/ignition.asc" +fi touch $rootfs/boot/ignition.firstboot # Finally, add the immutable bit to the physical root; we don't @@ -568,6 +574,10 @@ rdcore_replacement() { se_kernel="${se_tmp_boot}/vmlinuz" se_parmfile="${se_tmp_boot}/parmfile" + # Ignition GPG private key + mkdir -p "${se_tmp_boot}/usr/lib/coreos" + generate_gpgkeys "${se_tmp_boot}/usr/lib/coreos/ignition.asc" + blsfile=$(find "${rootfs}"/boot/loader/entries/*.conf) echo "$(grep options "${blsfile}" | cut -d' ' -f2-)" "${se_kargs_append[@]}" > "${se_parmfile}" kernel="${rootfs}/boot/$(grep linux "${blsfile}" | cut -d' ' -f2)" @@ -590,11 +600,13 @@ if [[ ${secure_execution} -eq 1 ]]; then # We need to run the genprotimg step in a separate step for rhcos release images if [ ! -e /dev/disk/by-id/virtio-genprotimg ]; then echo "Building local Secure Execution Image, running zipl and genprotimg" + generate_gpgkeys "/tmp/ignition.asc" + mount --rbind "/tmp/ignition.asc" "${deploy_root}/usr/lib/coreos/ignition.asc" # run zipl with root hashes as kargs rdcore_zipl_args+=("--secex-mode=enforce" "--hostkey=/dev/disk/by-id/virtio-hostkey") rdcore_zipl_args+=("--append-karg=rootfs.roothash=$(cat /tmp/root-roothash)") rdcore_zipl_args+=("--append-karg=bootfs.roothash=$(cat /tmp/boot-roothash)") - rdcore_zipl_args+=("--append-file=/tmp/ignition.asc") + rdcore_zipl_args+=("--append-file=/usr/lib/coreos/ignition.asc") chroot_run /usr/lib/dracut/modules.d/50rdcore/rdcore zipl "${rdcore_zipl_args[@]}" else echo "Building release Secure Execution Image, zipl and genprotimg will be run later" diff --git a/src/secex-genprotimgvm-scripts/genprotimg-script.sh b/src/secex-genprotimgvm-scripts/genprotimg-script.sh index ab52c1d65c..6ea3c999fd 100644 --- a/src/secex-genprotimgvm-scripts/genprotimg-script.sh +++ b/src/secex-genprotimgvm-scripts/genprotimg-script.sh @@ -6,11 +6,19 @@ echo "Preparing for genprotimg-daemon" source="/build/genprotimg" destination="/genprotimg" +pkey="usr/lib/coreos/ignition.asc" + +trap "rm -f ${source}/${pkey}" EXIT # Files need to be named correctly # genprotimg daemon can only see /genprotimg folder cp "${source}/vmlinuz" "${source}/initrd.img" "${source}/parmfile" "${destination}/" +# Append Ignition GPG private key to initramfs +cd "${source}" +echo "${pkey}" | cpio --quiet -H newc -o | gzip -9 -n >> "${destination}/initrd.img" +rm "${pkey}" + # Signal daemon that it can run genprotimg touch "${destination}/signal.file"