From 2b16d112b258e4b51877501dd04290f7c0959620 Mon Sep 17 00:00:00 2001 From: Simon Harrer Date: Tue, 4 Aug 2026 10:26:04 +0200 Subject: [PATCH] Sign released Docker images with cosign Keyless signing via the release workflow's GitHub OIDC identity, so no key material has to be managed. The image digest is signed rather than the tags, which are mutable. The ECR mirror now uses `cosign copy` instead of `docker buildx imagetools create`, because imagetools copies the manifest only and would leave the signature and the SBOM/provenance attestations behind on Docker Hub. --- .github/workflows/release.yaml | 28 +++++++++++++++++++------ CHANGELOG.md | 1 + README.md | 2 ++ docs/docs/installation.md | 37 ++++++++++++++++++++++++++++++++++ docs/docs/release-notes.md | 6 +++++- 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9ba9cc2a4..066445faa 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -164,6 +164,9 @@ jobs: needs: - test - check_version + permissions: + contents: read + id-token: write # IMPORTANT: mandatory for keyless signing with cosign steps: - name: Checkout uses: actions/checkout@v4 @@ -195,6 +198,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push + id: build uses: docker/build-push-action@v5 with: context: . @@ -207,14 +211,23 @@ jobs: labels: ${{ steps.meta.outputs.labels }} sbom: true provenance: mode=max + - name: Install cosign + if: ${{ github.event_name != 'pull_request' }} + uses: sigstore/cosign-installer@v3 + - name: Sign the image with cosign (keyless) + if: ${{ github.event_name != 'pull_request' }} + env: + DIGEST: ${{ steps.build.outputs.digest }} + # Sign the immutable digest of the multi-arch index, not the mutable tags. + run: cosign sign --yes "datacontract/cli@${DIGEST}" push-to-ecr: runs-on: ubuntu-latest needs: - docker steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Install cosign + uses: sigstore/cosign-installer@v3 - name: Set up AWS CLI uses: aws-actions/configure-aws-credentials@v4 @@ -234,10 +247,13 @@ jobs: aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/s4e5k7s9 - name: Copy Multi-Arch Image from Docker Hub to AWS ECR + # `cosign copy` carries the signature and the SBOM/provenance attestations + # along with the image; `docker buildx imagetools create` would copy the + # manifest only and leave the signature behind on Docker Hub. run: | VERSION="${{ github.ref_name }}" VERSION="${VERSION#v}" # Strip 'v' prefix to match Docker Hub tag - docker buildx imagetools create \ - --tag public.ecr.aws/s4e5k7s9/datacontract-cli:latest \ - --tag public.ecr.aws/s4e5k7s9/datacontract-cli:${VERSION} \ - docker.io/datacontract/cli:${VERSION} + SOURCE="docker.io/datacontract/cli:${VERSION}" + TARGET="public.ecr.aws/s4e5k7s9/datacontract-cli" + cosign copy --force "${SOURCE}" "${TARGET}:${VERSION}" + cosign copy --force "${SOURCE}" "${TARGET}:latest" diff --git a/CHANGELOG.md b/CHANGELOG.md index 063123ccf..370498c5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This removes the JVM dependency, makes the images much, much smaller (Docker ima ### Added - `DATACONTRACT_KAFKA_MAX_MESSAGES` limits how many messages `datacontract test` reads from a topic, and `DATACONTRACT_KAFKA_TIMEOUT` how long it waits for one +- Released Docker images are signed with cosign keyless signing, on Docker Hub and the Amazon ECR Public mirror; see [Installation](https://docs.datacontract.com/installation#verifying-the-image) for how to verify them ### Changed - `datacontract test` for Kafka no longer needs PySpark or a Java runtime: `datacontract-cli[kafka]` now installs confluent-kafka, fastavro, and DuckDB instead diff --git a/README.md b/README.md index a3b82a380..7ab4eb087 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,8 @@ alias datacontract='docker run --rm -v "${PWD}:/home/datacontract" datacontract/ _Note:_ The output of Docker command line messages is limited to 80 columns and may include line breaks. Don't pipe docker output to files if you want to export code. Use the `--output` option instead. +Released images are signed with [cosign](https://docs.sigstore.dev/cosign/signing/overview/) keyless signing. See [Verifying the image](https://docs.datacontract.com/installation#verifying-the-image) to check a signature before you run it. + ## Optional Dependencies (Extras) diff --git a/docs/docs/installation.md b/docs/docs/installation.md index 177ab04b9..362ce28ec 100644 --- a/docs/docs/installation.md +++ b/docs/docs/installation.md @@ -74,6 +74,43 @@ alias datacontract='docker run --rm -v "${PWD}:/home/datacontract" datacontract/ The output of Docker command line messages is limited to 80 columns and may include line breaks. Don't pipe Docker output to files if you want to export code — use the `--output` option instead. ::: +The image is also mirrored to Amazon ECR Public: + +```bash +docker pull public.ecr.aws/s4e5k7s9/datacontract-cli +``` + +### Verifying the image + +Released images are signed with [cosign](https://docs.sigstore.dev/cosign/signing/overview/) keyless signing, using the GitHub Actions OIDC identity of the release workflow. There is no public key to distribute — verification checks that the image was built and signed by that workflow, in this repository, from a release tag. + +Install [cosign](https://docs.sigstore.dev/cosign/system_config/installation/), then: + +```bash +cosign verify datacontract/cli:latest \ + --certificate-identity-regexp '^https://github\.com/datacontract/datacontract-cli/\.github/workflows/release\.yaml@refs/tags/v' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com +``` + +Both `--certificate-identity-regexp` and `--certificate-oidc-issuer` are required. Without them, `cosign verify` accepts any valid Sigstore signature — including one produced by someone else. + +The same command works for the ECR mirror, which carries the signature and attestations along with the image: + +```bash +cosign verify public.ecr.aws/s4e5k7s9/datacontract-cli:latest \ + --certificate-identity-regexp '^https://github\.com/datacontract/datacontract-cli/\.github/workflows/release\.yaml@refs/tags/v' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com +``` + +Each image also ships an SBOM and SLSA provenance attestation, which you can inspect with: + +```bash +docker buildx imagetools inspect datacontract/cli:latest --format '{{ json .SBOM }}' +docker buildx imagetools inspect datacontract/cli:latest --format '{{ json .Provenance }}' +``` + +Signatures are attached to images released from version 1.1.0 onwards. Older tags are unsigned. + ## Optional dependencies (extras) The CLI defines several optional dependencies (extras) for specific server types. With `all`, every server dependency is included. diff --git a/docs/docs/release-notes.md b/docs/docs/release-notes.md index 2dee6b8b4..a108207c9 100644 --- a/docs/docs/release-notes.md +++ b/docs/docs/release-notes.md @@ -24,10 +24,14 @@ marked as such in the entry. - Install or upgrade with `uv tool install --upgrade datacontract-cli` or `pip install --upgrade datacontract-cli` — see [Installation](./installation.md). - Packages: [PyPI](https://pypi.org/project/datacontract-cli/#history) · [Docker Hub](https://hub.docker.com/r/datacontract/cli/tags) · [GitHub releases](https://github.com/datacontract/datacontract-cli/releases) -## Unreleased {#unreleased} +## Unreleased - targeting for 1.1.0 {#vUnreleased - targeting for 1-1-0} + +This release drops the pyspark compile-time dependency. The server types `dataframe` and `databricks` still work with a provided Spark session. +This removes the JVM dependency, makes the images much, much smaller (Docker image from 777 MB to 277 MB), and many CVEs are resolved. ### Added - `DATACONTRACT_KAFKA_MAX_MESSAGES` limits how many messages `datacontract test` reads from a topic, and `DATACONTRACT_KAFKA_TIMEOUT` how long it waits for one +- Released Docker images are signed with cosign keyless signing, on Docker Hub and the Amazon ECR Public mirror; see [Installation](https://docs.datacontract.com/installation#verifying-the-image) for how to verify them ### Changed - `datacontract test` for Kafka no longer needs PySpark or a Java runtime: `datacontract-cli[kafka]` now installs confluent-kafka, fastavro, and DuckDB instead