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.