diff --git a/.github/actions/attest-ref-values/action.yml b/.github/actions/attest-ref-values/action.yml index 12f4fc5..62fcc51 100644 --- a/.github/actions/attest-ref-values/action.yml +++ b/.github/actions/attest-ref-values/action.yml @@ -26,7 +26,7 @@ runs: - name: Save the signed Sigstore bundles id: upload if: ${{ inputs.save == 'true' }} - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: path: '/tmp/${{ inputs.reference-path }}/*.scai.sigstore.json' retention-days: 15 diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index 589696c..e7b8dcf 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -6,9 +6,10 @@ on: permissions: id-token: write contents: write + attestations: write + packages: write jobs: - image: - if: github.actor == 'chkimes' + prep: runs-on: ubuntu-latest steps: @@ -26,6 +27,26 @@ jobs: go get ./... go build -o ../build-azure ./... + - name: Upload image attestation tool + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: image-attestation + path: build-azure/image-attestation + + image: + needs: prep + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4 + + - name: Download image attestation tool + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: image-attestation + path: build-azure + - name: Login to Azure uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2 with: @@ -39,6 +60,7 @@ jobs: AZURE_RESOURCE_GROUP: ${{ secrets.AZURE_RESOURCE_GROUP }} AZURE_LOCATION: ${{ secrets.AZURE_LOCATION }} AZURE_VM_NAME: ${{ secrets.AZURE_VM_NAME }} + SSH_KEYS_URL: https://github.com/${{ github.actor }}.keys run: | set -e @@ -54,7 +76,31 @@ jobs: path: img/* - name: Generate provenance - uses: github-early-access/generate-build-provenance@5fb744556d7e95958990349de0ddb84909c219bb #main + uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a #v3 with: subject-path: img/* + attest: + needs: [prep, image] + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4 + + - name: Login to Azure + uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Attesting Azure VM + env: + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + AZURE_RESOURCE_GROUP: ${{ secrets.AZURE_RESOURCE_GROUP }} + AZURE_VM_NAME: ${{ secrets.AZURE_VM_NAME }} + run: | + set -e + + build-azure/attest.sh diff --git a/.github/workflows/test-buildenv-l1-container.yml b/.github/workflows/test-buildenv-l1-container.yml index 0dbe657..78bc12b 100644 --- a/.github/workflows/test-buildenv-l1-container.yml +++ b/.github/workflows/test-buildenv-l1-container.yml @@ -2,6 +2,7 @@ name: Test for BuildEnv L1 container build on: + workflow_dispatch: push: branches: - main diff --git a/.github/workflows/test-buildenv-l2-container.yml b/.github/workflows/test-buildenv-l2-container.yml index 5983cba..04fd71f 100644 --- a/.github/workflows/test-buildenv-l2-container.yml +++ b/.github/workflows/test-buildenv-l2-container.yml @@ -2,6 +2,7 @@ name: Test for BuildEnv L2 container build on: + workflow_dispatch: push: branches: - main @@ -34,7 +35,7 @@ jobs: # kernel, build platform client, build executor, and root filesystem. - name: Attest container reference values id: ref-values - uses: chkimes/image-attestation/.github/actions/attest-ref-values@main + uses: ./.github/actions/attest-ref-values with: reference-path: 'build-container/*' diff --git a/README.md b/README.md index 29a2d2a..4ea9dc3 100644 --- a/README.md +++ b/README.md @@ -4,26 +4,52 @@ This is a proof-of-concept for the OpenSSF SLSA draft [Attested Build Environments (BuildEnv) track](https://github.com/slsa-framework/slsa/issues/975). The CLI in this repo implements vTPM-based attestation and -integrity checking of a Linux VM image. This repo also provides +integrity checking of a Linux VM image running in Azure. This repo also provides demo GHA workflows showcasing how to meet SLSA BuildEnv L1 and L2 (WIP). +This Demo uses 2 VMs - `ImageVM` and `HasherVM`. +- `ImageVM` is used to build the target image, it could be populated with all the tools and data needed +- `HasherVM` is a "worker" VM whose sole purpose is to compute Verity tree over the ImageVM root FS. + +```mermaid + graph LR; + ImageVM-- root FS disk -->ImageDisk; + HasherVM-- computes hashes -->ImageDisk; +``` + +Once `HasherVM` completes setting up `ImageDisk` it could be snapshotted to create clones of the `ImageVM`. + +Both VMs have to be trusted given that they have write access to the image root file system. Build image providers operating at BuildEnv 3 level should be protecting disk integrity while in use and at rest (as disk is reattached from ImageVM to the HasherVM), e.g. by encrypting the disk. + +## Image configuration + +Image has 3 notable partitions - `boot`, `root file system` and `verity tree`. Verity tree contains hashes for the root file system. Verity configuration data (e.g., root hash) is passed in a well-known configuration file within the boot partition. This file is processed by `initrd` to properly initialize (i.e. open) Verity device. Root hash is measured into TPM and hence is present in the remote attestation quote. + +Initrd sets up OverlayFS for the root file system using local ephemeral disk as a storage device. Build environments are ephemeral at `Build L3` and intermediate data is not expected to be preserved upon the termination of the environment. To achieve `BuildEnv L3` temporary storage must be encrypted, which could be done with an ephemeral key generated in Initrd upon booting the environment. `BuildEnv L2` does not require encryption. + +GRUB environment block is disabled to prevent unintended modificaton of the root file system upon booting. + ## How To Use -### Generate the initramfs +### Configure Azure account -From a fresh Ubuntu 20+ VM, install the initramfs scripts: -``` -sudo initramfs/install.sh -``` +_You need to have Azure account to run this demo_ -Generate the initramfs: -``` -sudo mkinitramfs -o image-attestation.img -``` +Few configuration settings need to be defined in [Actions secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). None of these are secrets per se but they are treated as secrets to reduce visibility of private resources in public Actions workflow logs: +- `AZURE_SUBSCRIPTION_ID` - Azure subscription id that you have `Owner` access to +- `AZURE_RESOURCE_GROUP` - resource group name where VM and all associated resources are provisioned +- `AZURE_TENANT_ID` - Azure Entra tenant id where App is located +- `AZURE_CLIENT_ID` - Azure app id for accessing subscription from the Actions workflow (**MUST** have `Contributor` access to the subscription) +- `AZURE_LOCATION` - location where VM is provisioned (e.g. `eastus`) +- `AZURE_VM_NAME` - Name to be used for the VM + +You need to configure OIDC credentials for the app to let Actions workflow acquire access token for this app ([doc link](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-azure)). + +### Dispath the workflow -### Update GRUB +Dispatch `Image Build` workflow to run this demo. Workflow will build a VM and give you access to this VM by adding GitHub actor public keys into the authorized keys for the `azureuser`. Keys are downloaded from `https://github.com/.keys` -TODO +After the VM is built you will be able to SSH to the VM with `ssh azureuser@` and poke around. ### CLI diff --git a/build-azure/attest.sh b/build-azure/attest.sh new file mode 100755 index 0000000..f2dbbc7 --- /dev/null +++ b/build-azure/attest.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +echo "Attesting VM..." +#TODO diff --git a/build-azure/build.sh b/build-azure/build.sh index 4731279..d68d02e 100755 --- a/build-azure/build.sh +++ b/build-azure/build.sh @@ -3,43 +3,61 @@ set -e SCRIPTPATH="$( cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P )" +VM_USER="${AZURE_VM_USER:-azureuser}" -if ! [ -f ~/.ssh/id_rsa ]; then - ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa -N "" -fi - +echo "Creating resource group..." az account set --subscription $AZURE_SUBSCRIPTION_ID az group create --resource-group $AZURE_RESOURCE_GROUP --location $AZURE_LOCATION -echo "Creating VM..." -az vm create --resource-group $AZURE_RESOURCE_GROUP \ - --name $AZURE_VM_NAME \ - --image Canonical:0001-com-ubuntu-minimal-mantic:minimal-23_10-gen2:23.10.202402260 \ - --size Standard_D4ds_v5 \ - --admin-username azureuser \ - --ssh-key-value ~/.ssh/id_rsa.pub \ - --security-type TrustedLaunch \ - --nic-delete-option delete \ - --os-disk-delete-option delete \ - | tee create.log - -cleanup() { - echo "Cleaning up..." - az vm delete --resource-group $AZURE_RESOURCE_GROUP --name $AZURE_VM_NAME --yes - rm -f create.log -} - -trap 'cleanup' EXIT - -IP_ADDR=$(cat create.log | jq -r .publicIpAddress | tail -n 1) -echo "VM created with IP address: $IP_ADDR" +echo "Creating image VM..." +IMAGE_VM_NAME="${AZURE_VM_NAME}" +$SCRIPTPATH/create-vm $IMAGE_VM_NAME +IMAGE_VM_ID=$(az vm show --resource-group $AZURE_RESOURCE_GROUP --name $IMAGE_VM_NAME | jq -r ".id") +IP_ADDR=$($SCRIPTPATH/get-ip $IMAGE_VM_ID) +$SCRIPTPATH/test-connectivity $IP_ADDR echo "Copying files to VM..." -scp -r -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "$SCRIPTPATH/../initramfs" azureuser@$IP_ADDR: -scp -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "$SCRIPTPATH/build-vm.sh" azureuser@$IP_ADDR: -scp -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "$SCRIPTPATH/client" azureuser@$IP_ADDR: -scp -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "$SCRIPTPATH/server" azureuser@$IP_ADDR: +scp -r -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "$SCRIPTPATH/../initramfs" "${VM_USER}@${IP_ADDR}": +scp -r -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "$SCRIPTPATH/../scripts" "${VM_USER}@${IP_ADDR}": +scp -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "$SCRIPTPATH/image-attestation" "${VM_USER}@${IP_ADDR}": echo "Building VM image..." -ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa azureuser@$IP_ADDR "sudo ../scripts/build-linux-vm.sh" -scp -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa azureuser@$IP_ADDR:~/image.tar.gz . +ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "${VM_USER}@${IP_ADDR}" "sudo SSH_KEYS_URL=$SSH_KEYS_URL scripts/build-linux-vm.sh" +scp -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "${VM_USER}@${IP_ADDR}":~/scripts/image.tar.gz . + +echo "Deallocating image VM..." +az vm deallocate --id $IMAGE_VM_ID + +echo "Detaching OS disk..." +DISK_ID=$(az vm show --id $IMAGE_VM_ID | jq -r ".storageProfile.osDisk.managedDisk.id") +IMAGE_ID=$(az disk show --id $DISK_ID | jq -r ".creationData.imageReference.id") +SWAP_DISK_NAME="${AZURE_VM_NAME}-$(openssl rand -base64 12 | tr -dc 'A-Za-z0-9' | head -c 16 ; echo)" +az disk create --image-reference $IMAGE_ID --resource-group $AZURE_RESOURCE_GROUP --name $SWAP_DISK_NAME --security-type TrustedLaunch +SWAP_DISK_ID=$(az disk show --resource-group $AZURE_RESOURCE_GROUP --name $SWAP_DISK_NAME | jq -r ".id") +az vm update --name $IMAGE_VM_NAME --resource-group $AZURE_RESOURCE_GROUP --os-disk $SWAP_DISK_ID + +echo "Creating hasher VM..." +HASHER_VM_NAME="${AZURE_VM_NAME}hash" +$SCRIPTPATH/create-vm $HASHER_VM_NAME +HASHER_VM_ID=$(az vm show --resource-group $AZURE_RESOURCE_GROUP --name $HASHER_VM_NAME | jq -r ".id") +IP_ADDR=$($SCRIPTPATH/get-ip $HASHER_VM_ID) +$SCRIPTPATH/test-connectivity $IP_ADDR + +echo "Attaching image disk (with 10% added space for verity hashes)..." +DISK_SIZE=$(az disk show --id $DISK_ID --query "diskSizeGB") +NEW_DISK_SIZE=$(echo "$DISK_SIZE * 1.1" | bc) +NEW_DISK_SIZE=$(printf "%.0f" "$NEW_DISK_SIZE") +az disk update --id $DISK_ID --disk-size-gb $NEW_DISK_SIZE +az vm disk attach --resource-group $AZURE_RESOURCE_GROUP --vm-name $HASHER_VM_NAME --disk-id $DISK_ID --lun 0 + +echo "Setting up Verity..." +scp -r -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "$SCRIPTPATH/../scripts" "${VM_USER}@${IP_ADDR}": +ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "${VM_USER}@${IP_ADDR}" "sudo scripts/rootfs-prepare-verity.sh" +ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "${VM_USER}@${IP_ADDR}" "sudo scripts/rootfs-measure-verity.sh" + +echo "Deleting hasher VM..." +az vm delete --id $HASHER_VM_ID --yes + +echo "Attaching OS disk back..." +az vm update --name $IMAGE_VM_NAME --resource-group $AZURE_RESOURCE_GROUP --os-disk $DISK_ID +az disk delete --id $SWAP_DISK_ID --yes diff --git a/build-azure/create-vm b/build-azure/create-vm new file mode 100755 index 0000000..d1c39c4 --- /dev/null +++ b/build-azure/create-vm @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +VM_NAME=$1 +VM_USER=${AZURE_VM_USER:-azureuser} + +if ! [ -f ~/.ssh/id_rsa ]; then + ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa -N "" +fi + +az vm create --resource-group $AZURE_RESOURCE_GROUP \ + --name $VM_NAME \ + --image "Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest" \ + --admin-username $VM_USER \ + --ssh-key-value ~/.ssh/id_rsa.pub \ + --size Standard_D4ds_v5 \ + --security-type TrustedLaunch \ + --nic-delete-option delete \ + --os-disk-delete-option delete \ + --patch-mode ImageDefault + +az vm boot-diagnostics enable --resource-group $AZURE_RESOURCE_GROUP \ + --name $VM_NAME diff --git a/build-azure/get-ip b/build-azure/get-ip new file mode 100755 index 0000000..75e6759 --- /dev/null +++ b/build-azure/get-ip @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +VM_RESOURCE=$1 + +NIC_RESOURCE=$(az vm show --id $VM_RESOURCE | jq -r ".networkProfile.networkInterfaces[0].id") +IP_RESOURCE=$(az network nic show --id $NIC_RESOURCE | jq -r ".ipConfigurations[0].publicIPAddress.id") +IP_ADDR=$(az network public-ip show --id $IP_RESOURCE | jq -r ".ipAddress") + +echo $IP_ADDR \ No newline at end of file diff --git a/build-azure/test-connectivity b/build-azure/test-connectivity new file mode 100755 index 0000000..ae70e3b --- /dev/null +++ b/build-azure/test-connectivity @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +IP_ADDR=$1 +VM_USER="${AZURE_VM_USER:-azureuser}" + +echo "Making sure we can connect to the VM using IP $IP_ADDR..." +MAX_RETRIES=10 +RETRY_DELAY=10 +count=0 +while [ $count -lt $MAX_RETRIES ]; do + ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa "${VM_USER}@${IP_ADDR}" "uname -a" && break + count=$((count + 1)) + echo "Retry $count/$MAX_RETRIES failed. Waiting $RETRY_DELAY seconds before next attempt..." + sleep $RETRY_DELAY +done + +if [ $count -eq $MAX_RETRIES ]; then + echo "All $MAX_RETRIES attempts failed." + exit 1 +fi + +echo "$IP_ADDR is reachable for SSH!" diff --git a/initramfs/initramfs-tools/scripts/local-bottom/bottom-verity b/initramfs/initramfs-tools/scripts/local-bottom/bottom-verity index d032398..9bd0ba0 100755 --- a/initramfs/initramfs-tools/scripts/local-bottom/bottom-verity +++ b/initramfs/initramfs-tools/scripts/local-bottom/bottom-verity @@ -16,17 +16,16 @@ case "${1}" in ;; esac -for x in $(cat /proc/cmdline); do -#for x in $(cat tmpcmdline); do - case ${x} in - verityname=*) - VERITYNAME=${x#*=} - ;; - overlaydev=*) - OVERLAYDEV=${x#*=} - ;; - esac -done +echo "Reading verity configuration" +mkdir -p /boot/efi +mount -t vfat -o ro /dev/disk/by-label/UEFI /boot/efi + +if [ -e "/boot/efi/verity/verity.name" ]; then + VERITYNAME=$(cat /boot/efi/verity/verity.name) + echo "VERITYNAME $VERITYNAME" +fi + +umount /boot/efi if [ -z "$VERITYNAME" ]; then echo "Verity not configured. Bypassing overlay creation." @@ -34,12 +33,15 @@ if [ -z "$VERITYNAME" ]; then exit 0 fi -if [ ! -e "/dev/mapper/$verityname" ]; then +if [ ! -e "/dev/mapper/$VERITYNAME" ]; then echo "Verity not setup. Bypassing overlay creation." /scripts/measure-event "OVERLAY_BYPASS" exit 0 fi +# TODO Discover it by 'LABEL="Temporary Storage"' +OVERLAYDEV="/dev/sdb1" + if [ -z "$OVERLAYDEV" ]; then echo "Overlay settings incorrect! Must supply overlaydev" /scripts/measure-event "OVERLAY_FAILURE" @@ -52,7 +54,7 @@ umount /root echo Mounting verity device to /roroot mkdir -p /roroot -mount "/dev/mapper/$verityname" /roroot +mount "/dev/mapper/$VERITYNAME" /roroot # TODO: in a real implementation, encrypt the device @@ -72,6 +74,7 @@ mkdir -p /mnt/overlay/work mkdir -p /mnt/overlay/upper mount -t overlay overlay -o lowerdir=/roroot,workdir=/mnt/overlay/work,upperdir=/mnt/overlay/upper "${rootmnt?}" +echo "OverlayFS created!" /scripts/measure-event "OVERLAY_SUCCESS" mkdir -p /root/measurements diff --git a/initramfs/initramfs-tools/scripts/local-top/top-verity b/initramfs/initramfs-tools/scripts/local-top/top-verity index 791575c..ded4240 100755 --- a/initramfs/initramfs-tools/scripts/local-top/top-verity +++ b/initramfs/initramfs-tools/scripts/local-top/top-verity @@ -16,49 +16,33 @@ case "${1}" in ;; esac -for x in $(cat /proc/cmdline); do -#for x in $(cat tmpcmdline); do - case ${x} in - veritydata=*) - value=${x#*=} - - # Find the device node path depending on the form of veritydata= : - case ${value} in - UUID=*) - VERITYDATADEV=/dev/disk/by-uuid/${value#UUID=} - ;; - LABEL=*) - VERITYDATADEV=/dev/disk/by-label/${value#LABEL=} - ;; - *) - VERITYDATADEV=${value} - ;; - esac - ;; - veritytree=*) - value=${x#*=} - - # Find the device node path depending on the form of veritytree= : - case ${value} in - UUID=*) - VERITYTREEDEV=/dev/disk/by-uuid/${value#UUID=} - ;; - LABEL=*) - VERITYTREEDEV=/dev/disk/by-label/${value#LABEL=} - ;; - *) - VERITYTREEDEV=${value} - ;; - esac - ;; - verityhash=*) - VERITYHASH=${x#*=} - ;; - verityname=*) - VERITYNAME=${x#*=} - ;; - esac -done +echo "Reading verity configuration" +mkdir -p /boot/efi +mount -t vfat -o ro /dev/disk/by-label/UEFI /boot/efi + +if [ -e "/boot/efi/verity/rootfs.uuid" ]; then + UUID=$(cat /boot/efi/verity/rootfs.uuid) + VERITYDATADEV=/dev/disk/by-uuid/${UUID} + echo "VERITYDATADEV $VERITYDATADEV" +fi + +if [ -e "/boot/efi/verity/verityfs.uuid" ]; then + UUID=$(cat /boot/efi/verity/verityfs.uuid) + VERITYTREEDEV=/dev/disk/by-uuid/${UUID} + echo "VERITYTREEDEV $VERITYTREEDEV" +fi + +if [ -e "/boot/efi/verity/rootfs.hash" ]; then + VERITYHASH=$(cat /boot/efi/verity/rootfs.hash) + echo "VERITYHASH $VERITYHASH" +fi + +if [ -e "/boot/efi/verity/verity.name" ]; then + VERITYNAME=$(cat /boot/efi/verity/verity.name) + echo "VERITYNAME $VERITYNAME" +fi + +umount /boot/efi SETTINGS_COUNT=0 if [ -n "$VERITYDATADEV" ]; then @@ -86,6 +70,14 @@ if [ $SETTINGS_COUNT -ne 4 ]; then exit 1 fi +#echo "Verifying verity hashes" +#ret=0 +#veritysetup --verbose --debug verify "$VERITYDATADEV" "$VERITYTREEDEV" "$VERITYHASH" || ret=$? +#if [ $ret -ne 0 ]; then +# echo "veritysetup verification failed!" +# exit 1 +#fi + echo "Opening verity device" /scripts/measure-event "VERITY_HASH: $VERITYHASH" diff --git a/scripts/build-linux-vm.sh b/scripts/build-linux-vm.sh index 01796ab..e480a80 100755 --- a/scripts/build-linux-vm.sh +++ b/scripts/build-linux-vm.sh @@ -9,21 +9,20 @@ if [ "$EUID" -ne 0 ] exit fi -VM_USER="${VM_USER:-azureuser}" -SSH_KEYS_URL="${SSH_KEYS_URL:-https://github.com/marcelamelara.keys}" - -echo Installing software necessary for image build -apt-get update -apt-get install -y rsync +VM_USER="${AZURE_VM_USER:-azureuser}" +SSH_KEYS_URL="${SSH_KEYS_URL:-https://github.com/$VM_USER.keys}" echo Installing software desired for the eventual image +apt-get update apt-get install -y golang tpm2-tools -echo Setting public keys +echo "Setting public keys from $SSH_KEYS_URL" mkdir -p /home/$VM_USER/.ssh touch /home/$VM_USER/.ssh/authorized_keys curl $SSH_KEYS_URL >> /home/$VM_USER/.ssh/authorized_keys chown -R $VM_USER:$VM_USER /home/$VM_USER/.ssh +chmod 700 /home/$VM_USER/.ssh +chmod 600 /home/$VM_USER/.ssh/authorized_keys echo Remove apt postinstall steps that impact the boot flow rm /etc/kernel/postinst.d/zz-update-grub @@ -33,15 +32,24 @@ echo Copying attestation utilities to sbin chmod +x image-attestation cp image-attestation /usr/sbin/image-attestation -echo Measuring rootfs -"$SCRIPTPATH"/scripts/rootfs-measure-verity.sh - echo Installing enlightened initramfs scripts and generate initramfs -"$SCRIPTPATH"/initramfs/install.sh -mkinitramfs -o "$TMP_DRIVE_PATH/initrd-$(uname -r).img" +TMP_DRIVE_PATH=$(mktemp -d) +"$SCRIPTPATH"/../initramfs/install.sh +mkinitramfs -o "$TMP_DRIVE_PATH/initrd.img-$(uname -r)" echo Copying the kernel cp "/boot/vmlinuz-$(uname -r)" $TMP_DRIVE_PATH echo Creating tarball tar -czf "$SCRIPTPATH"/image.tar.gz -C $TMP_DRIVE_PATH . + +echo Updating initramfs +sudo cp "$TMP_DRIVE_PATH/initrd.img-$(uname -r)" /boot/ + +echo Enabling initramfs +sudo sed -i '/^GRUB_FORCE_PARTUUID/ s/^/#/' /etc/default/grub.d/40-force-partuuid.cfg +#sudo sed -i 's/^GRUB_RECORDFAIL_TIMEOUT=.*/GRUB_RECORDFAIL_TIMEOUT=0/' /etc/default/grub.d/50-cloudimg-settings.cfg +sudo update-grub + +echo Disabling grubenv +sudo rm /boot/grub/grubenv diff --git a/scripts/rootfs-measure-verity.sh b/scripts/rootfs-measure-verity.sh index 871df24..d165267 100755 --- a/scripts/rootfs-measure-verity.sh +++ b/scripts/rootfs-measure-verity.sh @@ -1,54 +1,25 @@ #!/bin/bash -if [ "$EUID" -ne 0 ] - then echo "Please run as root" - exit -fi +set -e -VM_USER="${VM_USER:-azureuser}" +# TODO: pass it into this script as parameter +LUN_ID="0" +# TODO: Look it up by 'cloudimg-rootfs' partition label +ROOTFS_DEVICE="/dev/disk/azure/scsi1/lun${LUN_ID}-part1" +# TODO: Look it up by 'verity-tree' GPT partition name +VERITY_DEVICE="/dev/disk/azure/scsi1/lun${LUN_ID}-part2" +# TODO: Look it up by 'UEFI' partition label +UEFI_DEVICE="/dev/disk/azure/scsi1/lun${LUN_ID}-part15" -echo Installing software necessary for verity measurement -apt-get update -apt-get install -y cryptsetup +sudo mkdir -p /mnt/uefi +sudo mount $UEFI_DEVICE /mnt/uefi -echo Patching up fstab -# Use UEFI label for the EFI partition instead of UUID -sed -i 's/UUID=[^\s]\+\(\s\+\/boot\/efi\)/LABEL=UEFI\1/' /etc/fstab -# Remove the /mnt partition, it should already be used for overlay -sed -i '/\/mnt/d' /etc/fstab +echo "Setting up Verity for $ROOTFS_DEVICE on $VERITY_DEVICE" +sudo mkdir -p /mnt/uefi/verity +sudo veritysetup --verbose --debug format /dev/disk/azure/scsi1/lun0-part1 /dev/disk/azure/scsi1/lun0-part2 --root-hash-file /mnt/uefi/verity/rootfs.hash -echo Create and mount ext4 volume -TMP_DRIVE_PATH="/mnt/fs-tmp" -FS_FILE="$TMP_DRIVE_PATH/fs.img" -FS_MOUNT="$TMP_DRIVE_PATH/fs" -FS_SIZE="3G" +blkid -s UUID -o value $ROOTFS_DEVICE | sudo tee /mnt/uefi/verity/rootfs.uuid +blkid -s UUID -o value $VERITY_DEVICE | sudo tee /mnt/uefi/verity/verityfs.uuid +echo "slsa-verity" | sudo tee /mnt/uefi/verity/verity.name -mkdir -p $TMP_DRIVE_PATH -rm -rf $TMP_DRIVE_PATH/* -truncate -s $FS_SIZE $FS_FILE -mkfs.ext4 $FS_FILE -tune2fs -c 0 -i 0 $FS_FILE -mkdir -p $FS_MOUNT -mount $FS_FILE $FS_MOUNT - -# copy full rootfs into new fs volume (using -x to avoid cross fs boundaries) -# rsync over cp since cp has issues handling copies from / -# you might normally use /* to avoid this, but that starts pulling from /proc and -# other special filesystems that we don't want to copy -echo Copying rootfs into new volume -rsync -ax / $FS_MOUNT/ - -echo Cleaning up file from new volume -rm -rf $FS_MOUNT/tmp/* -rm -rf $FS_MOUNT/home/$VM_USER/* # this glob should leave the .ssh directory - -echo "Add a marker file to show that we're in an attested VM" -touch $FS_MOUNT/home/$VM_USER/attested-vm - -echo Unmounting fs volume -umount $FS_MOUNT -rm -r $FS_MOUNT - -echo Generating verity files -# Verity root hash will be in /measurements/eventlog if using enlightened initramfs -veritysetup format $FS_FILE $TMP_DRIVE_PATH/fs-verity.img --root-hash-file $TMP_DRIVE_PATH/fs.hash +sudo umount /mnt/uefi diff --git a/scripts/rootfs-prepare-verity.sh b/scripts/rootfs-prepare-verity.sh new file mode 100755 index 0000000..b648f63 --- /dev/null +++ b/scripts/rootfs-prepare-verity.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +echo Installing software necessary for verity measurement +sudo apt update +sudo apt-get install -y expect cryptsetup + +echo Patching up fstab +# Use UEFI label for the EFI partition instead of UUID +sed -i 's/UUID=[^\s]\+\(\s\+\/boot\/efi\)/LABEL=UEFI\1/' /etc/fstab +# Remove the /mnt partition, it should already be used for overlay +sed -i '/\/mnt/d' /etc/fstab + +echo "Fixing up GPT for the expanded disk" +expect -c 'spawn sudo parted /dev/disk/azure/scsi1/lun0 print; expect "Warning: Not all of the space available*"; send "f\r"; expect eof' + +# sfdisk provides sector already aligned to 2048 (unlike parted) +START_POS=$(sudo sfdisk /dev/disk/azure/scsi1/lun0 -F | tail -n 1 | awk '{print $1}') +echo "Creating Verity device partition at $START_POS" +sudo parted -s /dev/disk/azure/scsi1/lun0 mkpart verity-tree "${START_POS}s" 100% +sudo parted -s /dev/disk/azure/scsi1/lun0 print