diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09cd91c0..3e22b1eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,6 +120,12 @@ jobs: if: ((github.event_name == 'push' || github.event_name == 'schedule') && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) name: 5 push needs: [setup, build] + permissions: + attestations: write + contents: read + id-token: write + packages: write + statuses: write uses: ./.github/workflows/internal-push.yml with: sha: ${{ needs.setup.outputs.sha }} diff --git a/.github/workflows/internal-push.yml b/.github/workflows/internal-push.yml index bda89e07..354e5667 100644 --- a/.github/workflows/internal-push.yml +++ b/.github/workflows/internal-push.yml @@ -59,6 +59,8 @@ jobs: fail-fast: false name: 1 push (${{ matrix.tag }}, ${{ matrix.arch }}) permissions: + attestations: write + id-token: write packages: write statuses: write runs-on: ubuntu-latest @@ -87,6 +89,19 @@ jobs: run: > docker push ${{ secrets.registry }}/${{ inputs.registry_repo }}:${{ steps.tag.outputs.tag }} + - name: Image Digest + id: digest + if: github.ref == 'refs/heads/main' + run: | + set -o pipefail + digest="$(docker buildx imagetools inspect --format '{{json .Manifest}}' ${{ secrets.registry }}/${{ inputs.registry_repo }}:${{ steps.tag.outputs.tag }} | jq -r '.digest')" + echo "digest=$digest" | tee -a $GITHUB_OUTPUT + - name: Attest Image + if: github.ref == 'refs/heads/main' + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-name: ${{ secrets.registry }}/${{ inputs.registry_repo }} + subject-digest: ${{ steps.digest.outputs.digest }} - name: Post Status with Image Name uses: actions/github-script@v5 with: @@ -108,6 +123,8 @@ jobs: fail-fast: false name: 2 push manifest (${{ matrix.tag }}) permissions: + attestations: write + id-token: write packages: write statuses: write runs-on: ubuntu-latest @@ -137,6 +154,19 @@ jobs: run: > docker manifest push ${{ secrets.registry }}/${{ inputs.registry_repo }}:${{ steps.tag.outputs.tag }} + - name: Image Digest + id: digest + if: github.ref == 'refs/heads/main' + run: | + set -o pipefail + digest="$(docker buildx imagetools inspect --format '{{json .Manifest}}' ${{ secrets.registry }}/${{ inputs.registry_repo }}:${{ steps.tag.outputs.tag }} | jq -r '.digest')" + echo "digest=$digest" | tee -a $GITHUB_OUTPUT + - name: Attest Image + if: github.ref == 'refs/heads/main' + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-name: ${{ secrets.registry }}/${{ inputs.registry_repo }} + subject-digest: ${{ steps.digest.outputs.digest }} - uses: actions/github-script@v5 with: script: | diff --git a/README.md b/README.md index 19c0bdfb..c3be322e 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,25 @@ All tags are published as multiplatform images supporting architectures: - `linux/amd46` - `linux/arm64` +## Verifying Images + +Images are published with [build provenance attestations] signed by GitHub +Actions, recording the workflow and commit that built them. Verify an image with +the [`gh` CLI]: + +``` +gh attestation verify oci://docker.io/stellar/quickstart:latest --repo stellar/quickstart +``` + +The `gh` CLI must be authenticated, because the attestation is fetched from +GitHub rather than from the registry. + +Attestations are only available for images built after attestation support was +added to the build, and are not available for older images. + +[build provenance attestations]: https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds +[`gh` CLI]: https://cli.github.com + ## Usage To use this project successfully, you should first decide a few things: