mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-23 14:39:40 -05:00
bazel: add upload_os_images rule
This rule combines uplosi, the upload command, measurement code and cosign to upload OS images, extract measurements, sign them and upload the measurements.
This commit is contained in:
parent
b7bab7c3c8
commit
f237ae8ae2
1
bazel/osimage/BUILD.bazel
Normal file
1
bazel/osimage/BUILD.bazel
Normal file
@ -0,0 +1 @@
|
||||
exports_files(["upload_os_images.sh.in"])
|
104
bazel/osimage/upload_os_images.bzl
Normal file
104
bazel/osimage/upload_os_images.bzl
Normal file
@ -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,
|
||||
"@@DISSECT_TOOLCHAIN@@": ctx.executable._dissect_toolchain.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,
|
||||
"@@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",
|
||||
),
|
||||
"_dissect_toolchain": attr.label(
|
||||
default = Label("@systemd//:bin/systemd-dissect"),
|
||||
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",
|
||||
),
|
||||
"_version": attr.label(
|
||||
default = Label("//bazel/settings:tag"),
|
||||
allow_single_file = True,
|
||||
),
|
||||
},
|
||||
executable = True,
|
||||
)
|
144
bazel/osimage/upload_os_images.sh.in
Normal file
144
bazel/osimage/upload_os_images.sh.in
Normal file
@ -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 --yes --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"
|
Loading…
Reference in New Issue
Block a user