Skip to content

Commit 9044bec

Browse files
ci: sign the published Helm charts with cosign (keyless)
The image already carried provenance + SBOM attestations while the charts were pushed bare, so nothing about a published chart was verifiable. Sign them in the release workflow with keyless cosign: the signature is bound to this workflow's GitHub OIDC identity via a short-lived Fulcio certificate, so there is no long-lived key to store or rotate. Charts are signed BY DIGEST (parsed out of `helm push`), so a signature binds to that exact artifact and cannot be voided by someone later moving the tag. The digest parse is guarded: an unparseable `helm push` output fails the job rather than silently skipping the signature. Note this does NOT fix the Artifact Hub "429 Too Many Requests" alert. That comes from ghcr's OCI referrers endpoint, which 303-redirects to a legacy GitHub packages URL with the digest truncated at the colon; it 404s or rate-limits regardless of whether a signature exists. This is about the charts actually being verifiable, not about silencing that probe.
1 parent 2ddf7d3 commit 9044bec

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

.github/workflows/release.yml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ jobs:
7676
permissions:
7777
contents: read
7878
packages: write
79+
# Keyless signing trades this OIDC token for a short-lived Fulcio
80+
# certificate: the signature is bound to this workflow's identity, so
81+
# there is no long-lived private key to store or rotate.
82+
id-token: write
7983
steps:
8084
- name: Clone the code
8185
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -85,15 +89,39 @@ jobs:
8589
- name: Set up Helm
8690
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
8791

92+
- name: Install cosign
93+
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
94+
8895
- name: Log in to the Container registry
8996
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login "${REGISTRY}" --username "${{ github.actor }}" --password-stdin
9097

91-
- name: Package and push charts
98+
# cosign authenticates through the Docker config, which `helm registry
99+
# login` does not write — so log in again the Docker way for the push of
100+
# the signature itself.
101+
- name: Log in to the Container registry (for cosign)
102+
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
103+
with:
104+
registry: ${{ env.REGISTRY }}
105+
username: ${{ github.actor }}
106+
password: ${{ secrets.GITHUB_TOKEN }}
107+
108+
- name: Package, push and sign charts
92109
run: |
93110
version="${GITHUB_REF_NAME#v}"
94111
for chart in charts/valkey-operator charts/valkey-cluster; do
95112
helm package "$chart" --version "$version" --app-version "$version"
96113
done
97114
for pkg in *.tgz; do
98-
helm push "$pkg" "oci://${REGISTRY}/${{ github.repository_owner }}/charts"
115+
# `helm push` reports the pushed ref and its digest. Sign BY DIGEST so
116+
# the signature binds to that exact artifact and cannot be voided by
117+
# someone later moving the tag.
118+
out=$(helm push "$pkg" "oci://${REGISTRY}/${{ github.repository_owner }}/charts" 2>&1)
119+
echo "$out"
120+
ref=$(printf '%s\n' "$out" | awk '/Pushed:/{print $2}')
121+
digest=$(printf '%s\n' "$out" | awk '/Digest:/{print $2}')
122+
if [ -z "$ref" ] || [ -z "$digest" ]; then
123+
echo "could not parse ref/digest out of helm push output for $pkg" >&2
124+
exit 1
125+
fi
126+
cosign sign --yes "${ref%:*}@${digest}"
99127
done

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ All notable changes to this project are documented here. The format is based on
44
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims to
55
follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [Unreleased]
8+
9+
### Added
10+
- The published Helm charts are now signed with cosign, keyless, using the
11+
release workflow's own GitHub OIDC identity, so there is no private key to
12+
store or rotate. Charts are signed **by digest**, so a signature binds to that
13+
exact artifact and cannot be voided by moving a tag. Verify one with:
14+
15+
```sh
16+
cosign verify ghcr.io/melancholictheory/charts/valkey-operator:<version> \
17+
--certificate-identity-regexp '^https://github\.com/melancholictheory/wellcake/\.github/workflows/release\.yml@' \
18+
--certificate-oidc-issuer https://token.actions.githubusercontent.com
19+
```
20+
721
## [0.5.0]
822

923
Data-plane telemetry and resilience work built on Valkey 9.1+. Every directive is

0 commit comments

Comments
 (0)