name: Generate and Upload Measurements on: workflow_dispatch: inputs: osImage: description: "Full name of OS image (CSP independent image version UID)." type: string required: true isDebugImage: description: "Is OS image a debug image?" type: boolean required: true signMeasurements: description: "Sign and upload the measurements?" type: boolean required: true jobs: calculate-measurements-on-csp: name: "Calculate Measurements on CSP" runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: provider: ["aws", "azure", "gcp"] permissions: id-token: write contents: read env: ARM_CLIENT_ID: ${{ secrets.AZURE_E2E_CLIENT_ID }} ARM_CLIENT_SECRET: ${{ secrets.AZURE_E2E_CLIENT_SECRET }} ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_E2E_SUBSCRIPTION_ID }} ARM_TENANT_ID: ${{ secrets.AZURE_E2E_TENANT_ID }} steps: - name: Check out repository uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 with: ref: ${{ github.head_ref }} - name: Check if image definition from build pipeline exists run: | wget -O /dev/null https://cdn.confidential.cloud/constellation/v1/images/${{ github.event.inputs.osImage }}.json shell: bash - name: Setup Go environment uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # tag=v3.3.1 with: go-version: "1.19.3" - name: Build hack/pcr-reader run: | go build . pwd >> "$GITHUB_PATH" working-directory: hack/pcr-reader shell: bash - name: Login to Azure if: matrix.provider == 'azure' uses: ./.github/actions/login_azure with: azure_credentials: ${{ secrets.AZURE_E2E_CREDENTIALS }} - name: Create Azure resource group if: matrix.provider == 'azure' id: az_resource_group_gen shell: bash run: | uuid=$(cat /proc/sys/kernel/random/uuid) name=e2e-test-${uuid%%-*} az group create --location northeurope --name "$name" --tags e2e echo "res_group_name=$name" >> "$GITHUB_OUTPUT" - name: Create Cluster in E2E Test environment id: create_cluster uses: ./.github/actions/e2e_test with: workerNodesCount: 1 controlNodesCount: 1 cloudProvider: ${{ matrix.provider }} gcpProject: ${{ secrets.GCP_E2E_PROJECT }} gcp_service_account_json: ${{ secrets.GCP_SERVICE_ACCOUNT }} gcpClusterServiceAccountKey: ${{ secrets.GCP_CLUSTER_SERVICE_ACCOUNT }} azureSubscription: ${{ secrets.AZURE_E2E_SUBSCRIPTION_ID }} azureTenant: ${{ secrets.AZURE_E2E_TENANT_ID }} azureClientID: ${{ secrets.AZURE_E2E_CLIENT_ID }} azureClientSecret: ${{ secrets.AZURE_E2E_CLIENT_SECRET }} azureUserAssignedIdentity: ${{ secrets.AZURE_E2E_USER_ASSIGNED_IDENTITY }} azureResourceGroup: ${{ steps.az_resource_group_gen.outputs.res_group_name }} osImage: ${{ github.event.inputs.osImage }} isDebugImage: ${{ github.event.inputs.isDebugImage }} test: "nop" - name: Fetch PCRs from running cluster run: | KUBECONFIG="${PWD}/constellation-admin.conf" kubectl rollout status ds/verification-service -n kube-system --timeout=3m CONSTELL_IP=$(jq -r ".ip" constellation-id.json) mkdir -p "${{ github.workspace }}/generated-measurements" pcr-reader -constell-ip "${CONSTELL_IP}" -format json -metadata -csp "${{ matrix.provider }}" -image "${{ github.event.inputs.osImage }}" > "${{ github.workspace }}/generated-measurements/measurements.json" echo "All PCRs of current boot:" cat "${{ github.workspace }}/generated-measurements/measurements.json" case ${CSP} in aws) yq e 'del(.measurements.[1,10,16,17,18,19,20,21,22,23])' -i "${{ github.workspace }}/generated-measurements/measurements.json" yq '.measurements.4.warnOnly = false | .measurements.8.warnOnly = false | .measurements.9.warnOnly = false | .measurements.11.warnOnly = false | .measurements.12.warnOnly = false | .measurements.13.warnOnly = false | .measurements.15.warnOnly = false | .measurements.15.expected = "0000000000000000000000000000000000000000000000000000000000000000"' \ -I 0 -o json -i "${{ github.workspace }}/generated-measurements/measurements.json" ;; azure) yq e 'del(.measurements.[0,6,10,16,17,18,19,20,21,22,23])' -I 0 -o json -i "${{ github.workspace }}/generated-measurements/measurements.json" yq '.measurements.4.warnOnly = false | .measurements.8.warnOnly = false | .measurements.9.warnOnly = false | .measurements.11.warnOnly = false | .measurements.12.warnOnly = false | .measurements.13.warnOnly = false | .measurements.15.warnOnly = false | .measurements.15.expected = "0000000000000000000000000000000000000000000000000000000000000000"' \ -I 0 -o json -i "${{ github.workspace }}/generated-measurements/measurements.json" ;; gcp) yq e 'del(.measurements.[16,17,18,19,20,21,22,23])' -I 0 -o json -i "${{ github.workspace }}/generated-measurements/measurements.json" yq '.measurements.0.warnOnly = false | .measurements.4.warnOnly = false | .measurements.8.warnOnly = false | .measurements.9.warnOnly = false | .measurements.11.warnOnly = false | .measurements.12.warnOnly = false | .measurements.13.warnOnly = false | .measurements.15.warnOnly = false | .measurements.15.expected = "0000000000000000000000000000000000000000000000000000000000000000"' \ -I 0 -o json -i "${{ github.workspace }}/generated-measurements/measurements.json" ;; *) echo "CSP case check failed!" exit 1 ;; esac echo "PCRs to be published after removing known variable ones:" cat "${{ github.workspace }}/generated-measurements/measurements.json" mv "${{ github.workspace }}/generated-measurements/measurements.json" "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json" shell: bash env: CSP: ${{ matrix.provider }} - name: Upload measurements as artifact uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3.1.1 with: name: measurements-${{ matrix.provider }}.json path: "${{ github.workspace }}/generated-measurements" - name: Always terminate cluster if: always() continue-on-error: true uses: ./.github/actions/constellation_destroy with: kubeconfig: ${{ steps.create_cluster.outputs.kubeconfig }} - name: Always destroy Azure resource group if: always() && matrix.provider == 'azure' shell: bash run: | az group delete \ --name ${{ steps.az_resource_group_gen.outputs.res_group_name }} \ --force-deletion-types Microsoft.Compute/virtualMachineScaleSets \ --force-deletion-types Microsoft.Compute/virtualMachines \ --no-wait \ --yes validate-measurements: name: "Validate Measurements" needs: [calculate-measurements-on-csp] runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: provider: ["aws", "azure", "gcp"] steps: - name: Check out repository uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 with: ref: ${{ github.head_ref }} - name: Setup Go environment uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # tag=v3.3.1 with: go-version: "1.19.3" - name: Build hack/pcr-compare run: | go build . pwd >> "$GITHUB_PATH" working-directory: hack/pcr-compare shell: bash - name: Download calculated measurements from artifact uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # tag=v3.1.1 with: name: measurements-${{ matrix.provider }}.json path: "${{ github.workspace }}/generated-measurements" - name: Download expected measurements from build pipeline for image run: | mkdir -p ${{ github.workspace }}/expected-measurements wget -O ${{ github.workspace }}/expected-measurements/measurements.image.json https://cdn.confidential.cloud/constellation/v1/measurements/${{ github.event.inputs.osImage }}/${{ matrix.provider }}/measurements.image.json cat ${{ github.workspace }}/expected-measurements/measurements.image.json shell: bash - name: Check if expected measurements == actual measurements from running cluster run: | pcr-compare ${{ github.workspace }}/expected-measurements/measurements.image.json ${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json shell: bash sign-measurements: name: "Sign Measurements" if: inputs.signMeasurements needs: [calculate-measurements-on-csp, validate-measurements] runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: provider: ["aws", "azure", "gcp"] permissions: id-token: write contents: read steps: - name: Install Cosign uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # tag=v2.8.1 - name: Install Rekor shell: bash run: | curl -sLO https://github.com/sigstore/rekor/releases/download/v0.12.0/rekor-cli-linux-amd64 sudo install rekor-cli-linux-amd64 /usr/local/bin/rekor-cli rm rekor-cli-linux-amd64 - name: Download measurements from artifact uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # tag=v3.0.1 with: name: measurements-${{ matrix.provider }}.json path: "${{ github.workspace }}/generated-measurements" - name: Sign measurements shell: bash env: COSIGN_PUBLIC_KEY: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PUBLIC_KEY || secrets.COSIGN_DEV_PUBLIC_KEY }} COSIGN_PRIVATE_KEY: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PRIVATE_KEY || secrets.COSIGN_DEV_PRIVATE_KEY }} COSIGN_PASSWORD: ${{ startsWith(github.ref, 'refs/heads/release/v') && secrets.COSIGN_PASSWORD || secrets.COSIGN_DEV_PASSWORD }} run: | echo "${COSIGN_PUBLIC_KEY}" > cosign.pub # Enabling experimental mode also publishes signature to Rekor COSIGN_EXPERIMENTAL=1 cosign sign-blob --key env://COSIGN_PRIVATE_KEY "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json" > "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json.sig" # Verify - As documentation & check # Local Signature (input: artifact, key, signature) cosign verify-blob --key cosign.pub --signature "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json.sig" "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json" # Transparency Log Signature (input: artifact, key) uuid=$(rekor-cli search --artifact "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json" | tail -n 1) sig=$(rekor-cli get --uuid="${uuid}" --format=json | jq -r .Body.HashedRekordObj.signature.content) cosign verify-blob --key cosign.pub --signature <(echo "${sig}") "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json" - name: Upload signed measurements as artifact uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3.1.1 with: name: measurements-${{ matrix.provider }}.json.sig path: "${{ github.workspace }}/generated-measurements" publish-measurements: name: "Publish Measurements" if: inputs.signMeasurements needs: [calculate-measurements-on-csp, validate-measurements, sign-measurements] runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: provider: ["aws", "azure", "gcp"] permissions: id-token: write contents: read steps: - name: Download measurements from artifact uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # tag=v3.0.1 with: name: measurements-${{ matrix.provider }}.json path: "${{ github.workspace }}/generated-measurements" - name: Download signature from artifact uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # tag=v3.0.1 with: name: measurements-${{ matrix.provider }}.json.sig path: "${{ github.workspace }}/generated-measurements" - 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 to S3 run: | S3_PATH=s3://cdn-constellation-backend/constellation/v1/measurements/${IMAGE_UID,,}/${{ matrix.provider }} aws s3 cp "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json" "${S3_PATH}/measurements.json" aws s3 cp "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json.sig" "${S3_PATH}/measurements.json.sig" shell: bash env: IMAGE_UID: ${{ inputs.osImage }} PUBLIC_BUCKET_NAME: ${{ secrets.PUBLIC_BUCKET_NAME }} CSP: ${{ matrix.provider }}