diff --git a/.github/actions/os_build_variables/action.yml b/.github/actions/os_build_variables/action.yml index 69ad2f709..b70360c73 100644 --- a/.github/actions/os_build_variables/action.yml +++ b/.github/actions/os_build_variables/action.yml @@ -36,9 +36,9 @@ outputs: awsImagePath: description: "AWS image path" value: ${{ steps.aws.outputs.imagePath }} - awsAmiOutput: - description: "AWS ami output path" - value: ${{ steps.aws.outputs.amiOutput }} + awsJsonOutput: + description: "AWS ami json output path" + value: ${{ steps.aws.outputs.jsonOutput }} awsImageFilename: description: "AWS raw image filename" value: ${{ steps.aws.outputs.imageFilename }} @@ -72,6 +72,9 @@ outputs: azureImagePath: description: "Azure image path" value: ${{ steps.azure.outputs.imagePath }} + azureJsonOutput: + description: "Azure image json output path" + value: ${{ steps.azure.outputs.jsonOutput }} azureSecurityType: description: "Azure security type" value: ${{ steps.azure.outputs.securityType }} @@ -108,6 +111,9 @@ outputs: gcpImagePath: description: "GCP image path" value: ${{ steps.gcp.outputs.imagePath }} + gcpJsonOutput: + description: "GCP image json output path" + value: ${{ steps.gcp.outputs.jsonOutput }} gcpImageName: description: "GCP image name" value: ${{ steps.gcp.outputs.imageName }} @@ -117,6 +123,18 @@ outputs: gcpImageFamily: description: "GCP image family" value: ${{ steps.gcp.outputs.imageFamily }} + qemuJsonOutput: + description: "QEMU image json output path" + value: ${{ steps.qemu.outputs.jsonOutput }} + qemuBucket: + description: "QEMU S3 bucket" + value: ${{ steps.qemu.outputs.bucket }} + qemuBaseUrl: + description: "QEMU raw image base URL" + value: ${{ steps.qemu.outputs.baseUrl }} + qemuImagePath: + description: "QEMU image path" + value: ${{ steps.qemu.outputs.imagePath }} runs: using: "composite" @@ -142,7 +160,7 @@ runs: echo "bucket=constellation-images" >> $GITHUB_OUTPUT echo "efivarsPath=${basePath}/mkosi.output.aws/fedora~36/efivars.bin" >> $GITHUB_OUTPUT echo "imagePath=${basePath}/mkosi.output.aws/fedora~36/image.raw" >> $GITHUB_OUTPUT - echo "amiOutput=${basePath}/mkosi.output.aws/fedora~36/ami.json" >> $GITHUB_OUTPUT + echo "jsonOutput=${basePath}/mkosi.output.aws/fedora~36/image-upload.json" >> $GITHUB_OUTPUT echo "imageFilename=image-$(date +%s).raw" >> $GITHUB_OUTPUT if [ "${imageType}" = release ] then @@ -172,6 +190,7 @@ runs: semver: ${{ steps.version.outputs.semanticVersion }} pseudover: ${{ steps.version.outputs.pseudoVersion }} branchName: ${{ steps.version.outputs.branchName }} + uploadVariant: ${{ inputs.uploadVariant }} run: | echo "resourceGroupName=constellation-images" >> $GITHUB_OUTPUT echo "region=northeurope" >> $GITHUB_OUTPUT @@ -181,6 +200,7 @@ runs: echo "publisher=edgelesssys" >> $GITHUB_OUTPUT echo "rawImagePath=${basePath}/mkosi.output.azure/fedora~36/image.raw" >> $GITHUB_OUTPUT echo "imagePath=${basePath}/mkosi.output.azure/fedora~36/image.vhd" >> $GITHUB_OUTPUT + echo "jsonOutput=${basePath}/mkosi.output.azure/fedora~36/image-upload${uploadVariant}.json" >> $GITHUB_OUTPUT # TODO: set default security type to "ConfidentialVM" once replication is possible securityType=${{ inputs.uploadVariant }} if [ -z "${securityType}" ]; then @@ -234,6 +254,7 @@ runs: echo "region=europe-west3" >> $GITHUB_OUTPUT echo "rawImagePath=${basePath}/mkosi.output.gcp/fedora~36/image.raw" >> $GITHUB_OUTPUT echo "imagePath=${basePath}/mkosi.output.gcp/fedora~36/image.tar.gz" >> $GITHUB_OUTPUT + echo "jsonOutput=${basePath}/mkosi.output.gcp/fedora~36/image-upload.json" >> $GITHUB_OUTPUT if [ "${imageType}" = release ] then echo "imageName=constellation-${imageVersion//./-}" >> $GITHUB_OUTPUT @@ -249,3 +270,15 @@ runs: echo "imageFilename=constellation-${timestamp}.tar.gz" >> $GITHUB_OUTPUT echo "imageFamily=constellation-${branchName}" >> $GITHUB_OUTPUT fi + + - name: Configure QEMU input variables + id: qemu + if: ${{ inputs.csp == 'qemu' }} + shell: bash + env: + basePath: ${{ inputs.basePath }} + run: | + echo "bucket=cdn-confidential-cloud-backend" >> $GITHUB_OUTPUT + echo "baseUrl=https://cdn.confidential.cloud" >> $GITHUB_OUTPUT + echo "imagePath=${basePath}/mkosi.output.qemu/fedora~36/image.raw" >> $GITHUB_OUTPUT + echo "jsonOutput=${basePath}/mkosi.output.qemu/fedora~36/image-upload.json" >> $GITHUB_OUTPUT diff --git a/.github/workflows/build-os-image.yml b/.github/workflows/build-os-image.yml index 0098b6670..c3db33435 100644 --- a/.github/workflows/build-os-image.yml +++ b/.github/workflows/build-os-image.yml @@ -67,6 +67,7 @@ jobs: outputs: imageType: ${{ steps.image-type.outputs.imageType }} pkiSet: ${{ steps.pki-set.outputs.pkiSet }} + imageVersionUid: ${{ steps.image-version-uid.outputs.imageVersionUid }} steps: - name: Checkout @@ -103,6 +104,18 @@ jobs: echo "pkiSet=pki_testing" >> "$GITHUB_OUTPUT" fi + - name: Determine image version uid + id: image-version-uid + shell: bash + run: | + if [ "${{ steps.image-type.outputs.imageType }}" = "release" ]; then + echo "imageVersionUid=${{ github.event.inputs.imageVersion }}" >> "$GITHUB_OUTPUT" + elif [ "${{ steps.image-type.outputs.imageType }}" = "debug" ]; then + echo "imageVersionUid=debug-${{ steps.version.outputs.pseudoVersion }}" >> "$GITHUB_OUTPUT" + else + echo "imageVersionUid=${{ steps.version.outputs.branchName }}-${{ steps.version.outputs.pseudoVersion }}" >> "$GITHUB_OUTPUT" + fi + make-os-image: name: "Build OS using mkosi" needs: [build-settings, build-dependencies] @@ -250,7 +263,7 @@ jobs: strategy: fail-fast: false matrix: - csp: [aws, azure, gcp] + csp: [aws, azure, gcp, qemu] upload-variant: [""] include: - csp: azure @@ -291,7 +304,10 @@ jobs: - name: Login to AWS uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # tag=v1.7.0 - if: ${{ matrix.csp == 'aws' || matrix.csp == 'azure' }} + # on AWS, login is required to upload the image as AMI + # on Azure, login is done to download the VMGS from S3 + # on QEMU, login is done to upload the image to S3 + if: ${{ matrix.csp == 'aws' || matrix.csp == 'azure' || matrix.csp == 'qemu' }} with: role-to-assume: arn:aws:iam::795746500882:role/GitHubConstellationImagePipeline aws-region: eu-central-1 @@ -334,14 +350,14 @@ jobs: run: | echo "::group::Upload AWS image" secure-boot/aws/create_uefivars.sh "${AWS_EFIVARS_PATH}" - upload/upload_aws.sh "${AWS_AMI_OUTPUT}" - echo -e "Uploaded AWS image: \`\`\`$(jq < "${AWS_AMI_OUTPUT}")\`\`\`" >> "$GITHUB_STEP_SUMMARY" + upload/upload_aws.sh + echo -e "Uploaded AWS image: \n\n\`\`\`\n$(jq < "${AWS_JSON_OUTPUT}")\n\`\`\`\n" >> "$GITHUB_STEP_SUMMARY" echo "::endgroup::" working-directory: ${{ github.workspace }}/image if: ${{ matrix.csp == 'aws' }} env: PKI: ${{ github.workspace }}/image/pki - AWS_AMI_OUTPUT: ${{ steps.vars.outputs.awsAmiOutput }} + AWS_JSON_OUTPUT: ${{ steps.vars.outputs.awsJsonOutput }} AWS_BUCKET: ${{ steps.vars.outputs.awsBucket }} AWS_EFIVARS_PATH: ${{ steps.vars.outputs.awsEfivarsPath }} AWS_IMAGE_FILENAME: ${{ steps.vars.outputs.awsImageFilename }} @@ -357,12 +373,13 @@ jobs: echo "::group::Upload GCP image" upload/pack.sh gcp "${GCP_RAW_IMAGE_PATH}" "${GCP_IMAGE_PATH}" upload/upload_gcp.sh - echo -e "Uploaded GCP image: \`projects/${GCP_PROJECT}/global/images/${GCP_IMAGE_NAME}\`" >> "$GITHUB_STEP_SUMMARY" + echo -e "Uploaded GCP image: \n\n\`\`\`\n$(jq < "${GCP_JSON_OUTPUT}")\n\`\`\`\n" >> "$GITHUB_STEP_SUMMARY" echo "::endgroup::" working-directory: ${{ github.workspace }}/image if: ${{ matrix.csp == 'gcp' }} env: PKI: ${{ github.workspace }}/image/pki + GCP_JSON_OUTPUT: ${{ steps.vars.outputs.gcpJsonOutput }} GCP_BUCKET: ${{ steps.vars.outputs.gcpBucket }} GCP_IMAGE_FAMILY: ${{ steps.vars.outputs.gcpImageFamily }} GCP_IMAGE_FILENAME: ${{ steps.vars.outputs.gcpImageFilename }} @@ -378,12 +395,13 @@ jobs: echo "::group::Upload Azure image" upload/pack.sh azure "${AZURE_RAW_IMAGE_PATH}" "${AZURE_IMAGE_PATH}" upload/upload_azure.sh -g --disk-name "${AZURE_DISK_NAME}" "${AZURE_VMGS_PATH}" - echo -e "Uploaded Azure ${AZURE_SECURITY_TYPE} image: \`/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/${AZURE_RESOURCE_GROUP_NAME^^}/providers/Microsoft.Compute/galleries/${AZURE_GALLERY_NAME}/images/${AZURE_IMAGE_DEFINITION}/versions/${AZURE_IMAGE_VERSION}\`" >> "$GITHUB_STEP_SUMMARY" + echo -e "Uploaded Azure ${AZURE_SECURITY_TYPE} image: \n\n\`\`\`\n$(jq < "${AZURE_JSON_OUTPUT}")\n\`\`\`\n" >> "$GITHUB_STEP_SUMMARY" echo "::endgroup::" working-directory: ${{ github.workspace }}/image if: ${{ matrix.csp == 'azure' }} env: PKI: ${{ github.workspace }}/image/pki + AZURE_JSON_OUTPUT: ${{ steps.vars.outputs.azureJsonOutput }} AZURE_DISK_NAME: ${{ steps.vars.outputs.azureDiskName }} AZURE_GALLERY_NAME: ${{ steps.vars.outputs.azureGalleryName }} AZURE_IMAGE_DEFINITION: ${{ steps.vars.outputs.azureImageDefinition }} @@ -400,9 +418,34 @@ jobs: AZURE_SKU: ${{ steps.vars.outputs.azureSku }} AZURE_VMGS_PATH: ${{ steps.vars.outputs.azureVmgsPath }} + - name: Upload QEMU image + shell: bash + if: ${{ matrix.csp == 'qemu' }} + run: | + echo "::group::Upload QEMU image" + upload/upload_qemu.sh + echo -e "Uploaded QEMU image: \n\n\`\`\`\n$(jq < "${QEMU_JSON_OUTPUT}")\n\`\`\`\n" >> "$GITHUB_STEP_SUMMARY" + echo "::endgroup::" + working-directory: ${{ github.workspace }}/image + env: + QEMU_JSON_OUTPUT: ${{ steps.vars.outputs.qemuJsonOutput }} + QEMU_BUCKET: ${{ steps.vars.outputs.qemuBucket }} + QEMU_BASE_URL: ${{ steps.vars.outputs.qemuBaseUrl }} + QEMU_IMAGE_PATH: ${{ steps.vars.outputs.qemuImagePath }} + IMAGE_VERSION_UID: ${{ needs.build-settings.outputs.imageVersionUid }} + + - name: Upload image lookup table as artifact + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3.1.1 + with: + name: lookup-table + path: ${{ github.workspace }}/image/mkosi.output.*/*/image-upload*.json + calculate-pcrs: name: "Calculate PCRs" - needs: [make-os-image] + needs: [build-settings, make-os-image] + permissions: + id-token: write + contents: read runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -414,6 +457,12 @@ jobs: with: ref: ${{ github.head_ref }} + - name: Login to AWS + uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # tag=v1.7.0 + with: + role-to-assume: arn:aws:iam::795746500882:role/GitHubConstellationImagePipeline + aws-region: eu-central-1 + - name: Download OS image artifact uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # tag=v3.0.1 with: @@ -436,7 +485,7 @@ jobs: ./precalculate_pcr_9.sh ${{ github.workspace }}/image.raw ${{ github.workspace }}/pcr-9-${{ matrix.csp }}.json } >> "$GITHUB_STEP_SUMMARY" cp pcr-stable.json ${{ github.workspace }}/ - jq --sort-keys -s '.[0] * .[1] * .[2] * .[3]' ${{ github.workspace }}/pcr-* > ${{ github.workspace }}/pcrs-${{ matrix.csp }}.json + jq -sSc '.[0] * .[1] * .[2] * .[3]' ${{ github.workspace }}/pcr-* > ${{ github.workspace }}/pcrs-${{ matrix.csp }}.json echo "::endgroup::" working-directory: ${{ github.workspace }}/image/measured-boot @@ -446,11 +495,28 @@ jobs: name: pcrs path: pcrs-${{ matrix.csp }}.json + - name: Upload expected PCRs to S3 + shell: bash + run: | + aws s3 cp \ + "pcrs-${{ matrix.csp }}.json" \ + "s3://cdn-confidential-cloud-backend/v1/measurements/${{ needs.build-settings.outputs.imageVersionUid }}/${{ matrix.csp }}/measurements.image.json" \ + --no-progress + generate-sbom: name: "Generate SBOM" - needs: [build-dependencies, make-os-image] + needs: [build-settings, build-dependencies, make-os-image] + permissions: + id-token: write + contents: read runs-on: ubuntu-22.04 steps: + - name: Login to AWS + uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # tag=v1.7.0 + with: + role-to-assume: arn:aws:iam::795746500882:role/GitHubConstellationImagePipeline + aws-region: eu-central-1 + - name: Install squashfs tools run: | echo "::group::Install squashfs tools" @@ -465,6 +531,13 @@ jobs: # since the images only differ in the ESP partition name: parts-qemu + - name: Download manifest + uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # tag=v3.0.1 + with: + # downloading / using only the QEMU manifest is fine + # since the images only differ in the ESP partition + name: manifest-qemu + - name: Unpack squashfs run: | echo "::group::Unpack squashfs" @@ -475,18 +548,21 @@ jobs: with: path: image.root.tree artifact-name: sbom.spdx.json + output-file: sbom.spdx.json format: spdx-json - uses: anchore/sbom-action@06e109483e6aa305a2b2395eabae554e51530e1d # tag=v0.13.1 with: path: image.root.tree artifact-name: sbom.cyclonedx.json + output-file: sbom.cyclonedx.json format: cyclonedx-json - uses: anchore/sbom-action@06e109483e6aa305a2b2395eabae554e51530e1d # tag=v0.13.1 with: path: image.root.tree artifact-name: sbom.syft.json + output-file: sbom.syft.json format: syft-json - name: Combine hashes @@ -529,3 +605,55 @@ jobs: EOF cat SHA256SUMS echo -e "SHA256SUMS:\n\`\`\`\n$(cat SHA256SUMS)\n\`\`\`" >> "$GITHUB_STEP_SUMMARY" + + - name: Upload SBOMs to S3 + shell: bash + run: | + sboms='sbom.spdx.json sbom.cyclonedx.json sbom.syft.json' + manifests='image.raw.manifest image.raw.changelog' + hashes='SHA256SUMS' + for file in ${sboms} ${manifests} ${hashes}; do + aws s3 cp \ + "${file}" \ + "s3://cdn-confidential-cloud-backend/v1/sbom/${{ needs.build-settings.outputs.imageVersionUid }}/${file}" \ + --no-progress + done + + upload-image-lookup-table: + name: "Upload image lookup table" + runs-on: ubuntu-22.04 + needs: [build-settings, upload-os-image] + permissions: + id-token: write + contents: read + steps: + - name: Download image lookup table + uses: actions/download-artifact@v2 + with: + name: lookup-table + + - name: Combine lookup tables for CSPs + shell: bash + run: | + echo '{}' > intermediate.json + for lut in mkosi.output.*/*/image-upload*.json; do + jq -scS '.[0] * .[1]' intermediate.json "${lut}" > lookup-table.json + cp lookup-table.json intermediate.json + done + rm -f intermediate.json + mv lookup-table.json "${{ needs.build-settings.outputs.imageVersionUid }}.json" + + - name: Login to AWS + uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # tag=v1.7.0 + with: + role-to-assume: arn:aws:iam::795746500882:role/GitHubConstellationImagePipeline + aws-region: eu-central-1 + + - name: Upload lookup table to S3 + shell: bash + run: | + aws s3 cp \ + "${{ needs.build-settings.outputs.imageVersionUid }}.json" \ + "s3://cdn-confidential-cloud-backend/v1/images/${{ needs.build-settings.outputs.imageVersionUid }}.json" \ + --no-progress + echo -e "- [Lookup table](https://cdn.confidential.cloud/v1/images/${{ needs.build-settings.outputs.imageVersionUid }}.json)" >> "$GITHUB_STEP_SUMMARY" diff --git a/image/.gitignore b/image/.gitignore index e4edbc530..406a41448 100644 --- a/image/.gitignore +++ b/image/.gitignore @@ -3,3 +3,5 @@ mkosi.extra pki image.* mkosi.output.* +pki_*/*.key +pki_*/*.vmgs diff --git a/image/Makefile b/image/Makefile index 4268f06b3..c17b6da0e 100644 --- a/image/Makefile +++ b/image/Makefile @@ -5,6 +5,7 @@ BOOTSTRAPPER_BINARY ?= $(BASE_PATH)/../build/bootstrapper DISK_MAPPER_BINARY ?= $(BASE_PATH)/../build/disk-mapper PKI ?= $(BASE_PATH)/pki MKOSI_EXTRA ?= $(BASE_PATH)/mkosi.extra +IMAGE_VERSION ?= v0.0.0 -include $(CURDIR)/config.mk csps := aws qemu gcp azure certs := $(PKI)/PK.cer $(PKI)/KEK.cer $(PKI)/db.cer @@ -16,7 +17,7 @@ all: $(csps) $(csps): %: mkosi.output.%/fedora~36/image.raw mkosi.output.%/fedora~36/image.raw: mkosi.files/mkosi.%.conf inject-bins inject-certs - mkosi --config mkosi.files/mkosi.$*.conf build + mkosi --config mkosi.files/mkosi.$*.conf --image-version=$(IMAGE_VERSION) build secure-boot/signed-shim.sh $@ @if [ -n $(SUDO_UID) ] && [ -n $(SUDO_GID) ]; then \ chown -R $(SUDO_UID):$(SUDO_GID) mkosi.output.$*; \ diff --git a/image/README.md b/image/README.md index ff274fb85..73fef42be 100644 --- a/image/README.md +++ b/image/README.md @@ -84,7 +84,7 @@ The generated images are partially signed by Microsoft ([shim loader](https://gi For QEMU and Azure, you can pre-generate the NVRAM variables for secure boot. This is not necessary for GCP, as you can specify secure boot parameters via the GCP API on image creation.
-libvirt / QEMU / KVM +libvirt / QEMU / KVM ```sh secure-boot/generate_nvram_vars.sh mkosi.output.qemu/fedora~36/image.raw @@ -112,6 +112,7 @@ export AZURE_SNAPSHOT_NAME=${AZURE_DISK_NAME} export AZURE_RAW_IMAGE_PATH=${PWD}/mkosi.output.azure/fedora~36/image.raw export AZURE_IMAGE_PATH=${PWD}/mkosi.output.azure/fedora~36/image.vhd export AZURE_VMGS_FILENAME=${AZURE_SECURITY_TYPE}.vmgs +export AZURE_JSON_OUTPUT=${PWD}/mkosi.output.azure/fedora~36/image-upload.json export BLOBS_DIR=${PWD}/blobs upload/pack.sh azure "${AZURE_RAW_IMAGE_PATH}" "${AZURE_IMAGE_PATH}" upload/upload_azure.sh --disk-name "${AZURE_DISK_NAME}-setup-secure-boot" "" @@ -158,9 +159,9 @@ export AWS_BUCKET=constellation-images export AWS_EFIVARS_PATH=${PWD}/mkosi.output.aws/fedora~36/efivars.bin export AWS_IMAGE_PATH=${PWD}/mkosi.output.aws/fedora~36/image.raw export AWS_IMAGE_FILENAME=image-$(date +%s).raw -export AWS_AMI_OUTPUT=${PWD}/mkosi.output.aws/fedora~36/ami.txt +export AWS_JSON_OUTPUT=${PWD}/mkosi.output.aws/fedora~36/image-upload.json secure-boot/aws/create_uefivars.sh "${AWS_EFIVARS_PATH}" -upload/upload_aws.sh "${AWS_AMI_OUTPUT}" +upload/upload_aws.sh ```
@@ -187,6 +188,7 @@ export GCP_BUCKET=constellation-images export GCP_RAW_IMAGE_PATH=${PWD}/mkosi.output.gcp/fedora~36/image.raw export GCP_IMAGE_FILENAME=$(date +%s).tar.gz export GCP_IMAGE_PATH=${PWD}/mkosi.output.gcp/fedora~36/image.tar.gz +export GCP_JSON_OUTPUT=${PWD}/mkosi.output.gcp/fedora~36/image-upload.json upload/pack.sh gcp ${GCP_RAW_IMAGE_PATH} ${GCP_IMAGE_PATH} upload/upload_gcp.sh ``` @@ -228,8 +230,27 @@ export AZURE_PUBLISHER=edgelesssys export AZURE_DISK_NAME=constellation-$(date +%s) export AZURE_RAW_IMAGE_PATH=${PWD}/mkosi.output.azure/fedora~36/image.raw export AZURE_IMAGE_PATH=${PWD}/mkosi.output.azure/fedora~36/image.vhd +export AZURE_JSON_OUTPUT=${PWD}/mkosi.output.azure/fedora~36/image-upload.json upload/pack.sh azure "${AZURE_RAW_IMAGE_PATH}" "${AZURE_IMAGE_PATH}" upload/upload_azure.sh -g --disk-name "${AZURE_DISK_NAME}" "${AZURE_VMGS_PATH}" ``` + +
+QEMU + +- Install `aws` cli (see [here](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)) +- Login to AWS (see [here](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html)) + +```sh +# set these variables +export IMAGE_VERSION_UID= # e.g. "test123" or "v2.1.0" +export QEMU_BUCKET=cdn-confidential-cloud-backend +export QEMU_BASE_URL="https://cdn.confidential.cloud" +export QEMU_IMAGE_PATH=${PWD}/mkosi.output.qemu/fedora~36/image.raw +export QEMU_JSON_OUTPUT=${PWD}/mkosi.output.qemu/fedora~36/image-upload.json +upload/upload_qemu.sh +``` + +
diff --git a/image/measured-boot/pcr-stable.json b/image/measured-boot/pcr-stable.json index 48ba33120..89d3ba0d6 100755 --- a/image/measured-boot/pcr-stable.json +++ b/image/measured-boot/pcr-stable.json @@ -1,6 +1,8 @@ { - "pcr11": "0000000000000000000000000000000000000000000000000000000000000000", - "pcr12": "0000000000000000000000000000000000000000000000000000000000000000", - "pcr13": "0000000000000000000000000000000000000000000000000000000000000000", - "pcr15": "0000000000000000000000000000000000000000000000000000000000000000" + "measurements": { + "11": "0000000000000000000000000000000000000000000000000000000000000000", + "12": "0000000000000000000000000000000000000000000000000000000000000000", + "13": "0000000000000000000000000000000000000000000000000000000000000000", + "15": "0000000000000000000000000000000000000000000000000000000000000000" + } } diff --git a/image/measured-boot/precalculate_pcr_4.sh b/image/measured-boot/precalculate_pcr_4.sh index 3041a38c1..b5afa7a2d 100755 --- a/image/measured-boot/precalculate_pcr_4.sh +++ b/image/measured-boot/precalculate_pcr_4.sh @@ -22,21 +22,23 @@ write_output() { local out="$1" cat > "${out}" << EOF { - "pcr4": "${expected_pcr_4}", - "efistages": [ - { - "name": "shim", - "sha256": "${shim_authentihash}" - }, - { - "name": "systemd-boot", - "sha256": "${sd_boot_authentihash}" - }, - { - "name": "uki", - "sha256": "${uki_authentihash}" - } - ] + "measurements": { + "4": "${expected_pcr_4}" + }, + "efistages": [ + { + "name": "shim", + "sha256": "${shim_authentihash}" + }, + { + "name": "systemd-boot", + "sha256": "${sd_boot_authentihash}" + }, + { + "name": "uki", + "sha256": "${uki_authentihash}" + } + ] } EOF } diff --git a/image/measured-boot/precalculate_pcr_8.sh b/image/measured-boot/precalculate_pcr_8.sh index 8f8a8c9a7..0011c240f 100755 --- a/image/measured-boot/precalculate_pcr_8.sh +++ b/image/measured-boot/precalculate_pcr_8.sh @@ -7,7 +7,7 @@ # PCR[8] contains the hash of the kernel command line and is measured by systemd-boot. # This value is deprecated and will be moved to PCR[12] in the future. # This script may produce wrong results after 2023 (when the kernel command line is only measured in PCR[12]). -# Usage: precalculate_pcr_8.sh +# Usage: precalculate_pcr_8.sh set -euo pipefail shopt -s inherit_errexit @@ -34,8 +34,11 @@ write_output() { local out="$1" cat > "${out}" << EOF { - "pcr8": "${expected_pcr_8}", - "cmdline": "${cmdline}" + "measurements": { + "8": "${expected_pcr_8}" + }, + "cmdline": "${cmdline}", + "cmdline-sha256": "${cmdline_hash}" } EOF } diff --git a/image/measured-boot/precalculate_pcr_9.sh b/image/measured-boot/precalculate_pcr_9.sh index d2ceb6fcc..19dcf6cf1 100755 --- a/image/measured-boot/precalculate_pcr_9.sh +++ b/image/measured-boot/precalculate_pcr_9.sh @@ -27,8 +27,10 @@ write_output() { local out="$1" cat > "${out}" << EOF { - "pcr9": "${expected_pcr_9}", - "initrd": "${initrd_hash}" + "measurements": { + "9": "${expected_pcr_9}" + }, + "initrd-sha256": "${initrd_hash}" } EOF } diff --git a/image/mkosi.conf.d/mkosi.conf b/image/mkosi.conf.d/mkosi.conf index de8f304cf..1b9ab375c 100644 --- a/image/mkosi.conf.d/mkosi.conf +++ b/image/mkosi.conf.d/mkosi.conf @@ -17,6 +17,8 @@ SecureBootKey=pki/db.key SecureBootCertificate=pki/db.crt # TODO: Wait for systemd 252 to bring systemd-measure # Measure=yes +ImageId=constellation +Output=image.raw [Host] QemuHeadless=yes diff --git a/image/mkosi.postinst b/image/mkosi.postinst index ae075b686..83d19c8b1 100755 --- a/image/mkosi.postinst +++ b/image/mkosi.postinst @@ -20,3 +20,7 @@ mkdir -p /etc/{cni,kubernetes} mv /etc/issue.d /usr/lib/issue.d || true rm -f /etc/issue rm -f /etc/issue.net + +# update /etc/os-release +echo "IMAGE_ID=\"${IMAGE_ID}\"" >> /etc/os-release +echo "IMAGE_VERSION=\"${IMAGE_VERSION}\"" >> /etc/os-release diff --git a/image/upload/upload_aws.sh b/image/upload/upload_aws.sh index cf5b50631..406355c7d 100755 --- a/image/upload/upload_aws.sh +++ b/image/upload/upload_aws.sh @@ -13,7 +13,6 @@ fi CONTAINERS_JSON=$(mktemp /tmp/containers-XXXXXXXXXXXXXX.json) declare -A AMI_FOR_REGION -AMI_OUTPUT=$1 import_status() { local import_task_id=$1 @@ -160,8 +159,17 @@ for region in ${AWS_REPLICATION_REGIONS}; do tag_ami_with_backing_snapshot "${AMI_FOR_REGION[${region}]}" "${region}" make_ami_public "${AMI_FOR_REGION[${region}]}" "${region}" done -echo -n "{\"${AWS_REGION}\": \"${AMI_FOR_REGION[${AWS_REGION}]}\"" > "${AMI_OUTPUT}" + +json=$(jq -ncS \ + --arg region "${AWS_REGION}" \ + --arg ami "${AMI_FOR_REGION[${AWS_REGION}]}" \ + '{"aws":{($region): $ami}}') for region in ${AWS_REPLICATION_REGIONS}; do - echo -n ", \"${region}\": \"${AMI_FOR_REGION[${region}]}\"" >> "${AMI_OUTPUT}" + json=$(jq -ncS \ + --argjson json "${json}" \ + --arg region "${region}" \ + --arg ami "${AMI_FOR_REGION[${region}]}" \ + '$json * {"aws": {($region): $ami}}') done -echo "}" >> "${AMI_OUTPUT}" + +echo "${json}" > "${AWS_JSON_OUTPUT}" diff --git a/image/upload/upload_azure.sh b/image/upload/upload_azure.sh index 9c43d8c47..5f696ad3e 100755 --- a/image/upload/upload_azure.sh +++ b/image/upload/upload_azure.sh @@ -41,10 +41,13 @@ set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters if [[ ${AZURE_SECURITY_TYPE} == "ConfidentialVM" ]]; then AZURE_DISK_SECURITY_TYPE=ConfidentialVM_VMGuestStateOnlyEncryptedWithPlatformKey AZURE_SIG_VERSION_ENCRYPTION_TYPE=EncryptedVMGuestStateOnlyWithPmk + security_type_short_name="cvm" elif [[ ${AZURE_SECURITY_TYPE} == "ConfidentialVMSupported" ]]; then AZURE_DISK_SECURITY_TYPE="" + security_type_short_name="cvm" elif [[ ${AZURE_SECURITY_TYPE} == "TrustedLaunch" ]]; then AZURE_DISK_SECURITY_TYPE=TrustedLaunch + security_type_short_name="trustedlaunch" else echo "Unknown security type: ${AZURE_SECURITY_TYPE}" exit 1 @@ -188,6 +191,43 @@ create_sig_version() { ${SOURCE} } +get_image_version_reference() { + local is_community_gallery + is_community_gallery=$(az sig show --gallery-name "${AZURE_GALLERY_NAME}" \ + --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ + --query 'sharingProfile.communityGalleryInfo.communityGalleryEnabled' \ + -o tsv) + if [[ ${is_community_gallery} == "true" ]]; then + get_community_image_version_reference + return + fi + get_unshared_image_version_reference +} + +get_community_image_version_reference() { + local communityGalleryName + communityGalleryName=$(az sig show --gallery-name "${AZURE_GALLERY_NAME}" \ + --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ + --query 'sharingProfile.communityGalleryInfo.publicNames[0]' \ + -o tsv) + az sig image-version show-community \ + --public-gallery-name "${communityGalleryName}" \ + --gallery-image-definition "${AZURE_IMAGE_DEFINITION}" \ + --gallery-image-version "${AZURE_IMAGE_VERSION}" \ + --location "${AZURE_REGION}" \ + --query 'uniqueId' \ + -o tsv +} + +get_unshared_image_version_reference() { + az sig image-version show \ + --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ + --gallery-name "${AZURE_GALLERY_NAME}" \ + --gallery-image-definition "${AZURE_IMAGE_DEFINITION}" \ + --gallery-image-version "${AZURE_IMAGE_VERSION}" \ + --query id --output tsv +} + create_disk if [[ ${CREATE_SIG_VERSION} == "YES" ]]; then @@ -196,3 +236,10 @@ if [[ ${CREATE_SIG_VERSION} == "YES" ]]; then delete_image delete_disk fi + +image_reference=$(get_image_version_reference) +json=$(jq -ncS \ + --arg security_type "${security_type_short_name}" \ + --arg image_reference "${image_reference}" \ + '{"azure": {($security_type): $image_reference}}') +echo -n "${json}" > "${AZURE_JSON_OUTPUT}" diff --git a/image/upload/upload_gcp.sh b/image/upload/upload_gcp.sh index 2c267619c..d02394971 100755 --- a/image/upload/upload_gcp.sh +++ b/image/upload/upload_gcp.sh @@ -31,3 +31,11 @@ gcloud compute images add-iam-policy-binding "${GCP_IMAGE_NAME}" \ --member='allAuthenticatedUsers' \ --role='roles/compute.imageUser' gsutil rm "gs://${GCP_BUCKET}/${GCP_IMAGE_FILENAME}" + +image_reference=$(gcloud compute images describe "${GCP_IMAGE_NAME}" \ + --project "${GCP_PROJECT}" \ + '--format=value(selfLink.scope(v1))') +json=$(jq -ncS \ + --arg image_reference "${image_reference}" \ + '{"gcp": {"sev-es": $image_reference}}') +echo -n "${json}" > "${GCP_JSON_OUTPUT}" diff --git a/image/upload/upload_qemu.sh b/image/upload/upload_qemu.sh new file mode 100755 index 000000000..505f47d17 --- /dev/null +++ b/image/upload/upload_qemu.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Copyright (c) Edgeless Systems GmbH +# +# SPDX-License-Identifier: AGPL-3.0-only + +set -euo pipefail +shopt -s inherit_errexit + +if [[ -z ${CONFIG_FILE-} ]] && [[ -f ${CONFIG_FILE-} ]]; then + # shellcheck source=/dev/null + . "${CONFIG_FILE}" +fi + +aws s3 cp "${QEMU_IMAGE_PATH}" "s3://${QEMU_BUCKET}/v1/raw/${IMAGE_VERSION_UID}/qemu/image.raw" --no-progress + +image_url="${QEMU_BASE_URL}/v1/raw/${IMAGE_VERSION_UID}/qemu/image.raw" + +json=$(jq -ncS \ + --arg image_url "${image_url}" \ + '{"qemu": {"default": $image_url}}') +echo -n "${json}" > "${QEMU_JSON_OUTPUT}" diff --git a/rfc/image-discoverability.md b/rfc/image-discoverability.md index e37419505..5aa6278a0 100644 --- a/rfc/image-discoverability.md +++ b/rfc/image-discoverability.md @@ -42,12 +42,13 @@ The build pipeline takes as inputs: - a version number that is one of - a release version number (e.g. `v2.2.0`) for release images - - a pseudo-version number (e.g. `v2.3.0-pre-debug`) for development images - - a pseudo-version number (e.g. `v2.3.0-pre-branch-name`) for branch images + - a pseudo-version number (e.g. `debug-v2.3.0-pre.0.`) for development images + - a pseudo-version number (e.g. `branch-name-v2.3.0-pre.0.`) for branch images - a commit hash of the Constellation monorepo that is used to build the images (e.g. `cc0de5c68d41f31dd0b284d574f137e0b0ad106b`) +- a commit timestamp of the Constellation monorepo that is used to build the images (e.g. `20221115082220`) To identify images belonging to one invocation of the build pipeline, the pipeline uses a unique identifier for the set of images, referred to as `image version uid`. -This is either the release version number (e.g. `v2.2.0`) or a pseudo version that combines the version number and the commit hash (e.g. `v2.3.0-pre-debug-cc0de5c68d41f31dd0b284d574f137e0b0ad106b`). +This is either the release version number (e.g. `v2.2.0`) or a pseudo version that combines the version number, commit timestamp and the commit hash (e.g. `debug-v2.3.0-pre.0.20221115082220-cc0de5c68d41f31dd0b284d574f137e0b0ad106b`). The build pipeline produces as outputs: @@ -59,12 +60,34 @@ The build pipeline produces as outputs: The lookup table is uploaded to S3 and is used to identify the images that belong to a given `image version uid`. Measurements are uploaded to S3 and can be looked up for each cloud service provider and `image version uid`. +## Image API + +The build pipeline produces artifacts that are uploaded to S3 and can be accessed via HTTP. +The artifacts are organized in a directory structure that allows to look up the artifacts for a given `image version uid`. + +Where applicable, the API uses the following CSP names: + +- `aws` for Amazon Web Services +- `azure` for Microsoft Azure +- `gcp` for Google Cloud Platform +- `qemu` for QEMU + +The following HTTP endpoints are available: + +- `GET /v1/images/.json` returns the lookup table for the given `image version uid`. +- `GET /v1/measurements///` contains files with measurements and signatures for the given `image version uid` and CSP. + - `measurements.json` contains the final measurements for the given `image version uid` and CSP. + - `measurements.json.sig` returns the signature of the measurements file. + - `measurements.image.json` returns the measurements generated statically from the image. +- `GET /v1/raw///image.raw` returns the raw image for the given `image version uid` and CSP. +- `GET /v1/sbom//` contains SBOM files for the given `image version uid`. The exact formats and file names are TBD. + ## Image lookup table The image lookup table is a JSON file that maps the `image version uid` to the CSP-specific image references. It uses the `image version uid` as file name. ``` -s3:///images/.json +s3:///v1/images/.json ``` ```json @@ -80,8 +103,10 @@ s3:///images/.json }, "gcp": { "sev-es": "gcp-image-123" + }, + "qemu": { + "default": "https://cdn.confidential.cloud/v1/raw/v2.2.0/qemu/image.raw" } - "qemu": "https://cdn.edgeless.systems/.../qemu.raw" } ``` @@ -101,8 +126,9 @@ The format of the image measurements is described in the [secure software distri The image measurements are stored in a folder structure in S3 that is organized by CSP and `image version uid`. ``` -s3:///measurements///measurements.yaml -s3:///measurements///measurements.yaml.sig +s3:///v1/measurements///measurements.json +s3:///v1/measurements///measurements.json.sig +s3:///v1/measurements///measurements.image.json ``` ## CLI image discovery @@ -117,11 +143,12 @@ The `image` field is independent of the CSP and is a used to discover the CSP-sp The CLI can find a CSP- and region specific image reference by looking up the `image version uid` in the following order: - if a local file `.json` exists, use the lookup table in that file -- otherwise, load the image lookup table from a well known URL (e.g. `https://cdn.confidential.cloud/images/.json`) and use the lookup table in that file +- otherwise, load the image lookup table from a well known URL (e.g. `https://cdn.confidential.cloud/v1/images/.json`) and use the lookup table in that file - choose the CSP-specific image reference for the current region and security type: - On AWS, use the AMI ID for the current region (e.g. `.aws.us-east-1`) - On Azure, use the image ID for the security type (CVM or Trusted Launch) (e.g. `.azure.cvm`) - - On GCP, use the only image ID (e.g. `.gcp`) + - On GCP, use the only image ID (e.g. `.gcp.sev-es`) + - On QEMU, use the only image ID (e.g. `.qemu.default`) This allows customers to upload images to their own cloud subscription and use them with the CLI by providing the image lookup table as a local file.