diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6e3b883..a036ec1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -177,9 +177,25 @@ jobs: with: path: _caller repository: ${{ github.event.repository.full_name }} + ref: refs/tags/${{ inputs.tag }} fetch-depth: 0 persist-credentials: false + - name: Verify caller checkout matches release tag + working-directory: _caller + shell: bash + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + tag_commit="$(git rev-list -n 1 "refs/tags/$RELEASE_TAG")" + head_commit="$(git rev-parse HEAD)" + if [ "$head_commit" != "$tag_commit" ]; then + echo "::error::Checked out $head_commit but refs/tags/$RELEASE_TAG resolves to $tag_commit" + exit 1 + fi + echo "Verified $RELEASE_TAG at $head_commit" + - name: Checkout connector workflows uses: actions/checkout@v5 with: @@ -429,9 +445,25 @@ jobs: with: path: _caller repository: ${{ github.event.repository.full_name }} + ref: refs/tags/${{ inputs.tag }} fetch-depth: 0 persist-credentials: false + - name: Verify caller checkout matches release tag + working-directory: _caller + shell: bash + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + tag_commit="$(git rev-list -n 1 "refs/tags/$RELEASE_TAG")" + head_commit="$(git rev-parse HEAD)" + if [ "$head_commit" != "$tag_commit" ]; then + echo "::error::Checked out $head_commit but refs/tags/$RELEASE_TAG resolves to $tag_commit" + exit 1 + fi + echo "Verified $RELEASE_TAG at $head_commit" + - name: Checkout connector workflows uses: actions/checkout@v5 with: @@ -721,9 +753,25 @@ jobs: with: path: _caller repository: ${{ github.event.repository.full_name }} + ref: refs/tags/${{ inputs.tag }} fetch-depth: 0 persist-credentials: false + - name: Verify caller checkout matches release tag + working-directory: _caller + shell: bash + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + tag_commit="$(git rev-list -n 1 "refs/tags/$RELEASE_TAG")" + head_commit="$(git rev-parse HEAD)" + if [ "$head_commit" != "$tag_commit" ]; then + echo "::error::Checked out $head_commit but refs/tags/$RELEASE_TAG resolves to $tag_commit" + exit 1 + fi + echo "Verified $RELEASE_TAG at $head_commit" + - name: Checkout connector workflows if: inputs.docker == true || inputs.lambda == true uses: actions/checkout@v5 @@ -1207,8 +1255,28 @@ jobs: uses: actions/checkout@v5 with: path: _connector + repository: ${{ github.event.repository.full_name }} + ref: refs/tags/${{ inputs.tag }} + fetch-depth: 0 persist-credentials: false + - name: Verify connector checkout matches release tag + id: verify-connector-checkout + working-directory: _connector + shell: bash + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + tag_commit="$(git rev-list -n 1 "refs/tags/$RELEASE_TAG")" + head_commit="$(git rev-parse HEAD)" + if [ "$head_commit" != "$tag_commit" ]; then + echo "::error::Checked out $head_commit but refs/tags/$RELEASE_TAG resolves to $tag_commit" + exit 1 + fi + echo "commit_sha=$head_commit" >> "$GITHUB_OUTPUT" + echo "Verified $RELEASE_TAG at $head_commit" + - name: Read connector documentation id: read-docs run: | @@ -1288,7 +1356,7 @@ jobs: -name "${{ github.event.repository.name }}" \ -version "${{ inputs.tag }}" \ -repository-url "https://github.com/${{ github.repository }}" \ - -commit-sha "${{ github.sha }}" \ + -commit-sha "${{ steps.verify-connector-checkout.outputs.commit_sha }}" \ -workflow-run-id "${{ github.run_id }}" \ -registry-url "https://dist.conductorone.com" \ $DOCS_FLAG \ diff --git a/docs/release-workflow.md b/docs/release-workflow.md index e7c97be..93f84fe 100644 --- a/docs/release-workflow.md +++ b/docs/release-workflow.md @@ -100,6 +100,13 @@ Post-release validation (non-blocking): ## Security Properties +### Release Source Identity + +Release jobs check out caller code from `refs/tags/` and verify the +checked-out commit matches the tag target before building artifacts or recording +registry metadata. This prevents a release run from publishing artifacts for one +commit while labeling them as a different tag. + ### Keyless Signing All signatures use Sigstore's keyless signing: