Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial POC for modify-image #3136

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ BOOT_CONFIG_INPUT = "${BUILDSYS_ROOT_DIR}/bootconfig-input"
# Boot Configuration initrd
BOOT_CONFIG = "${BUILDSYS_ROOT_DIR}/bootconfig.data"

# CA Bundle for root of trust update
CA_BUNDLE = { script = ['echo "${CA_BUNDLE}:${BUILDSYS_ROOT_DIR}/ca-bundle.crt"'] }

# secondary root.json for update
NEW_ROOT_JSON = { script = ['echo "${NEW_ROOT_JSON}:${PUBLISH_REPO_ROOT_JSON}"'] }

# Determines the kubeconfig that should be used by testsys. If no kubeconfig was provided and the
# default kubeconfig location does not exist, use the users default kubeconfig.
CARGO_MAKE_TESTSYS_KUBECONFIG_ARG = {script = [
Expand Down Expand Up @@ -1039,6 +1045,151 @@ ln -sfn "${PUBLISH_REPO_OUTPUT_DIR##*/}" "${PUBLISH_REPO_OUTPUT_DIR%/*}/latest"
'''
]

# This is a bash setup right now just to enable my testing
# We should move this into the pubsys repo logic now
[tasks.import-images]
dependencies = ["publish-setup", "publish-tools", "tuftool"]
script = [
'''
set -e

export PATH="${BUILDSYS_TOOLS_DIR}/bin:${PATH}"

IMAGE="bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${BUILDSYS_VERSION_BUILD}"
OUTDIR="${BUILDSYS_OUTPUT_DIR}/${BUILDSYS_VERSION_BUILD}"

bootlz4="${IMAGE}-boot.ext4.lz4"
imglz4="${IMAGE}.img.lz4"
rootlz4="${IMAGE}-root.ext4.lz4"
hashlz4="${IMAGE}-root.verity.lz4"

# TODO - do we care about these? At the moment I don't but probably later I do - we could get the manifest.json and fetch them there
# kmodtarxz="${IMAGE}-kmod-kit.tar.xz" # Versioned with v
# migrationstar="${IMAGE}-migrations.tar" # not in repo
# datalz4="${IMAGE}-data.img.lz4" # This doesn't seem to exist in the repo

# Here is the list of files actually built, from there, its up to me to figure out what ones need to be solved for *Version without v
#bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${VERSION}-${HASH}-boot.ext4.lz4
#bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${VERSION}-${HASH}-data.img.lz4
#bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${VERSION}-${HASH}.img.lz4
#bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-v${VERSION}-kmod-kit.tar.xz
#bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-v${VERSION}-${HASH}-migrations.tar
#bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${VERSION}-${HASH}-root.ext4.lz4
#bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${VERSION}-${HASH}-root.verity.lz4

if [ -s "${bootlz4}" ] || [ -s "${imglz4}" ] || [ -s "${rootlz4}" ] || [ -s "${hashlz4}" ]; then
echo "Image files already exist for the current version/commit - ${IMAGE} - please run 'cargo make clean' to remove them" >&2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should have a clean-images task so we don't blow out all the docker and rpm stuff.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that seems reasonable. I was focusing on CI/CD flows but it stands to reason one might have other builds in their directory that they don't want cleaned.

exit 1
fi

# Build path
tuftool download \
"${OUTDIR}" \
--target-name "${bootlz4}" \
--target-name "${imglz4}" \
--target-name "${rootlz4}" \
--target-name "${hashlz4}" \
--root ./root.json \
--metadata-url "https://updates.bottlerocket.aws/2020-07-07/${BUILDSYS_VARIANT}/${BUILDSYS_ARCH}/" \
--targets-url "https://updates.bottlerocket.aws/targets/"
Comment on lines +1085 to +1094
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend splitting this up into two parts, maybe two tasks:

  • a pubsys invocation to pull down the images into the build/repos structure
  • a buildsys invocation to import images from that local structure into the build/images structure

The current convention is for tuftool invocations to be wrapped by pubsys and I'd like to keep that.

I feel stronger about the buildsys approach since ideally we'd use similar Dockerfile targets and refactor out the shell functions to handle the symlinks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense. I mostly wrote this to help me test the actual modification flow but I agree putting this into pubsys and buildsys is a better place. I'll add some tasks to get that work done.



# Recreate symlinks so it looks like it was built from that hash
# variant-arch
# variant-arch-version
# variant-arch-version(with-v)
VERSION=$(echo "${BUILDSYS_VERSION_BUILD}"| cut -f1 -d"-")
ln -s "${OUTDIR}/${bootlz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-boot.ext4.lz"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems problematic in that you need to have the right sha checked out for this to match what was downloaded, right?

Are we letting the symlinks point at this from our current git sha even if it doesn't match the git sha of the downloaded image?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems problematic in that you need to have the right sha checked out for this to match what was downloaded, right?

No, you don't actually need the SHA checked out which is one of the primary desires of this logic, to avoid having to fiddle with getting that just right, instead you simply need to know the version/hash to download from the repo and then it will create it, this makes me realize I didn't show the structure of the build directory:

$ ls -al build/images/x86_64-metal-k8s-1.25
...
... 1.12.0-6ef1139f
...  latest -> 1.12.0-6ef1139f

$ ls -al build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f
...
 bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-boot.ext4
 bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-boot.ext4.lz4
 bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f.img.lz4
 bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-root.ext4
 bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-root.ext4.lz4
 bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-root.verity.lz4
 bottlerocket-metal-k8s-1.25-x86_64-1.12.0-boot.ext4.lz -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-boot.ext4.lz4
 bottlerocket-metal-k8s-1.25-x86_64-1.12.0.img.lz4 -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f.img.lz4
 bottlerocket-metal-k8s-1.25-x86_64-1.12.0-root.ext4.lz4 -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-root.ext4.lz4
 bottlerocket-metal-k8s-1.25-x86_64-1.12.0-root.verity.lz4 -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-root.verity.lz4
 bottlerocket-metal-k8s-1.25-x86_64-boot.ext4.lz -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-boot.ext4.lz4
 bottlerocket-metal-k8s-1.25-x86_64.img.lz4 -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f.img.lz4
 bottlerocket-metal-k8s-1.25-x86_64-root.ext4.lz4 -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-root.ext4.lz4
 bottlerocket-metal-k8s-1.25-x86_64-root.verity.lz4 -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-root.verity.lz4
 bottlerocket-metal-k8s-1.25-x86_64-v1.12.0-boot.ext4.lz -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-boot.ext4.lz4
 bottlerocket-metal-k8s-1.25-x86_64-v1.12.0.img.lz4 -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f.img.lz4
 bottlerocket-metal-k8s-1.25-x86_64-v1.12.0-root.ext4.lz4 -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-root.ext4.lz4
 bottlerocket-metal-k8s-1.25-x86_64-v1.12.0-root.verity.lz4 -> /storage/yeazelm/git/yeazelm-br/build/images/x86_64-metal-k8s-1.25/1.12.0-6ef1139f/bottlerocket-metal-k8s-1.25-x86_64-1.12.0-6ef1139f-root.verity.lz4

Are we letting the symlinks point at this from our current git sha even if it doesn't match the git sha of the downloaded image?

We move the symlinks to that point in time specifically for that version within the build, this is always the case for a particular build with a SHA, but won't affect a different builds. This is all within the versioned directory. We could avoid touching the build/images/${VARIANT_NAME}/latest link but I found it useful for finding the right thing. Its debatable if its "right" or not.

ln -s "${OUTDIR}/${bootlz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${VERSION}-boot.ext4.lz"
ln -s "${OUTDIR}/${bootlz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-v${VERSION}-boot.ext4.lz"

ln -s "${OUTDIR}/${imglz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}.img.lz4"
ln -s "${OUTDIR}/${imglz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${VERSION}.img.lz4"
ln -s "${OUTDIR}/${imglz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-v${VERSION}.img.lz4"

ln -s "${OUTDIR}/${rootlz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-root.ext4.lz4"
ln -s "${OUTDIR}/${rootlz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${VERSION}-root.ext4.lz4"
ln -s "${OUTDIR}/${rootlz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-v${VERSION}-root.ext4.lz4"

ln -s "${OUTDIR}/${hashlz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-root.verity.lz4"
ln -s "${OUTDIR}/${hashlz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${VERSION}-root.verity.lz4"
ln -s "${OUTDIR}/${hashlz4}" "${OUTDIR}/bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-v${VERSION}-root.verity.lz4"

ln -snf "${OUTDIR##*/}" "${BUILDSYS_OUTPUT_DIR}/latest"
'''
]


[tasks.modify-image]
dependencies = ["publish-setup", "publish-tools"]
script_runner = "bash"
script = [
'''
set -e

IMAGE="bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${BUILDSYS_VERSION_BUILD}.img.lz4"


run_modify_image="
/tmp/tools/modify-image --image=/tmp/build/${BUILDSYS_VERSION_BUILD}/${IMAGE} --new-root=/tmp/root.json --ca-bundle=/tmp/ca-bundle.crt
"

echo ${run_modify_image}

test_run="/tmp/tools/modify-image"

set -x

docker run --rm -it \
--user "$(id -u):$(id -g)" \
--security-opt label:disable \
-e CARGO_HOME="/tmp/.cargo" \
-v "${CARGO_HOME}":/tmp/.cargo \
-v "${BUILDSYS_ROOT_DIR}/tools":/tmp/tools \
-v "${BUILDSYS_OUTPUT_DIR}":/tmp/build \
-v "${CA_BUNDLE}":/tmp/ca-bundle.crt \
-v "${NEW_ROOT_JSON}":/tmp/root.json \
"${BUILDSYS_SDK_IMAGE}" \
bash -c "${run_modify_image}"
Comment on lines +1143 to +1153
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like this to be another buildsys invocation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had not looked at buildsys very deeply when writing this but after looking at buildsys more I think I understand where this would live. Are you thinking of this as a third stage in the Dockerfile as well or some other path I'm not seeing for this logic?

'''
]

[tasks.modify-image-shell]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get what this task is in relation to modify-image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been here to simulate the env of modify-image but be interactive. It won't make the final PR but is here for debugging while I work through the issues.

dependencies = ["publish-setup", "publish-tools"]
script_runner = "bash"
script = [
'''
set -e

IMAGE="bottlerocket-${BUILDSYS_VARIANT}-${BUILDSYS_ARCH}-${BUILDSYS_VERSION_BUILD}.img.lz4"


run_modify_image="
/tmp/tools/modify-image --image=/tmp/build/${BUILDSYS_VERSION_BUILD}/${IMAGE} --new-root=/tmp/root.json --ca-bundle=/tmp/ca-bundle.crt
"

echo ${run_modify_image}

test_run="/tmp/tools/modify-image"

set -x

docker run --rm -it \
--user "$(id -u):$(id -g)" \
--security-opt label:disable \
-e CARGO_HOME="/tmp/.cargo" \
-v "${CARGO_HOME}":/tmp/.cargo \
-v "${BUILDSYS_ROOT_DIR}/tools":/tmp/tools \
-v "${BUILDSYS_OUTPUT_DIR}":/tmp/build \
-v "${CA_BUNDLE}":/tmp/ca-bundle.crt \
-v "${NEW_ROOT_JSON}":/tmp/root.json \
"${BUILDSYS_SDK_IMAGE}" \
bash
'''
]



[tasks.validate-repo]
dependencies = ["publish-setup-without-key", "publish-tools"]
script_runner = "bash"
Expand Down
222 changes: 222 additions & 0 deletions tools/modify-image
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#!/usr/bin/env bash

set -eu -o pipefail
shopt -qs failglob

# check for veritysetup, debugfs, lz4
IMAGE_PATH=""
IMAGE=""
OUT_IMAGE=""
ROOTFILE=""
CABUNDLE=""
declare -a CHANGED_ROOTS

function buildJson() {

local -n arr=$1
JSON_STRING=$( jq -n \
--arg path "${arr[Path]}" \
--arg nh "${arr[NewHash]}" \
--arg oh "${arr[OldHash]}" \
'{Path: $path, NewHash: $nh, OldHash: $oh}' )

CHANGED_ROOTS+=(${JSON_STRING})
}

for opt in "$@"; do
optarg="$(expr "${opt}" : '[^=]*=\(.*\)')"
case "${opt}" in
--image=*) IMAGE_PATH="${optarg}" ;;
--image-out=*) OUT_IMAGE="${optarg}" ;;
--new-root=*) ROOTFILE="${optarg}" ;;
--ca-bundle=*) CABUNDLE="${optarg}" ;;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't solve for a few use cases we probably need, notably:

  • Secure Boot
  • OVA image formats

esac
done

echo -e "here are the vars: ${IMAGE_PATH} ${OUT_IMAGE} ${ROOTFILE} ${CABUNDLE}"

DESTDIR=$(dirname ${IMAGE_PATH})
WORKDIR="$(mktemp -d)"
cleanup() {
[ -n "${WORKDIR}" ] && rm -rf "${WORKDIR}"
}
trap 'cleanup' EXIT

cp ${IMAGE_PATH} ${WORKDIR}

pushd "${WORKDIR}" >/dev/null

IMAGE=$(basename "${IMAGE_PATH}")

if [[ ${IMAGE} == *.lz4 ]] ; then
echo "Extracting ${IMAGE}"
rm -f ${IMAGE%.*}
unlz4 ${IMAGE}
IMAGE=${IMAGE%.*}
fi

IMAGE_NAME=${IMAGE%.*}
# step 2: extract the root filesystem
echo -e "Starting extract of root for ${IMAGE}"
ROOT_IMAGE="root.ext4"
ROOT_START="$(sgdisk --print "${IMAGE}" | awk '/BOTTLEROCKET-ROOT-A/{ print $2 }')"
echo ${ROOT_START}
ROOT_END="$(sgdisk --print "${IMAGE}" | awk '/BOTTLEROCKET-ROOT-A/{ print $3 }')"
echo ${ROOT_END}
ROOT_SIZE="$(( ROOT_END - ROOT_START + 1))"
Comment on lines +62 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might help to associate units with these - sectors, bytes, whatever - since it's not immediately obvious what the fields we're cutting out of sgdisk output. If sgdisk --info lets us match lines by a prefix then that might help too.

Maybe a helper function that takes a partition name and makes a couple sgdisk calls to find it and populate the variables that the caller passes in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call out, I like the helper function idea!

dd if="${IMAGE}" of="${ROOT_IMAGE}" skip="${ROOT_START}" count="${ROOT_SIZE}"

echo "File exists at ${ROOT_IMAGE}"

# step 3: replace the trusted root.json
# os_t is the right label for SELinux based upon defaults
if [ -n "${ROOTFILE}" ] ; then
declare -A ROOT_ARR
JSON_STRING=""
ROOT_ARR[Path]='/usr/share/updog/root.json'
ROOT_ARR[OldHash]=$(debugfs -f <(echo cat /usr/share/updog/root.json) ${ROOT_IMAGE} 2>/dev/null | tail -n +2 | sha512sum | head -c 128)
ROOT_ARR[NewHash]=$(sha512sum ${ROOTFILE} | head -c 128)
buildJson ROOT_ARR
cat <<EOF | debugfs -w -f - "${ROOT_IMAGE}"
rm /usr/share/updog/root.json
write ${ROOTFILE} /usr/share/updog/root.json
ea_set /usr/share/updog/root.json security.selinux system_u:object_r:os_t:s0
EOF
fi

# replace CA Bundle if requested
if [ -n "${CABUNDLE}" ] ; then
declare -A CA_ARR
JSON_STRING=""
CA_ARR[Path]='/usr/share/factory/etc/pki/tls/certs/ca-bundle.crt'
CA_ARR[OldHash]=$(debugfs -f <(echo cat /usr/share/factory/etc/pki/tls/certs/ca-bundle.crt) ${ROOT_IMAGE} 2>/dev/null | tail -n +2 | sha512sum | head -c 128)
CA_ARR[NewHash]=$(sha512sum ${CABUNDLE} | head -c 128)
buildJson CA_ARR
cat <<EOF | debugfs -w -f - "${ROOT_IMAGE}"
rm /usr/share/factory/etc/pki/tls/certs/ca-bundle.crt
write ${CABUNDLE} /usr/share/factory/etc/pki/tls/certs/ca-bundle.crt
ea_set /usr/share/factory/etc/pki/tls/certs/ca-bundle.crt security.selinux system_u:object_r:os_t:s0
EOF
fi

# Create the full manifest of modifications for writing to trust-roots.json
echo -e "The full list of json is: ${CHANGED_ROOTS[@]}"
MODIFIED_JSON="$(jq --raw-output . <<< "${CHANGED_ROOTS[@]}")"

FULL_MODIFIED_JSON="$(jq --slurp 'sort_by(.Name)' <<< "${MODIFIED_JSON}" | jq '{"ModifiedRootsOfTrust": .}')"
Comment on lines +102 to +106
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have doubts about creating this manifest as a file in the image, but I could see adding this as an additional output for any newly built or modified image.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the primary value of this file is for image self-reference. One can use the image or booted OS directly to determine what is present and not rely upon build logs to determine this. I think of this similarly to the application-inventory.json where this data can be gleaned from the git repo and build logs but it can be convenient to have this data directly in the image. The main difference between the application inventory and this file is that this trust-roots file could be easily generated from the image/files on the filesystem directly. So if one does care about these files, they can find them and hash them themselves. So all in all, this is mostly a nice to have and the build output is probably valuable and I could be convinced to not include it in the image directly.


echo "${FULL_MODIFIED_JSON}" > trust-roots.json
cat <<EOF | debugfs -w -f - "${ROOT_IMAGE}"
rm /usr/share/bottlerocket/trust-roots.json
write trust-roots.json /usr/share/bottlerocket/trust-roots.json
ea_set /usr/share/bottlerocket/trust-roots.json security.selinux system_u:object_r:os_t:s0
EOF

echo -e "${FULL_MODIFIED_JSON}"


echo "Getting root image written back"

OLD_ROOT_SIZE="$(( ${ROOT_SIZE} * 512 ))"
NEW_ROOT_SIZE="$(stat --format='%s' "${ROOT_IMAGE}")"
if [ "${NEW_ROOT_SIZE}" -gt "${OLD_ROOT_SIZE}" ] ; then
echo "new root image size of ${NEW_ROOT_SIZE} bytes is greater than old size of ${OLD_ROOT_SIZE} bytes" >&2
exit 1
fi

# step 4: write the root filesystem back
dd if="${ROOT_IMAGE}" of="${IMAGE}" seek="${ROOT_START}" conv=notrunc

echo "Wrote files, recomputing verity"
# step 5: regenerate the hash image
HASH_IMAGE="verity.data"
HASH_START="$(sgdisk --print "${IMAGE}" | awk '/BOTTLEROCKET-HASH-A/{ print $2 }')"
HASH_END="$(sgdisk --print "${IMAGE}" | awk '/BOTTLEROCKET-HASH-A/{ print $3 }')"
HASH_SIZE="$(( HASH_END - HASH_START + 1))"

VERITY_VERSION="1"
VERITY_HASH_ALGORITHM="sha256"
VERITY_DATA_BLOCK_SIZE="4096"
VERITY_HASH_BLOCK_SIZE="4096"
VERITY_OUTPUT="$(veritysetup format \
--format "${VERITY_VERSION}" \
--hash "${VERITY_HASH_ALGORITHM}" \
--data-block-size "${VERITY_DATA_BLOCK_SIZE}" \
--hash-block-size "${VERITY_HASH_BLOCK_SIZE}" \
"${ROOT_IMAGE}" "${HASH_IMAGE}" | tee /dev/stderr)"
VERITY_DATA_4K_BLOCKS="$(grep '^Data blocks:' <<<"${VERITY_OUTPUT}" | awk '{ print $NF }')"
VERITY_DATA_512B_BLOCKS="$((VERITY_DATA_4K_BLOCKS * 8))"
VERITY_ROOT_HASH="$(grep '^Root hash:' <<<"${VERITY_OUTPUT}" | awk '{ print $NF }')"
VERITY_SALT="$(grep '^Salt:' <<<"${VERITY_OUTPUT}" | awk '{ print $NF }')"

OLD_HASH_SIZE="$(( HASH_SIZE * 512 ))"
NEW_HASH_SIZE="$(stat --format='%s' "${HASH_IMAGE}")"
if [ "${NEW_HASH_SIZE}" -gt "${OLD_HASH_SIZE}" ] ; then
echo "new hash image size of ${NEW_HASH_SIZE} bytes is greater than old size of ${OLD_HASH_SIZE} bytes" >&2
exit 1
fi

# step 6: write the hash image back
dd if="${HASH_IMAGE}" of="${IMAGE}" seek="${HASH_START}" conv=notrunc

# step 7: extract the boot filesystem
BOOT_IMAGE="boot.ext4"
BOOT_START="$(sgdisk --print "${IMAGE}" | awk '/BOTTLEROCKET-BOOT-A/{ print $2 }')"
BOOT_END="$(sgdisk --print "${IMAGE}" | awk '/BOTTLEROCKET-BOOT-A/{ print $3 }')"
BOOT_SIZE="$(( BOOT_END - BOOT_START + 1))"
dd if="${IMAGE}" of="${BOOT_IMAGE}" skip="${BOOT_START}" count="${BOOT_SIZE}"

# step 8: modify grub.cfg
echo 'cat /grub/grub.cfg' | debugfs -f - "${BOOT_IMAGE}" > grub.cfg

DM_MOD_OPTS_PATTERN="[0-9]\+ verity ${VERITY_VERSION}"
DM_MOD_OPTS="${VERITY_DATA_512B_BLOCKS} verity ${VERITY_VERSION}"

VERITY_OPTS_PATTERN="[0-9]\+ [0-9]\+ [0-9]\+"
VERITY_OPTS_PATTERN+=" 1 ${VERITY_HASH_ALGORITHM}"
VERITY_OPTS_PATTERN+=" [0-9a-f]\+ [0-9a-f]\+"

VERITY_OPTS="${VERITY_DATA_BLOCK_SIZE} ${VERITY_HASH_BLOCK_SIZE} ${VERITY_DATA_4K_BLOCKS}"
VERITY_OPTS+=" 1 ${VERITY_HASH_ALGORITHM}"
VERITY_OPTS+=" ${VERITY_ROOT_HASH} ${VERITY_SALT}"

sed -i \
-e "s,\(.*\) ${DM_MOD_OPTS_PATTERN} \(.*\),\1 ${DM_MOD_OPTS} \2," \
-e "s,\(.*\) ${VERITY_OPTS_PATTERN} \(.*\),\1 ${VERITY_OPTS} \2," \
grub.cfg


cat <<'EOF' | debugfs -w -f - "${BOOT_IMAGE}"
rm /grub/grub.cfg
write grub.cfg /grub/grub.cfg
ea_set /grub/grub.cfg security.selinux system_u:object_r:os_t:s0
EOF

OLD_BOOT_SIZE="$(( BOOT_SIZE * 512 ))"
NEW_BOOT_SIZE="$(stat --format='%s' "${BOOT_IMAGE}")"
if [ "${NEW_BOOT_SIZE}" -gt "${OLD_BOOT_SIZE}" ] ; then
echo "new boot image size of ${NEW_BOOT_SIZE} bytes is greater than old size of ${OLD_BOOT_SIZE} bytes" >&2
exit 1
fi

# step 9: write the boot filesystem back
dd if="${BOOT_IMAGE}" of="${IMAGE}" seek="${BOOT_START}" conv=notrunc

# now that the images have been written byte for byte back, we can resize them before compressing
resize2fs -M ${ROOT_IMAGE}
resize2fs -M ${BOOT_IMAGE}

# re compress the images
lz4 -9f ${IMAGE}
lz4 -9f ${ROOT_IMAGE}
lz4 -9f ${BOOT_IMAGE}
lz4 -9f ${HASH_IMAGE}

# step 10: copy out the altered image wait for now to keep cycles happy
echo -e "Copying ${IMAGE} back to ${DESTDIR}"
cp "${IMAGE}.lz4" "${DESTDIR}"
cp "${ROOT_IMAGE}.lz4" "${DESTDIR}"/${IMAGE_NAME}-root.ext4.lz4
cp "${BOOT_IMAGE}.lz4" "${DESTDIR}"/${IMAGE_NAME}-boot.ext4.lz4
cp "${HASH_IMAGE}.lz4" "${DESTDIR}"/${IMAGE_NAME}-root.verity.lz4

popd > /dev/null