Skip to content

Commit 136b092

Browse files
akhanattentiveAli KhanbindsiWilliamBerryiii
authored
ci(docs): enforce signed release tags and verification (#175)
## Summary Implements OpenSSF `version_tags_signed` for release tags. - Document release tag signing policy and verification in `CONTRIBUTING.md` - Add end-user tag verification guidance to `README.md` - Update release workflow to create signed tags using Sigstore `gitsign` - Add tag signature verification workflow for pushed `v*` tags ## Validation - `get_errors` reports no editor diagnostics in updated files - Local markdown/yaml npm lint commands were not runnable in this environment because required CLIs (`markdownlint-cli2`, `pwsh`) are not installed - Tested using workflow in my fork, unsigned fails and signed succeeds https://github.com/akhanattentive/physical-ai-toolchain/actions ## OpenSSF - Addresses requirement: `version_tags_signed` ## Notes - Uses keyless Sigstore signing (`gitsign`) instead of distributed maintainer GPG keys --------- Co-authored-by: Ali Khan <khana@microsoft.com> Co-authored-by: Marcel Bindseil <marcelbindseil@gmail.com> Co-authored-by: Bill Berry <WilliamBerryiii@users.noreply.github.com>
1 parent 62c9900 commit 136b092

5 files changed

Lines changed: 190 additions & 11 deletions

File tree

.cspell/general-technical.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,14 +1553,17 @@ auditable
15531553
auditability
15541554
auditifnotexists
15551555
gdpr
1556+
gitsign
15561557
hipaa
1558+
keyless
15571559
oidc
15581560
OSSF
15591561
passwordless
15601562
siem
15611563
sox
15621564
Sarbanes
15631565
Oxley
1566+
x509
15641567

15651568
# Cloud Platforms & Services (not covered by public dicts)
15661569
amazonaws

.github/workflows/main.yml

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ jobs:
145145
cancel-in-progress: false
146146
permissions:
147147
contents: read
148+
id-token: write
148149
outputs:
149150
release_created: ${{ steps.release.outputs.release_created }}
150151
tag_name: ${{ steps.release.outputs.tag_name }}
@@ -202,33 +203,91 @@ jobs:
202203
git commit -m "chore: regenerate uv.lock"
203204
git push
204205
206+
- name: Checkout default branch for tag signing
207+
if: ${{ steps.release.outputs.release_created == 'true' }}
208+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
209+
with:
210+
token: ${{ steps.app-token.outputs.token }}
211+
fetch-depth: 0
212+
213+
- name: Install gitsign
214+
if: ${{ steps.release.outputs.release_created == 'true' }}
215+
run: |
216+
$version = 'v0.13.0'
217+
$arch = '${{ runner.arch }}'
218+
219+
switch ($arch) {
220+
'X64' {
221+
$asset = 'gitsign_0.13.0_linux_amd64'
222+
$expectedSha = '011af11d57ad205b4ae4e3f41ecc8c64fbe0043c829190c0cb44fce6fce74bbb'
223+
}
224+
'ARM64' {
225+
$asset = 'gitsign_0.13.0_linux_arm64'
226+
$expectedSha = 'f0ec4732696b88f133ab6ef9ac2fe8be843ab9540bd1cc51dc4143142a858372'
227+
}
228+
default {
229+
throw "Unsupported runner architecture: $arch"
230+
}
231+
}
232+
233+
$assetUrl = "https://github.com/sigstore/gitsign/releases/download/$version/$asset"
234+
$assetPath = Join-Path $env:RUNNER_TEMP $asset
235+
Invoke-WebRequest -Uri $assetUrl -OutFile $assetPath
236+
237+
$actualSha = (Get-FileHash -Path $assetPath -Algorithm SHA256).Hash.ToLowerInvariant()
238+
if ($actualSha -ne $expectedSha) {
239+
throw "Checksum verification failed for $asset. Expected $expectedSha but got $actualSha"
240+
}
241+
242+
$binDir = Join-Path $env:RUNNER_TEMP 'bin'
243+
New-Item -ItemType Directory -Force -Path $binDir | Out-Null
244+
$gitsign = Join-Path $binDir 'gitsign'
245+
Move-Item -Path $assetPath -Destination $gitsign -Force
246+
chmod +x $gitsign
247+
Add-Content -Path $env:GITHUB_PATH -Value $binDir
248+
& $gitsign version
249+
205250
# Workaround: release-please with "draft": true uses lazy tag
206251
# creation. The git tag is not materialized until the release is
207252
# published. Without the tag, release-please cannot find the draft
208253
# on subsequent runs, breaking version anchoring and causing bogus
209254
# changelog entries. The skip-github-pull-request conditional above
210255
# prevents PR creation on release commits (zero unreleased changes),
211-
# and this step creates the tag so subsequent runs find the release.
256+
# and this step creates a signed tag so subsequent runs find the release.
212257
# Replace both workarounds with "force-tag-creation": true in
213258
# release-please-config.json once release-please-action ships a
214259
# version that includes googleapis/release-please#2627.
215-
- name: Create git tag for draft release
260+
- name: Create signed git tag for draft release
216261
if: ${{ steps.release.outputs.release_created == 'true' }}
217262
env:
218263
GH_TOKEN: ${{ steps.app-token.outputs.token }}
219264
run: |
265+
git config --global gpg.format x509
266+
git config --global gpg.x509.program gitsign
267+
git config --global tag.gpgSign true
268+
git config user.name "github-actions[bot]"
269+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
270+
271+
git fetch --tags --force
272+
220273
$tag = '${{ steps.release.outputs.tag_name }}'
221-
$repo = '${{ github.repository }}'
222-
$null = gh api "/repos/$repo/git/refs/tags/$tag" --silent 2>&1
223-
if ($LASTEXITCODE -ne 0) {
224-
gh api "/repos/$repo/git/refs" `
225-
-f "ref=refs/tags/$tag" `
226-
-f "sha=${{ github.sha }}"
227-
Write-Output "Created git tag $tag -> ${{ github.sha }}"
228-
} else {
274+
$existingTag = git tag --list $tag
275+
if ($existingTag) {
229276
Write-Output "Git tag $tag already exists"
277+
exit 0
230278
}
231279
280+
git remote set-url origin "https://x-access-token:${env:GH_TOKEN}@github.com/${env:GITHUB_REPOSITORY}.git"
281+
git tag -s $tag $env:GITHUB_SHA -m "Release $tag"
282+
git push origin "refs/tags/$tag"
283+
Write-Output "Created signed git tag $tag -> $env:GITHUB_SHA"
284+
285+
- name: Verify signed git tag
286+
if: ${{ steps.release.outputs.release_created == 'true' }}
287+
run: |
288+
$tag = '${{ steps.release.outputs.tag_name }}'
289+
git tag -v $tag
290+
232291
# Sign release artifacts and generate SBOM attestation
233292
attest-release:
234293
if: ${{ needs.release-please.outputs.release_created == 'true' }}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Verify Release Tag Signature
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
verify-release-tag:
14+
name: Verify pushed release tag
15+
runs-on: ubuntu-latest
16+
permissions:
17+
contents: read
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
22+
with:
23+
fetch-depth: 0
24+
25+
- name: Install gitsign
26+
run: |
27+
VERSION="v0.13.0"
28+
29+
case "${{ runner.arch }}" in
30+
X64)
31+
ASSET="gitsign_0.13.0_linux_amd64"
32+
EXPECTED_SHA="011af11d57ad205b4ae4e3f41ecc8c64fbe0043c829190c0cb44fce6fce74bbb"
33+
;;
34+
ARM64)
35+
ASSET="gitsign_0.13.0_linux_arm64"
36+
EXPECTED_SHA="f0ec4732696b88f133ab6ef9ac2fe8be843ab9540bd1cc51dc4143142a858372"
37+
;;
38+
*)
39+
echo "Unsupported runner architecture: ${{ runner.arch }}"
40+
exit 1
41+
;;
42+
esac
43+
44+
BASE_URL="https://github.com/sigstore/gitsign/releases/download/${VERSION}"
45+
curl -fsSLo "${RUNNER_TEMP}/${ASSET}" "${BASE_URL}/${ASSET}"
46+
47+
echo "${EXPECTED_SHA} ${RUNNER_TEMP}/${ASSET}" | sha256sum -c -
48+
49+
BIN_DIR="${RUNNER_TEMP}/bin"
50+
mkdir -p "${BIN_DIR}"
51+
install -m 0755 "${RUNNER_TEMP}/${ASSET}" "${BIN_DIR}/gitsign"
52+
echo "${BIN_DIR}" >> "${GITHUB_PATH}"
53+
54+
- name: Configure git x509 verifier
55+
run: |
56+
git config --global gpg.format x509
57+
git config --global gpg.x509.program gitsign
58+
59+
- name: Verify tag signature
60+
env:
61+
TAG_NAME: ${{ github.ref_name }}
62+
run: |
63+
tag_type="$(git cat-file -t "refs/tags/${TAG_NAME}")"
64+
if [ "${tag_type}" != "tag" ]; then
65+
echo "Expected annotated tag object for ${TAG_NAME}, found ${tag_type}"
66+
exit 1
67+
fi
68+
69+
git tag -v "${TAG_NAME}"

CONTRIBUTING.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Contributing
33
description: How to contribute to the Physical AI Toolchain
44
author: Microsoft Robotics-AI Team
5-
ms.date: 2026-02-11
5+
ms.date: 2026-03-11
66
ms.topic: how-to
77
keywords:
88
- contributing
@@ -212,6 +212,43 @@ After merging to `main`, release-please automatically creates a release PR with
212212

213213
For commit message format details, see [commit-message.instructions.md](.github/instructions/commit-message.instructions.md).
214214

215+
## Release Tag Signing
216+
217+
All release tags are required to be signed. Unsigned release tags are non-compliant with project policy.
218+
219+
This repository uses Sigstore `gitsign` with GitHub OIDC identity for keyless tag signing.
220+
221+
### Configure Signing
222+
223+
```bash
224+
# Install gitsign
225+
# https://docs.sigstore.dev/cosign/signing/gitsign/
226+
227+
# Configure git for keyless x509 signing
228+
git config --global gpg.format x509
229+
git config --global gpg.x509.program gitsign
230+
git config --global tag.gpgSign true
231+
```
232+
233+
### Create a Signed Release Tag
234+
235+
```bash
236+
git tag -s v1.0.0 -m "Release v1.0.0"
237+
git push origin v1.0.0
238+
```
239+
240+
### Verify a Signed Tag
241+
242+
```bash
243+
git fetch --tags
244+
git tag -v v1.0.0
245+
```
246+
247+
GitHub Actions validates signatures for pushed version tags (`v*`).
248+
249+
> [!IMPORTANT]
250+
> Maintainer GPG key distribution is not required for this repository because release tags are signed using keyless Sigstore identities.
251+
215252
## Deprecation Policy
216253

217254
External interfaces follow a formal deprecation lifecycle before removal. The policy covers shell script arguments, environment variables, Terraform variables and outputs, configuration schemas, and workflow templates.

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ Contributions are welcome. Whether fixing documentation or adding new training t
6565
2. Review [open issues](https://github.com/microsoft/physical-ai-toolchain/issues)
6666
3. See the [prerequisites](docs/contributing/prerequisites.md) for required tools
6767

68+
## Verifying Git Tags
69+
70+
All release tags are signed. Verify a release tag before using it in production workflows:
71+
72+
```bash
73+
git fetch --tags
74+
git tag -v v1.0.0
75+
```
76+
77+
This repository uses Sigstore `gitsign` keyless signing for release tags. For tag signing policy and maintainer guidance, see [CONTRIBUTING.md](CONTRIBUTING.md#release-tag-signing).
78+
6879
## Roadmap
6980

7081
See the [project roadmap](docs/contributing/ROADMAP.md) for priorities, timelines, and success metrics.

0 commit comments

Comments
 (0)