Skip to content

Commit

Permalink
bazel: add upload_os_images rule
Browse files Browse the repository at this point in the history
This rule combines uplosi, the upload command, measurement code and cosign
to upload OS images, extract measurements, sign them and upload the measurements.
  • Loading branch information
malt3 committed Jan 5, 2024
1 parent 177ccf1 commit 1bdbb92
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 0 deletions.
1 change: 1 addition & 0 deletions bazel/osimage/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports_files(["upload_os_images.sh.in"])
104 changes: 104 additions & 0 deletions bazel/osimage/upload_os_images.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
""" Bazel rule for uploading a set of OS images to cloud providers. """

def _upload_os_images_impl(ctx):
executable = ctx.actions.declare_file("upload_os_images_%s.sh" % ctx.label.name)
files = []
files.extend(ctx.files.image_dirs)
files.append(ctx.file._version)
files.append(ctx.file._upload_cli)
files.append(ctx.file._measured_boot)
files.append(ctx.file._uplosi)
files.append(ctx.file._dissect_toolchain)
files.append(ctx.file._cosign)
files.append(ctx.file._rekor_cli)
files.append(ctx.file._parallel)
raw_image_paths = []
for image_dir in ctx.files.image_dirs:
raw_image_paths.append("%s/constellation.raw" % image_dir.short_path)
substitutions = {
"@@COSIGN@@": ctx.executable._cosign.short_path,
"@@FILES@@": " ".join(raw_image_paths),
"@@MEASURED_BOOT@@": ctx.executable._measured_boot.short_path,
"@@PARALLEL@@": ctx.executable._parallel.short_path,
"@@REKOR_CLI@@": ctx.executable._rekor_cli.short_path,
"@@UPLOAD_CLI@@": ctx.executable._upload_cli.short_path,
"@@UPLOSI@@": ctx.executable._uplosi.short_path,
"@@DISSECT_TOOLCHAIN@@": ctx.executable._dissect_toolchain.short_path,
"@@VERSION@@": ctx.file._version.short_path,
}
ctx.actions.expand_template(
template = ctx.file._upload_sh_tpl,
output = executable,
is_executable = True,
substitutions = substitutions,
)
runfiles = ctx.runfiles(files = files)
runfiles = runfiles.merge(ctx.attr._uplosi[DefaultInfo].data_runfiles)
runfiles = runfiles.merge(ctx.attr._dissect_toolchain[DefaultInfo].data_runfiles)
runfiles = runfiles.merge(ctx.attr._cosign[DefaultInfo].data_runfiles)
runfiles = runfiles.merge(ctx.attr._rekor_cli[DefaultInfo].data_runfiles)
runfiles = runfiles.merge(ctx.attr._parallel[DefaultInfo].data_runfiles)
runfiles = runfiles.merge(ctx.attr._upload_cli[DefaultInfo].data_runfiles)
runfiles = runfiles.merge(ctx.attr._measured_boot[DefaultInfo].data_runfiles)

return DefaultInfo(executable = executable, runfiles = runfiles)

upload_os_images = rule(
implementation = _upload_os_images_impl,
attrs = {
"image_dirs": attr.label_list(
doc = "List of directories containing OS images to upload.",
),
"_cosign": attr.label(
default = Label("@cosign//:bin/cosign"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
"_measured_boot": attr.label(
default = Label("//image/measured-boot/cmd"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
"_parallel": attr.label(
default = Label("@parallel//:bin/parallel"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
"_rekor_cli": attr.label(
default = Label("@rekor-cli//:bin/rekor-cli"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
"_upload_cli": attr.label(
default = Label("//image/upload"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
"_upload_sh_tpl": attr.label(
default = "upload_os_images.sh.in",
allow_single_file = True,
),
"_uplosi": attr.label(
default = Label("@uplosi//:bin/uplosi"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
"_dissect_toolchain": attr.label(
default = Label("@systemd//:bin/systemd-dissect"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
"_version": attr.label(
default = Label("//bazel/settings:tag"),
allow_single_file = True,
),
},
executable = True,
)
144 changes: 144 additions & 0 deletions bazel/osimage/upload_os_images.sh.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#!/usr/bin/env bash

set -euo pipefail
shopt -s inherit_errexit

# This script handles the upload of OS images and their corresponding image info.

POSITIONAL_ARGS=()

ref=""
upload_signed_measurements=0
fake_sign=0

while [[ $# -gt 0 ]]; do
case $1 in
--ref)
ref="$2"
shift # past argument
shift # past value
;;
--upload-measurements)
upload_signed_measurements=1
shift # past argument
;;
--fake-sign)
fake_sign=1
shift # past argument
;;
-*)
echo "Unknown option $1"
exit 1
;;
*)
POSITIONAL_ARGS+=("$1") # save positional arg
shift # past argument
;;
esac
done

set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters

if [[ $# -ne 0 ]]; then
echo "Unknown positional arguments: $*"
exit 1
fi

if [[ -z ${ref} ]]; then
echo "Missing required argument --ref"
exit 1
fi

version_file=$(realpath @@VERSION@@)
stat "${version_file}" >> /dev/null
version=$(cat "${version_file}")

uplosi=$(realpath @@UPLOSI@@)
stat "${uplosi}" >> /dev/null

systemd_dissect=$(realpath @@DISSECT_TOOLCHAIN@@)
stat "${systemd_dissect}" >> /dev/null
export DISSECT_TOOLCHAIN="${systemd_dissect}"

cosign=$(realpath @@COSIGN@@)
stat "${cosign}" >> /dev/null

rekor_cli=$(realpath @@REKOR_CLI@@)
stat "${rekor_cli}" >> /dev/null

upload_cli=$(realpath @@UPLOAD_CLI@@)
stat "${upload_cli}" >> /dev/null

measured_boot=$(realpath @@MEASURED_BOOT@@)
stat "${measured_boot}" >> /dev/null

parallel=$(realpath @@PARALLEL@@)
stat "${parallel}" >> /dev/null

FILES=(@@FILES@@)

workspace=$(mktemp -d)
# shellcheck disable=SC2064
trap "rm -rf ${workspace}" EXIT

echo Uploading "${#FILES[@]}" OS images. This may take a while... >&2

"${parallel}" --will-cite \
"${upload_cli}" uplosi \
--uplosi-path "${uplosi}" \
--version "${version}" \
--ref "${ref}" \
--raw-image {} \
--out "${workspace}/image-upload-{#}.json" \
::: "${FILES[@]}"

"${upload_cli}" info "${workspace}/"image-upload-*.json

if [[ ${upload_signed_measurements} -eq 0 ]]; then
echo "Skipping signed measurements upload. Enable by setting --upload-measurements" >&2
exit 0
fi

echo Uploading signed measurements. This requires sudo and a signing key. >&2
i=1
for file in "${FILES[@]}"; do
combined_name=$(basename "$(dirname "${file}")")
IFS="_" read -r csp attestation_variant stream <<< "${combined_name}"
sudo -E "${measured_boot}" "${file}" "${workspace}/pcrs-${i}.json"
sudo chown "$(id -u -n)" "${workspace}/pcrs-${i}.json"
"${upload_cli}" measurements envelope \
--in "${workspace}/pcrs-${i}.json" \
--out "${workspace}/pcrs-${i}.json" \
--version "ref/${ref}/stream/${stream}/${version}" \
--csp "${csp}" \
--attestation-variant "${attestation_variant}"
i=$((i + 1))
done

"${upload_cli}" measurements merge \
--out "${workspace}/measurements.json" \
"${workspace}"/pcrs-*.json

if [[ ${fake_sign} -eq 1 ]]; then
echo "Skipping signing of measurements and using fake signature instead (--fake-sign is set)." >&2
echo "THOSE MEASUREMENTS BELONG TO A DEBUG IMAGE. THOSE ARE NOT SINGED BY ANY KEY." > "${workspace}/measurements.json.sig"
else
# shellcheck disable=SC2016
echo 'Creating real signature with keys referenced in $COSIGN_PUBLIC_KEY_PATH, $COSIGN_PRIVATE_KEY and $COSIGN_PASSWORD. Set "--fake-sign" for debugging purposes.' >&2
# Enabling experimental mode also publishes signature to Rekor
COSIGN_EXPERIMENTAL=1 "${cosign}" sign-blob --key env://COSIGN_PRIVATE_KEY \
"${workspace}/measurements.json" > "${workspace}/measurements.json.sig"
# Verify - As documentation & check
# Local Signature (input: artifact, key, signature)
"${cosign}" verify-blob --key "${COSIGN_PUBLIC_KEY_PATH}" \
--signature "${workspace}/measurements.json.sig" \
"${workspace}/measurements.json"
# Transparency Log Signature (input: artifact, key)
uuid=$("${rekor_cli}" search --artifact "${workspace}/measurements.json" | tail -n 1)
sig=$("${rekor_cli}" get --uuid="${uuid}" --format=json | jq -r .Body.HashedRekordObj.signature.content)
"${cosign}" verify-blob --key "${COSIGN_PUBLIC_KEY_PATH}" --signature <(echo "${sig}") "${workspace}/measurements.json"
fi

"${upload_cli}" measurements upload \
--measurements "${workspace}/measurements.json" \
--signature "${workspace}/measurements.json.sig"

0 comments on commit 1bdbb92

Please sign in to comment.