From b52990eaa2eb7d63e91060b2a43dcf4ae3aa2b21 Mon Sep 17 00:00:00 2001 From: Simon Koudijs Date: Mon, 5 Oct 2026 15:47:44 +0000 Subject: [PATCH 1/2] fix: attest the published Helm chart and list it on Artifact Hub The 0.1.0 release pushed the chart but failed at its provenance attestation: "No credentials found for registry ghcr.io". helm logged in inside the CI container, while the attestation is pushed from the runner, which had no login. The chart job now logs in on the runner too, as the image job does. As gitops-reverser does, the job also pushes charts/artifacthub-repo.yml under the chart repository's artifacthub.io tag with oras, which is added to the ci stage of the devcontainer image. Co-Authored-By: Claude Opus 5.5 --- .devcontainer/Dockerfile | 18 +++++++++++++++++- .github/workflows/release.yml | 22 ++++++++++++++++++++++ charts/artifacthub-repo.yml | 17 +++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 charts/artifacthub-repo.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4213acd..097d5f4 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -56,6 +56,7 @@ RUN apt-get update \ # HELM_VERSION / HELM_SHA256 -> https://github.com/helm/helm/releases; the checksum is # get.helm.sh/helm--linux-amd64.tar.gz.sha256sum. The e2e fixture installs # Traefik with it. +# ORAS_VERSION -> https://github.com/oras-project/oras/releases ENV PATH="/go/bin:/usr/local/go/bin:${PATH}" \ GOLANGCI_LINT_VERSION=v2.13.2 \ TASK_VERSION=v3.53.1 \ @@ -64,7 +65,8 @@ ENV PATH="/go/bin:/usr/local/go/bin:${PATH}" \ ACTIONLINT_VERSION=1.7.12 \ HADOLINT_VERSION=2.15.1 \ HELM_VERSION=v4.3.0 \ - HELM_SHA256=86584a54def73570558f66f5111cc53dfed56689637ae32c1201205d494f54fb + HELM_SHA256=86584a54def73570558f66f5111cc53dfed56689637ae32c1201205d494f54fb \ + ORAS_VERSION=1.3.4 RUN test "$(dpkg --print-architecture)" = "amd64" \ || (echo "This devcontainer currently supports amd64 only." && exit 1) @@ -92,6 +94,20 @@ RUN curl -fsSL -o /tmp/helm.tar.gz "https://get.helm.sh/helm-${HELM_VERSION}-lin && rm /tmp/helm.tar.gz \ && helm version --short +# oras pushes charts/artifacthub-repo.yml beside the chart under the `artifacthub.io` tag +# (release.yml), a plain OCI artifact that `helm push` cannot produce. The checksum file +# comes from the same release as the archive. +RUN asset="oras_${ORAS_VERSION}_linux_amd64.tar.gz" \ + && base="https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}" \ + && curl -fsSL -o /tmp/oras.tar.gz "${base}/${asset}" \ + && curl -fsSL -o /tmp/oras-checksums.txt "${base}/oras_${ORAS_VERSION}_checksums.txt" \ + && awk -v a="${asset}" '$2 == a { print $1 " /tmp/oras.tar.gz" }' /tmp/oras-checksums.txt \ + | sha256sum -c - \ + && tar -xzO -f /tmp/oras.tar.gz oras > /usr/local/bin/oras \ + && chmod +x /usr/local/bin/oras \ + && rm /tmp/oras.tar.gz /tmp/oras-checksums.txt \ + && oras version + RUN curl -fsSL -o /usr/local/bin/hadolint "https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}/hadolint-Linux-x86_64" \ && chmod +x /usr/local/bin/hadolint \ && hadolint --version diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 652edf5..3d8731d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -149,6 +149,28 @@ jobs: digest="$(awk '/^Digest:/ { print $2 }' "$RUNNER_TEMP/push.log")" [ -n "$digest" ] || { echo "no digest in helm push's output" >&2; exit 1; } echo "digest=$digest" >> "$GITHUB_OUTPUT" + # Artifact Hub reads the repository metadata (ownership claim, verified publisher) + # from a plain OCI artifact under the fixed `artifacthub.io` tag of the chart + # repository, recognised by its media types. `helm push` cannot produce that shape, + # so oras does, as gitops-reverser does: + # https://artifacthub.io/docs/topics/repositories/helm-charts/ + - name: push the Artifact Hub repository metadata + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + docker run --rm -v "$GITHUB_WORKSPACE:$CI_WORKDIR" -w "$CI_WORKDIR/charts" \ + -e GITHUB_TOKEN -e GITHUB_ACTOR -e CHART "$CI_IMAGE" bash -lc ' + set -euo pipefail + echo "$GITHUB_TOKEN" | oras login ghcr.io -u "$GITHUB_ACTOR" --password-stdin + oras push "$CHART:artifacthub.io" \ + --config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml \ + artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml + ' + # Every login above happened inside the CI container. The attestation is pushed + # from the runner, which needs its own: without it the 0.1.0 release failed with + # "No credentials found for registry ghcr.io" after the chart itself was pushed. + - name: log in to ghcr.io + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 with: subject-name: ${{ env.CHART }} diff --git a/charts/artifacthub-repo.yml b/charts/artifacthub-repo.yml new file mode 100644 index 0000000..5db1bd8 --- /dev/null +++ b/charts/artifacthub-repo.yml @@ -0,0 +1,17 @@ +# Artifact Hub repository metadata, as gitops-reverser publishes its own. +# +# This is not part of the Helm chart. It describes the repository +# oci://ghcr.io/configbutler/charts/krm-foyer to Artifact Hub, and the chart job in +# .github/workflows/release.yml pushes it to that repository under the fixed +# `artifacthub.io` tag on every release, so this file stays the source of truth. It lives +# outside charts/krm-foyer/ so `helm package` does not bundle it into the chart. +# +# Format: https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-repo.yml +# +# Once the repository is added in the Artifact Hub control panel, its repositoryID goes +# here to earn the Verified Publisher badge. + +# Claims repository ownership. The email must match an Artifact Hub account exactly. +owners: + - name: Simon Koudijs + email: simonkoudijs@gmail.com From 41ba9a40627b33d579b147edd83d7165b8f21286 Mon Sep 17 00:00:00 2001 From: Simon Koudijs Date: Mon, 5 Oct 2026 16:01:05 +0000 Subject: [PATCH 2/2] fix: sign and publish releases the way gitops-reverser does Align the release pipeline with gitops-reverser's, where it was tuned: - CI runs before release-please, so a red commit never gets a release. - Releases are drafts until every asset is attached, then publish-release publishes them; the repository has immutable releases switched on, so a published release accepts no assets. release-please-pr opens the next release pull request against the published release. - The image is signed with cosign, attested (provenance and SPDX SBOM), and the SBOM goes on the release with its signature and provenance. - The chart is signed with cosign instead of attested, which also removes the attestation step the 0.1.0 release failed on. cosign comes from the CI image, beside oras. - The release notes show how to install and verify. Co-Authored-By: Claude Opus 5.5 --- .devcontainer/Dockerfile | 15 +++ .github/workflows/ci.yml | 2 + .github/workflows/release.yml | 224 +++++++++++++++++++++++++++------- docs/roadmap.md | 8 +- release-please-config.json | 1 + 5 files changed, 204 insertions(+), 46 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 097d5f4..050bb5f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -56,6 +56,8 @@ RUN apt-get update \ # HELM_VERSION / HELM_SHA256 -> https://github.com/helm/helm/releases; the checksum is # get.helm.sh/helm--linux-amd64.tar.gz.sha256sum. The e2e fixture installs # Traefik with it. +# COSIGN_VERSION -> https://github.com/sigstore/cosign/releases; release.yml's image +# job installs the same version with cosign-installer, so keep the two in step. # ORAS_VERSION -> https://github.com/oras-project/oras/releases ENV PATH="/go/bin:/usr/local/go/bin:${PATH}" \ GOLANGCI_LINT_VERSION=v2.13.2 \ @@ -66,6 +68,7 @@ ENV PATH="/go/bin:/usr/local/go/bin:${PATH}" \ HADOLINT_VERSION=2.15.1 \ HELM_VERSION=v4.3.0 \ HELM_SHA256=86584a54def73570558f66f5111cc53dfed56689637ae32c1201205d494f54fb \ + COSIGN_VERSION=v3.1.3 \ ORAS_VERSION=1.3.4 RUN test "$(dpkg --print-architecture)" = "amd64" \ @@ -94,6 +97,18 @@ RUN curl -fsSL -o /tmp/helm.tar.gz "https://get.helm.sh/helm-${HELM_VERSION}-lin && rm /tmp/helm.tar.gz \ && helm version --short +# cosign signs the published Helm chart (release.yml), and verifies what a release +# published from here. +RUN asset="cosign-linux-amd64" \ + && base="https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}" \ + && curl -fsSL -o /tmp/cosign "${base}/${asset}" \ + && curl -fsSL -o /tmp/cosign-checksums.txt "${base}/cosign_checksums.txt" \ + && awk -v a="${asset}" '$2 == a { print $1 " /tmp/cosign" }' /tmp/cosign-checksums.txt \ + | sha256sum -c - \ + && install -m 0755 /tmp/cosign /usr/local/bin/cosign \ + && rm /tmp/cosign /tmp/cosign-checksums.txt \ + && cosign version + # oras pushes charts/artifacthub-repo.yml beside the chart under the `artifacthub.io` tag # (release.yml), a plain OCI artifact that `helm push` cannot produce. The checksum file # comes from the same release as the archive. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37bd5b6..ac18807 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,8 @@ jobs: kubectl version --client k3d version helm version --short + cosign version + oras version docker --version openssl version ' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d8731d..cecd718 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,26 +1,36 @@ -# Versions, and the published image and Helm chart. +# Versions, and the published image and Helm chart. The shape follows gitops-reverser's +# release.yml, where it was tuned first; the differences are noted where they occur. # -# On every push to main, release-please keeps a release pull request up to date from the -# conventional-commit messages: `fix:` is a patch, and `feat:` and `feat!:` are both a minor +# On every push to main, the full CI workflow runs first, and only on a green commit does +# release-please do anything: `fix:` is a patch, and `feat:` and `feat!:` are both a minor # while the version is below 1.0 (`bump-minor-pre-major`). The first release is 0.1.0 # (`initial-version`; without it release-please starts at 1.0.0). Nothing moves to 1.0 on # its own: that is a deliberate `release-as` once other people use krm-foyer happily. -# Merging that pull request creates the tag and GitHub release. The image is then published only after the full CI workflow has passed -# on that exact commit. +# +# The repository has immutable releases switched on: a published release accepts no more +# assets. So release-please creates the release as a draft (`draft` in +# release-please-config.json), the publish jobs sign, attest and attach to that draft, and +# publish-release publishes it last. A failed publish job leaves an unpublished draft, never +# a half-populated immutable release. # # Repository setting required: Settings -> Actions -> General -> "Allow GitHub Actions to # create and approve pull requests", or release-please cannot open its pull request. # # A pull request opened with GITHUB_TOKEN does not trigger other workflows, so CI does not # run on the release pull request itself. That is acceptable because it only changes the -# changelog and the manifest, and the ci job below validates the merged commit before anything -# is published. +# changelog, the manifest and the chart's versions, and the ci job below validates the +# merged commit before anything is published. name: release on: push: branches: [main] +env: + REGISTRY: ghcr.io + IMAGE: ghcr.io/configbutler/krm-foyer + CHART_REGISTRY: ghcr.io/configbutler/charts + permissions: contents: read @@ -30,10 +40,19 @@ concurrency: cancel-in-progress: false jobs: + ci: + name: ci + uses: ./.github/workflows/ci.yml + with: + ref: ${{ github.sha }} + permissions: + contents: read + release-please: name: release-please runs-on: ubuntu-latest timeout-minutes: 10 + needs: ci permissions: contents: write pull-requests: write @@ -48,29 +67,29 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} config-file: release-please-config.json manifest-file: .release-please-manifest.json + # Cut the release here, but do not open the next release pull request yet. A + # draft release is invisible to release-please's own "latest release" lookup, so + # a pull request opened now would be computed against the whole history and + # propose a bogus version. release-please-pr opens it once the release is + # published. + skip-github-pull-request: true - ci: - name: ci - uses: ./.github/workflows/ci.yml - with: - ref: ${{ github.sha }} - permissions: - contents: read - + # Unlike gitops-reverser, which retags the multi-arch digests its CI run already pushed, + # the image is built here: krm-foyer is amd64 only, and its CI builds without pushing. image: name: publish the image runs-on: ubuntu-latest timeout-minutes: 20 - needs: [release-please, ci] + needs: [ci, release-please] if: needs.release-please.outputs.release_created == 'true' permissions: - contents: read + contents: write # attach the SBOM and the release notes to the draft release packages: write - id-token: write # keyless signing of the build provenance + id-token: write # cosign keyless signing + attestation OIDC attestations: write env: - IMAGE: ghcr.io/configbutler/krm-foyer VERSION: ${{ needs.release-please.outputs.version }} + TAG_NAME: ${{ needs.release-please.outputs.tag_name }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -78,7 +97,7 @@ jobs: ref: ${{ github.sha }} - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - name: log in to ghcr.io - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login "$REGISTRY" -u "${{ github.actor }}" --password-stdin - id: build uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: @@ -88,33 +107,110 @@ jobs: tags: ${{ env.IMAGE }}:${{ env.VERSION }} provenance: mode=max sbom: true + - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + with: + # Keep in step with COSIGN_VERSION in .devcontainer/Dockerfile, where the chart + # job gets its cosign. Without it the action installs its own, older, release. + cosign-release: v3.1.3 + - name: sign the image (cosign keyless) + # One signature on the digest covers every tag that points at it. + run: cosign sign --yes "$IMAGE@${{ steps.build.outputs.digest }}" - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 with: subject-name: ${{ env.IMAGE }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: true + - name: generate the SBOM (SPDX) + uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2 + with: + image: ${{ env.IMAGE }}@${{ steps.build.outputs.digest }} + format: spdx-json + output-file: sbom.spdx.json + upload-artifact: false + upload-release-assets: false + - uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0 + with: + subject-name: ${{ env.IMAGE }} + subject-digest: ${{ steps.build.outputs.digest }} + sbom-path: sbom.spdx.json + push-to-registry: true + # The image is signed and attested in the registry above, but OpenSSF Scorecard's + # Signed-Releases check only looks at the GitHub release's assets. So the SBOM is + # attached with a signature (*.sigstore.json) and SLSA provenance (*.intoto.jsonl) + # beside it. + - name: sign the SBOM asset (cosign keyless) + run: cosign sign-blob --bundle sbom.spdx.json.sigstore.json --yes sbom.spdx.json + - id: attest-sbom-asset + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: sbom.spdx.json + - name: rename the provenance bundle to the suffix Scorecard recognises + run: cp "${{ steps.attest-sbom-asset.outputs.bundle-path }}" sbom.spdx.json.intoto.jsonl + - name: attach the SBOM and the release notes to the draft release + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 + with: + tag_name: ${{ env.TAG_NAME }} + # Still a draft: an immutable release rejects assets once published, and + # publish-release publishes it after every job has attached its own. + draft: true + append_body: true + files: | + sbom.spdx.json + sbom.spdx.json.sigstore.json + sbom.spdx.json.intoto.jsonl + body: | + ## Install + + ```bash + helm install krm-foyer oci://${{ env.CHART_REGISTRY }}/krm-foyer --version ${{ env.VERSION }} + ``` + + The image is `${{ env.IMAGE }}:${{ env.VERSION }}` (linux/amd64), digest + `${{ steps.build.outputs.digest }}`. + + ## Verify this release + + The image and the chart are signed with cosign (keyless). The image also carries + SLSA build provenance and an SPDX SBOM as registry attestations. + + ```bash + cosign verify \ + --certificate-identity-regexp '^https://github.com/ConfigButler/krm-foyer/\.github/workflows/release\.yml@refs/heads/main$' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + ${{ env.IMAGE }}:${{ env.VERSION }} + + cosign verify \ + --certificate-identity-regexp '^https://github.com/ConfigButler/krm-foyer/\.github/workflows/release\.yml@refs/heads/main$' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + ${{ env.CHART_REGISTRY }}/krm-foyer:${{ env.VERSION }} - # The Helm chart, as an OCI artifact beside the image, as gitops-reverser publishes its - # own: helm install krm-foyer oci://ghcr.io/configbutler/charts/krm-foyer --version X. - # After the image, because the chart's default image tag is its appVersion: a chart is - # never published before the image it points at. Helm comes from the CI image, like - # every other tool; it is the same artifact the ci job built for this commit. + gh attestation verify oci://${{ env.IMAGE }}:${{ env.VERSION }} --repo ConfigButler/krm-foyer + ``` + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # The Helm chart, as an OCI artifact beside the image: helm install krm-foyer + # oci://ghcr.io/configbutler/charts/krm-foyer --version X. After the image, because the + # chart's default image tag is its appVersion: a chart is never published before the + # image it points at. helm, cosign and oras come from the CI image, like every other + # tool. gitops-reverser runs this job inside that image with `container:`; krm-foyer's CI + # image is a workflow artifact rather than a registry image, so each step docker-runs it. chart: name: publish the Helm chart runs-on: ubuntu-latest timeout-minutes: 15 - needs: [release-please, ci, image] + needs: [ci, release-please, image] if: needs.release-please.outputs.release_created == 'true' permissions: contents: read packages: write - id-token: write # keyless signing of the build provenance - attestations: write + id-token: write # cosign keyless signing env: CHART: ghcr.io/configbutler/charts/krm-foyer VERSION: ${{ needs.release-please.outputs.version }} CI_IMAGE: krm-foyer-ci:local CI_WORKDIR: /workspaces/krm-foyer + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -131,8 +227,6 @@ jobs: # (release-please-config.json); a chart that disagrees with the tag is not pushed. - id: push name: package and push the chart - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | docker run --rm -v "$GITHUB_WORKSPACE:$CI_WORKDIR" -w "$CI_WORKDIR" \ -e GITHUB_TOKEN -e GITHUB_ACTOR -e VERSION -e CHART "$CI_IMAGE" bash -lc ' @@ -149,14 +243,26 @@ jobs: digest="$(awk '/^Digest:/ { print $2 }' "$RUNNER_TEMP/push.log")" [ -n "$digest" ] || { echo "no digest in helm push's output" >&2; exit 1; } echo "digest=$digest" >> "$GITHUB_OUTPUT" + # Each tool logs in for itself: `helm registry login` writes helm's own registry + # config, and cosign reads ~/.docker/config.json, so without its own login the + # signature upload fails with UNAUTHORIZED. The ACTIONS_ID_TOKEN_REQUEST_* variables + # carry the job's OIDC token into the container for keyless signing. + - name: sign the chart (cosign keyless) + env: + DIGEST: ${{ steps.push.outputs.digest }} + run: | + docker run --rm -e GITHUB_TOKEN -e GITHUB_ACTOR -e CHART -e DIGEST \ + -e ACTIONS_ID_TOKEN_REQUEST_URL -e ACTIONS_ID_TOKEN_REQUEST_TOKEN "$CI_IMAGE" bash -lc ' + set -euo pipefail + cosign login ghcr.io -u "$GITHUB_ACTOR" -p "$GITHUB_TOKEN" + cosign sign --yes "$CHART@$DIGEST" + ' # Artifact Hub reads the repository metadata (ownership claim, verified publisher) # from a plain OCI artifact under the fixed `artifacthub.io` tag of the chart # repository, recognised by its media types. `helm push` cannot produce that shape, - # so oras does, as gitops-reverser does: - # https://artifacthub.io/docs/topics/repositories/helm-charts/ + # so oras does: https://artifacthub.io/docs/topics/repositories/helm-charts/. It is + # pushed again on every release, so the file in Git stays the source of truth. - name: push the Artifact Hub repository metadata - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | docker run --rm -v "$GITHUB_WORKSPACE:$CI_WORKDIR" -w "$CI_WORKDIR/charts" \ -e GITHUB_TOKEN -e GITHUB_ACTOR -e CHART "$CI_IMAGE" bash -lc ' @@ -166,13 +272,45 @@ jobs: --config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml \ artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml ' - # Every login above happened inside the CI container. The attestation is pushed - # from the runner, which needs its own: without it the 0.1.0 release failed with - # "No credentials found for registry ghcr.io" after the chart itself was pushed. - - name: log in to ghcr.io - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + + # Publishes the draft release once every asset is attached; see the top of this file. + publish-release: + name: publish the GitHub release + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [release-please, image, chart] + if: needs.release-please.outputs.release_created == 'true' + permissions: + contents: write # publish the draft release, which creates the tag + steps: + - name: publish the draft release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ needs.release-please.outputs.tag_name }} + run: gh release edit "$TAG_NAME" --draft=false --repo "${{ github.repository }}" + + # The pull request half of release-please, run separately so it sees a published + # release instead of the draft. It runs after publish-release when a release was cut, + # and on every other green push so the pending release pull request keeps up; after a + # failed publish (the release still a draft) it opens nothing. + release-please-pr: + name: release-please (pull request) + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [ci, release-please, publish-release] + if: >- + ${{ !cancelled() + && needs.ci.result == 'success' + && needs.release-please.result == 'success' + && (needs.publish-release.result == 'success' + || needs.release-please.outputs.release_created != 'true') }} + permissions: + contents: write + pull-requests: write + steps: + - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 with: - subject-name: ${{ env.CHART }} - subject-digest: ${{ steps.push.outputs.digest }} - push-to-registry: true + token: ${{ secrets.GITHUB_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + skip-github-release: true diff --git a/docs/roadmap.md b/docs/roadmap.md index 16e9ab4..6001b01 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -137,10 +137,12 @@ Security items need tests that try to get past the boundary. return paths, with a short fuzz run of each in `task verify` - [x] Helm chart with `values.schema.json`, `helm lint`, `helm template` tests, and e2e that installs through the chart -- [x] Publish the chart with each release: an OCI artifact beside the image, with build - provenance +- [x] Publish the chart with each release: an OCI artifact beside the image, signed with + cosign and described to Artifact Hub - [x] Release workflow configured to generate an SBOM and attest build provenance -- [ ] Signed multi-arch image (cosign keyless), with documented artifact verification +- [x] Image and chart signed with cosign (keyless); the release notes show how to verify + them, and the SBOM is attached to the release with its signature and provenance +- [ ] Multi-arch image - [ ] Dependency/image vulnerability scanning inside the CI image - [ ] Docs lint: markdownlint and link checking - [ ] Parse the whole squash message the way release-please does, once a dropped diff --git a/release-please-config.json b/release-please-config.json index 7ece12a..154d30c 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -7,6 +7,7 @@ "initial-version": "0.1.0", "package-name": "krm-foyer", "include-component-in-tag": false, + "draft": true, "extra-files": [ { "type": "yaml", "path": "charts/krm-foyer/Chart.yaml", "jsonpath": "$.version" }, { "type": "yaml", "path": "charts/krm-foyer/Chart.yaml", "jsonpath": "$.appVersion" }