From 27549b7a54c0da471ed5b38c05662d5995753dfb Mon Sep 17 00:00:00 2001 From: Alex Ashley Date: Thu, 21 Mar 2024 15:50:45 -0400 Subject: [PATCH] feat: sign image before pushing to registry (#127) Closes #111 --- .github/workflows/build-and-push.yaml | 171 +++++++++++++------------- 1 file changed, 88 insertions(+), 83 deletions(-) diff --git a/.github/workflows/build-and-push.yaml b/.github/workflows/build-and-push.yaml index 500d40a..c11cbb7 100644 --- a/.github/workflows/build-and-push.yaml +++ b/.github/workflows/build-and-push.yaml @@ -10,7 +10,7 @@ on: outputs: digest: description: "Docker image digest" - value: ${{ jobs.push.outputs.digest }} + value: ${{ jobs.sign.outputs.digest }} jobs: detect-workflow: @@ -65,16 +65,26 @@ jobs: name: image path: /tmp/image.tar - push: + sign: permissions: - packages: write # required to push the image to GHCR + id-token: write # required to sign image + contents: read # for reading local TUF root.json file + packages: write # required to upload signature to GHCR runs-on: ubuntu-latest needs: - build - detect-workflow outputs: digest: ${{ steps.digest.outputs.digest }} + defaults: + run: + working-directory: ./app steps: + - name: Checkout App Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + with: + path: app + - name: Checkout Workflows Repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: @@ -102,6 +112,14 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Cosign Setup + uses: ./gh-trusted-builds-workflows/.github/actions/cosign + with: + tufRoot: ${{ steps.config.outputs.tufRoot }} + tufMirror: ${{ steps.config.outputs.tufMirror }} + version: ${{ steps.config.outputs.cosignVersion }} + workingDirectory: ./gh-trusted-builds-workflows + - name: Install Crane env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -122,72 +140,26 @@ jobs: - name: Compute Digest id: digest run: | - localDigest=$(crane digest --tarball /tmp/image.tar) - echo "locally-computed digest: ${localDigest}" - echo "digest=${localDigest}" >> $GITHUB_OUTPUT - - - name: Push Image - id: push - env: - IMAGE_METADATA: ${{ needs.build.outputs.metadata }} - run: | - commit="${{ github.sha }}" - pushedImage=$(printf "${IMAGE_METADATA}" | jq --arg commit "${commit::7}" -r '.tags | .[] | select(.|endswith($commit))') - echo "Pushing ${pushedImage}" - crane push /tmp/image.tar "${pushedImage}" - echo "image=${pushedImage}" >> $GITHUB_OUTPUT + echo "digest=$(crane digest --tarball /tmp/image.tar)" >> $GITHUB_OUTPUT - - name: Compare Digests - run: | - pushedImage=${{ steps.push.outputs.image }} - registryDigest=$(crane digest "${pushedImage}") - localDigest=${{ steps.digest.outputs.digest }} - - if [[ "${localDigest}" == "${registryDigest}" ]]; then - echo "Remote digest matches digest from local registry" - else - echo "Digest in registry doesn't match expected digest" - echo "Locally-computed digest: ${localDigest}" - echo "Digest from registry: ${registryDigest}" - echo "This may indicate the image was modified by the registry" - exit 1 - fi - - - name: Add Tags - env: - IMAGE_METADATA: ${{ needs.build.outputs.metadata }} + - name: Sign run: | - pushedImage=${{ steps.push.outputs.image }} - - # add the remaining tags, skipping the tag that was pushed initially - printf "${IMAGE_METADATA}" | jq -rc '.tags | .[]' | while read taggedImage; do - if [[ "${taggedImage}" == "${pushedImage}" ]]; then - continue - fi - - tag=$(printf "${taggedImage}" | awk -F "${image}:" '{print $2}') - echo "adding tag ${tag}" - crane tag "${pushedImage}" "${tag}" - done + cosign sign \ + --annotations liatr.io/github-actions-run-link='${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' \ + --annotations liatr.io/signed-off-by=platform-team \ + --rekor-url ${{ steps.config.outputs.rekorUrl }} \ + --fulcio-url ${{ steps.config.outputs.fulcioUrl }} \ + --yes ghcr.io/${{ github.repository }}@${{ steps.digest.outputs.digest }} - sign: + push: permissions: - id-token: write # required to sign image - contents: read # for reading local TUF root.json file - packages: write # required to upload signature to GHCR + packages: write # required to push the image to GHCR runs-on: ubuntu-latest needs: - - push + - build + - sign - detect-workflow - defaults: - run: - working-directory: ./app steps: - - name: Checkout App Repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - with: - path: app - - name: Checkout Workflows Repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: @@ -202,13 +174,11 @@ jobs: with: environment: ${{ inputs.environment }} - - name: Cosign Setup - uses: ./gh-trusted-builds-workflows/.github/actions/cosign + - name: Download Image Archive + uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4 with: - tufRoot: ${{ steps.config.outputs.tufRoot }} - tufMirror: ${{ steps.config.outputs.tufMirror }} - version: ${{ steps.config.outputs.cosignVersion }} - workingDirectory: ./gh-trusted-builds-workflows + name: image + path: /tmp - name: Login to GitHub Container Registry uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3 @@ -217,15 +187,50 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Sign + - name: Install Crane + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cosign sign \ - --annotations liatr.io/github-actions-run-link='${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' \ - --annotations liatr.io/signed-off-by=platform-team \ - --rekor-url ${{ steps.config.outputs.rekorUrl }} \ - --fulcio-url ${{ steps.config.outputs.fulcioUrl }} \ - --yes ghcr.io/${{ github.repository }}@${{ needs.push.outputs.digest }} + cd $(mktemp -d) + + gh release download \ + --repo google/go-containerregistry \ + --pattern "go-containerregistry_Linux_x86_64.tar.gz" "${{ steps.config.outputs.craneVersion }}" + + tar xvf go-containerregistry_Linux_x86_64.tar.gz + mkdir -p $HOME/.bin/crane + mv ./crane $HOME/.bin/crane + echo "$HOME/.bin/crane" >> $GITHUB_PATH + $HOME/.bin/crane/crane version + + - name: Push Image + id: push + env: + IMAGE_METADATA: ${{ needs.build.outputs.metadata }} + run: | + commit="${{ github.sha }}" + pushedImage=$(printf "${IMAGE_METADATA}" | jq --arg commit "${commit::7}" -r '.tags | .[] | select(.|endswith($commit))') + echo "Pushing ${pushedImage}" + crane push /tmp/image.tar "${pushedImage}" + echo "image=${pushedImage}" >> $GITHUB_OUTPUT + + - name: Add Tags + env: + IMAGE_METADATA: ${{ needs.build.outputs.metadata }} + run: | + pushedImage=${{ steps.push.outputs.image }} + + # add the remaining tags, skipping the tag that was pushed initially + printf "${IMAGE_METADATA}" | jq -rc '.tags | .[]' | while read taggedImage; do + if [[ "${taggedImage}" == "${pushedImage}" ]]; then + continue + fi + + tag=$(printf "${taggedImage}" | awk -F "${image}:" '{print $2}') + echo "adding tag ${tag}" + crane tag "${pushedImage}" "${tag}" + done provenance: permissions: @@ -235,7 +240,7 @@ jobs: packages: write # required to upload attestations to GHCR runs-on: ubuntu-latest needs: - - push + - sign - detect-workflow defaults: run: @@ -303,7 +308,7 @@ jobs: --type slsaprovenance \ --fulcio-url ${{ steps.config.outputs.fulcioUrl }} \ --yes \ - ghcr.io/${{ github.repository }}@${{ needs.push.outputs.digest }} + ghcr.io/${{ github.repository }}@${{ needs.sign.outputs.digest }} sbom: @@ -313,7 +318,7 @@ jobs: packages: write # used to push attestations to GHCR runs-on: ubuntu-latest needs: - - push + - sign - detect-workflow - provenance # prevent race conditions when updating attestation tag defaults: @@ -363,14 +368,14 @@ jobs: SYFT_FILE_METADATA_CATALOGER_ENABLED: true SYFT_FILE_METADATA_DIGESTS: sha256 run: | - syft -o spdx-json --file sbom.spdx.json ghcr.io/${{ github.repository }}@${{ needs.push.outputs.digest }} + syft -o spdx-json --file sbom.spdx.json ghcr.io/${{ github.repository }}@${{ needs.sign.outputs.digest }} cosign attest --predicate="sbom.spdx.json" \ --rekor-url ${{ steps.config.outputs.rekorUrl }} \ --type spdxjson \ --fulcio-url ${{ steps.config.outputs.fulcioUrl }} \ --yes \ - ghcr.io/${{ github.repository }}@${{ needs.push.outputs.digest }} + ghcr.io/${{ github.repository }}@${{ needs.sign.outputs.digest }} rm sbom.spdx.json @@ -382,7 +387,7 @@ jobs: pull-requests: read # used to populate attestation fields runs-on: ubuntu-latest needs: - - push + - sign - detect-workflow - sbom # prevent race conditions when updating attestation tag defaults: @@ -435,7 +440,7 @@ jobs: run: | attestation github-pull-request \ --artifact-uri ghcr.io/${{ github.repository }} \ - --artifact-digest ${{ needs.push.outputs.digest }} \ + --artifact-digest ${{ needs.sign.outputs.digest }} \ --rekor-url ${{ steps.config.outputs.rekorUrl }} \ --fulcio-url ${{ steps.config.outputs.fulcioUrl }} @@ -443,11 +448,11 @@ jobs: permissions: {} runs-on: ubuntu-latest needs: - - push + - sign steps: - name: Create Results Metadata run: | - jq -n --arg digest ${{ needs.push.outputs.digest }} '{digest: $digest}' > /tmp/workflow-metadata.json + jq -n --arg digest ${{ needs.sign.outputs.digest }} '{digest: $digest}' > /tmp/workflow-metadata.json - name: Upload Workflow Metadata uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4