Skip to content

Commit 426afda

Browse files
authored
ci: validate Helm charts integrity nightly (#4052)
1 parent 5188e87 commit 426afda

File tree

4 files changed

+212
-35
lines changed

4 files changed

+212
-35
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
name: "Test - Chart Released Artifact Verification"
2+
3+
on:
4+
schedule:
5+
- cron: "0 8 * * *"
6+
workflow_dispatch: { }
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
verify:
13+
name: Verify released chart artifacts
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Install Cosign
17+
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
18+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
19+
with:
20+
fetch-depth: 0
21+
- name: Get chart versions
22+
id: get-chart-versions
23+
uses: ./.github/actions/get-chart-versions
24+
- name: ⭐ Verify artifacts ⭐
25+
id: verify-artifacts
26+
env:
27+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28+
# NOTE: Once we move to Helm OCI repo, the logic here will be simpler
29+
# as Cosign can work directly with the OCI registry.
30+
run: |
31+
# Track verification results.
32+
skipped_tags=()
33+
skipped_verifications=()
34+
failed_verifications=()
35+
successful_verifications=()
36+
37+
# Loop over the chart versions to check the artifact integrity.
38+
for camunda_version in ${{ steps.get-chart-versions.outputs.active }}; do
39+
echo ""
40+
echo "##################################################"
41+
echo "# 🏢 Camunda ${camunda_version}"
42+
echo "##################################################"
43+
44+
chart_version_prefix="camunda-platform-${camunda_version}"
45+
chart_git_tags=$(git tag -l | grep "${chart_version_prefix}-")
46+
for chart_tag_name in ${chart_git_tags}; do
47+
chart_version="$(echo "${chart_tag_name}" | sed "s/${chart_version_prefix}-//")"
48+
chart_cosign_verify_file="camunda-platform-${chart_version}-cosign-verify.sh"
49+
50+
# Print chart details.
51+
echo ""
52+
echo "📜 Chart ${chart_version}:"
53+
echo "=========================="
54+
55+
echo "- 🏷️ Release tag \"${chart_tag_name}\"."
56+
57+
# Early return if the chart release doesn't exist.
58+
if ! gh release view "${chart_tag_name}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
59+
echo "- ⚠️ Chart GitHub release does not exist."
60+
skipped_tags+=("${chart_tag_name}")
61+
continue
62+
fi
63+
64+
echo "- 📥 Download release artifacts."
65+
gh release download "${chart_tag_name}" --dir chart-release-artifacts
66+
67+
# Run only if the release has a verify file.
68+
cd chart-release-artifacts
69+
70+
# Early return if the verify file is not present.
71+
if [ ! -f "${chart_cosign_verify_file}" ]; then
72+
echo "- ⛔ The chart release doesn't have a Cosign verification script."
73+
skipped_verifications+=("${chart_tag_name}")
74+
continue
75+
fi
76+
77+
# Run the Cosign verification script.
78+
echo "- 🔐 Running Cosign verification"
79+
if bash "${chart_cosign_verify_file}"; then
80+
echo "- ✅ Cosign verification successful."
81+
successful_verifications+=("${chart_tag_name}")
82+
else
83+
echo "- ❌ Cosign verification failed."
84+
cat "${chart_cosign_verify_file}"
85+
failed_verifications+=("${chart_tag_name}")
86+
fi
87+
88+
# Clean up.
89+
cd ..
90+
rm -rf chart-release-artifacts
91+
92+
done # End of chart version loop.
93+
done # End of Camunda minor version loop.
94+
95+
echo "##################################################"
96+
echo "# Export outputs."
97+
echo "##################################################"
98+
echo "skipped-tags-count=${#skipped_tags[@]}" | tee >> $GITHUB_OUTPUT
99+
echo "skipped-verifications-count=${#skipped_verifications[@]}" | tee >> $GITHUB_OUTPUT
100+
echo "failed-verifications-count=${#failed_verifications[@]}" | tee >> $GITHUB_OUTPUT
101+
echo "successful-verifications-count=${#successful_verifications[@]}" | tee >> $GITHUB_OUTPUT
102+
103+
echo ""
104+
echo "##################################################"
105+
echo "# 📊 Verification Summary"
106+
echo "##################################################"
107+
108+
echo -e "\n⚠️ Skipped tags (${#skipped_tags[@]}):"
109+
printf -- "- %s\n" "${skipped_tags[@]:-🎉 No skipped tags}"
110+
111+
echo -e "\n⛔ Skipped verifications (${#skipped_verifications[@]}):"
112+
printf -- "- %s\n" "${skipped_verifications[@]:-🎉 No skipped verifications}"
113+
114+
echo -e "\n❌ Failed verifications (${#failed_verifications[@]}):"
115+
printf -- "- %s\n" "${failed_verifications[@]:-🎉 No failed verifications}"
116+
117+
echo -e "\n✅ Successful verifications (${#successful_verifications[@]}):"
118+
printf -- "- %s\n" "${successful_verifications[@]:-⚠️ No successful verifications ⚠️}"
119+
120+
if [[ ${#failed_verifications[@]} -gt 0 || ${#successful_verifications[@]} -eq 0 ]]; then
121+
exit 1
122+
fi
123+
# NOTE: If needed, we can limit the scope of the Slack notification to failed verifications only.
124+
- name: 🚨 Notify the team via Slack 🚨
125+
if: failure()
126+
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
127+
with:
128+
webhook: ${{ secrets.SLACK_DISTRO_TEAM_WEBHOOK }}
129+
webhook-type: incoming-webhook
130+
payload: |
131+
blocks:
132+
- type: header
133+
text:
134+
type: plain_text
135+
text: Chart Released Artifact Verification Failed
136+
emoji: true
137+
- type: section
138+
text:
139+
type: mrkdwn
140+
text: |
141+
🚨 *Action Required* 🚨
142+
There could be a compromised Helm chart artifact! Please investigate the issue immediately.
143+
144+
*Summary:*
145+
• Failed verifications: ${{ steps.verify-artifacts.outputs.failed-verifications-count }}
146+
• Successful verifications: ${{ steps.verify-artifacts.outputs.successful-verifications-count }}
147+
• Skipped verifications: ${{ steps.verify-artifacts.outputs.skipped-verifications-count }}
148+
• Skipped tags: ${{ steps.verify-artifacts.outputs.skipped-tags-count }}
149+
150+
<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Check failed workflow>

.github/workflows/chart-release.yaml

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,15 @@ jobs:
100100
contents: write
101101
id-token: write
102102
env:
103-
CHART_RELEASER_CONFIG: ".github/config/chart-releaser.yaml"
103+
CHART_RELEASER_CONFIG_FILE: ".github/config/chart-releaser.yaml"
104104
CHART_DIR: "charts/camunda-platform-${{ matrix.chart.dirID }}"
105-
CHART_VERSION: "${{ matrix.chart.version }}"
106-
CHART_PACKAGE_NAME: "camunda-platform-${{ matrix.chart.version }}"
107-
CHART_TAG_NAME: "camunda-platform-${{ matrix.chart.appVersion }}-${{ matrix.chart.version }}"
105+
CHART_RELEASE_VERSION: "${{ matrix.chart.version }}"
106+
CHART_RELEASE_TAG_NAME: "camunda-platform-${{ matrix.chart.appVersion }}-${{ matrix.chart.version }}"
107+
CHART_RELEASE_PACKAGE_FILE: "camunda-platform-${{ matrix.chart.version }}.tgz"
108+
CHART_RELEASE_COSIGN_BUNDLE_FILE: "camunda-platform-${{ matrix.chart.version }}-cosign-bundle.json"
109+
CHART_RELEASE_COSIGN_VERIFY_FILE: "camunda-platform-${{ matrix.chart.version }}-cosign-verify.sh"
110+
CHART_RELEASE_COSIGN_CERTIFICATE_IDENTITY: "https://github.com/${{ github.workflow_ref }}"
111+
CHART_RELEASE_COSIGN_CERTIFICATE_OIDC_ISSUER: "https://token.actions.githubusercontent.com"
108112
steps:
109113
# Init.
110114
- name: Checkout
@@ -165,65 +169,93 @@ jobs:
165169
# Using the chart-releaser CLI provides more flexibility and control over the release process.
166170
- name: Run Chart Releaser - Packaging
167171
run: |
168-
helm-cr package ${{ env.CHART_DIR }} --config ${{ env.CHART_RELEASER_CONFIG }}
172+
helm-cr package ${{ env.CHART_DIR }} --config ${{ env.CHART_RELEASER_CONFIG_FILE }}
169173
# Only keep the chart in the release process to avoid releasing untargeted charts.
170174
- name: Clean up packages
171175
run: |
172176
ls -lsa .cr-release-packages/*
173177
find .cr-release-packages/* \
174-
-not -name "camunda-platform-${{ env.CHART_VERSION }}.tgz" \
178+
-not -name "${{ env.CHART_RELEASE_PACKAGE_FILE }}" \
175179
-delete
176180
- name: Run Chart Releaser - Tagging/Uploading
177181
env:
178182
CR_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
179183
run: |
180-
helm-cr upload --config ${{ env.CHART_RELEASER_CONFIG }} \
184+
helm-cr upload --config ${{ env.CHART_RELEASER_CONFIG_FILE }} \
181185
--push \
182186
--owner "${{ github.repository_owner }}" \
183187
--git-repo "$(basename ${{ github.repository }})" \
184-
--release-name-template "${{ env.CHART_TAG_NAME }}"
188+
--release-name-template "${{ env.CHART_RELEASE_TAG_NAME }}"
185189
- name: Run Chart Releaser - Indexing
186190
env:
187191
CR_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
188192
run: |
189-
helm-cr index --config ${{ env.CHART_RELEASER_CONFIG }} \
193+
helm-cr index --config ${{ env.CHART_RELEASER_CONFIG_FILE }} \
190194
--push \
191195
--owner "${{ github.repository_owner }}" \
192196
--git-repo "$(basename ${{ github.repository }})" \
193-
--release-name-template "${{ env.CHART_TAG_NAME }}"
197+
--release-name-template "${{ env.CHART_RELEASE_TAG_NAME }}"
194198
- name: Set GitHub release type
195199
if: ${{ matrix.chart.prerelease }}
196200
env:
197201
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
198202
run: |
199-
gh release edit "${{ env.CHART_TAG_NAME }}" \
203+
gh release edit "${{ env.CHART_RELEASE_TAG_NAME }}" \
200204
--repo "${GITHUB_REPOSITORY}" \
201205
--prerelease
202206
203207
# Sign and upload the signature.
204208
- name: Sign Helm chart with Cosign
209+
working-directory: .cr-release-packages
205210
run: |
206-
cosign sign-blob -y .cr-release-packages/${{ env.CHART_PACKAGE_NAME }}.tgz \
207-
--bundle "${{ env.CHART_PACKAGE_NAME }}.cosign.bundle"
211+
cosign sign-blob -y ${{ env.CHART_RELEASE_PACKAGE_FILE }} \
212+
--bundle "${{ env.CHART_RELEASE_COSIGN_BUNDLE_FILE }}"
213+
- name: Get Helm chart Cosign Rekor log index
214+
working-directory: .cr-release-packages
215+
run: |
216+
rekor_log_index="$(cat ${{ env.CHART_RELEASE_COSIGN_BUNDLE_FILE }} | jq '.rekorBundle.Payload.logIndex')"
217+
echo "CHART_RELEASE_COSIGN_REKOR_LOG_INDEX=${rekor_log_index}" >> $GITHUB_ENV
218+
- name: Create the script to verify signed Helm chart with Cosign
219+
working-directory: .cr-release-packages
220+
run: |
221+
cat << EOF > ${{ env.CHART_RELEASE_NAME }}-cosign-verify.sh
222+
# Rekor.
223+
echo "Rekor record:"
224+
echo "https://search.sigstore.dev/?logIndex=${{ env.CHART_RELEASE_COSIGN_REKOR_LOG_INDEX }}"
225+
226+
# Cosign.
227+
cosign verify-blob ${{ env.CHART_RELEASE_PACKAGE_FILE }} \\
228+
--bundle "${{ env.CHART_RELEASE_COSIGN_BUNDLE_FILE }}" \\
229+
--certificate-identity "${{ env.CHART_RELEASE_COSIGN_CERTIFICATE_IDENTITY }}" \\
230+
--certificate-oidc-issuer "${{ env.CHART_RELEASE_COSIGN_CERTIFICATE_OIDC_ISSUER }}"
231+
EOF
208232
- name: Verify signed Helm chart with Cosign
233+
working-directory: .cr-release-packages
209234
run: |
210-
cosign verify-blob .cr-release-packages/${{ env.CHART_PACKAGE_NAME }}.tgz \
211-
--bundle "${{ env.CHART_PACKAGE_NAME }}.cosign.bundle" \
212-
--certificate-identity "https://github.com/${GITHUB_WORKFLOW_REF}" \
213-
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
214-
- name: Upload Helm chart signature bundle
235+
bash ${{ env.CHART_RELEASE_COSIGN_VERIFY_FILE }}
236+
- name: Upload Helm chart Cosign bundle file
237+
working-directory: .cr-release-packages
238+
env:
239+
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
240+
run: |
241+
gh release upload "${{ env.CHART_RELEASE_TAG_NAME }}" \
242+
"${{ env.CHART_RELEASE_COSIGN_BUNDLE_FILE }}" \
243+
--repo "${GITHUB_REPOSITORY}"
244+
- name: Upload Helm chart Cosign verify file
245+
working-directory: .cr-release-packages
215246
env:
216247
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
217248
run: |
218-
gh release upload "${{ env.CHART_TAG_NAME }}" \
219-
"${{ env.CHART_PACKAGE_NAME }}.cosign.bundle" \
249+
gh release upload "${{ env.CHART_RELEASE_TAG_NAME }}" \
250+
"${{ env.CHART_RELEASE_COSIGN_VERIFY_FILE }}" \
220251
--repo "${GITHUB_REPOSITORY}"
221252
- name: Add release info to workflow summary
222253
run: |
223254
echo "ℹ️ Release Published ℹ️" >> $GITHUB_STEP_SUMMARY
224255
cat << EOF >> $GITHUB_STEP_SUMMARY
225-
- GitHub: https://github.com/${GITHUB_REPOSITORY}/releases/tag/${{ env.CHART_TAG_NAME }}
226-
- Artifact Hub: https://artifacthub.io/packages/helm/camunda/camunda-platform/${{ env.CHART_VERSION }}
256+
- GitHub: https://github.com/${GITHUB_REPOSITORY}/releases/tag/${{ env.CHART_RELEASE_TAG_NAME }}
257+
- Artifact Hub: https://artifacthub.io/packages/helm/camunda/camunda-platform/${{ env.CHART_RELEASE_VERSION }}
258+
- Rekor record: https://rekor.sigstore.dev/?logIndex=${{ env.CHART_RELEASE_COSIGN_REKOR_LOG_INDEX }}
227259
Note: Artifact Hub link needs some time till it's AH scraps the Helm repo index.
228260
EOF
229261
@@ -261,13 +293,6 @@ jobs:
261293
helm
262294
helm-ct
263295
yq
264-
# - name: Simple smoke test
265-
# uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3
266-
# with:
267-
# max_attempts: 3
268-
# timeout_minutes: 5
269-
# retry_wait_seconds: 10
270-
# command: make release.verify-components-version
271296
- name: Label PRs with app and chart version
272297
env:
273298
GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

scripts/generate-release-notes.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ main () {
9090

9191
release_notes_footer () {
9292
chart_dir="${1}"
93-
export CHART_NAME_WITH_VERSION="$(yq '[.name, .version] | join("-")' "${chart_dir}/Chart.yaml")"
93+
export CHART_RELEASE_NAME="$(yq '[.name, .version] | join("-")' "${chart_dir}/Chart.yaml")"
9494
export VERSION_MATRIX_RELEASE_HEADER="false"
9595
export VERSION_MATRIX_RELEASE_INFO="$(CHART_DIR=${chart_dir} make release.generate-version-matrix-unreleased)"
9696
echo "\nChart dir: $${chart_dir}";\

scripts/templates/release-notes/RELEASE-NOTES-FOOTER.md.tpl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
### Verification
66

7-
To verify the integrity of the Helm chart using [Cosign](https://docs.sigstore.dev/signing/quickstart/):
7+
For quick verification of the Helm chart integrity using [Cosign](https://docs.sigstore.dev/signing/quickstart/):
88

99
```shell
10-
cosign verify-blob {{ getenv "CHART_NAME_WITH_VERSION" }}.tgz \
11-
--bundle {{ getenv "CHART_NAME_WITH_VERSION" }}.cosign.bundle \
12-
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
13-
--certificate-identity "https://github.com/{{ getenv "GITHUB_WORKFLOW_REF" }}"
10+
cosign verify-blob {{ getenv "CHART_RELEASE_NAME" }}.tgz \
11+
--bundle "{{ getenv "CHART_RELEASE_NAME" }}-cosign-bundle.json" \
12+
--certificate-identity-regex "https://github.com/{{ getenv "GITHUB_REPOSITORY" }}" \
13+
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
1414
```
15+
16+
For detailed verification instructions, check the steps in the `{{ getenv "CHART_RELEASE_NAME" }}-cosign-verify.sh` file.

0 commit comments

Comments
 (0)