From 262ba17f26caa175ebef19b5090cb23042ec76f0 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:28:55 +0000 Subject: [PATCH 1/2] ci: upload release objects without listing the bucket Use direct authenticated object PUTs instead of gcloud cp, which probes new destinations with a bucket listing. Preserve generation-zero preconditions, verify transport MD5, and upload the manifest last. > Xum prepared this change on behalf of @ibetitsmike. Signed-off-by: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> --- .github/workflows/upload-release.yml | 24 ++++++++++++++++-------- RELEASING.md | 8 +++++--- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.github/workflows/upload-release.yml b/.github/workflows/upload-release.yml index aaeb6573..cf95b24a 100644 --- a/.github/workflows/upload-release.yml +++ b/.github/workflows/upload-release.yml @@ -29,9 +29,6 @@ jobs: echo "Invalid release tag: $RELEASE_TAG" >&2 exit 1 fi - - uses: actions/checkout@v4 - with: - persist-credentials: false - name: Download and verify release assets shell: bash env: @@ -53,14 +50,25 @@ jobs: cd artifacts sha256sum "${artifacts[@]}" | diff - SHA256SUMS - uses: google-github-actions/auth@v3 + id: gcloud_auth with: workload_identity_provider: ${{ vars.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }} service_account: ${{ vars.GCP_CODE_SIGNING_SERVICE_ACCOUNT }} - - uses: google-github-actions/setup-gcloud@v3 + token_format: access_token + create_credentials_file: false - name: Upload release assets shell: bash + env: + GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }} run: | - gcloud storage cp --if-generation-match=0 artifacts/mutagen-* \ - "gs://coder-desktop/mutagen/$RELEASE_TAG/" - gcloud storage cp --if-generation-match=0 artifacts/SHA256SUMS \ - "gs://coder-desktop/mutagen/$RELEASE_TAG/" + for artifact in artifacts/mutagen-* artifacts/SHA256SUMS; do + content_md5="$(openssl dgst -md5 -binary "$artifact" | openssl base64 -A)" + curl --fail-with-body --silent --show-error \ + --header "Authorization: Bearer $GCLOUD_ACCESS_TOKEN" \ + --header "Content-Type: application/octet-stream" \ + --header "x-goog-if-generation-match: 0" \ + --header "Content-MD5: $content_md5" \ + --upload-file "$artifact" \ + "https://storage.googleapis.com/coder-desktop/mutagen/$RELEASE_TAG/${artifact##*/}" + printf 'Uploaded gs://coder-desktop/mutagen/%s/%s\n' "$RELEASE_TAG" "${artifact##*/}" + done diff --git a/RELEASING.md b/RELEASING.md index d346e725..1adcb24a 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -30,9 +30,11 @@ Desktop. All of them are produced by the to `gs://coder-desktop/mutagen//` without rebuilding or re-signing them. The bucket permissions described below must be in place first. - Uploads refuse to overwrite existing objects. Re-running a successful - upload fails; after a partial failure, an operator must inspect and remove - the partial upload before retrying. + Direct object uploads avoid bucket listing permissions and use generation + preconditions to refuse overwrites. Cloud Storage checks each upload against + its `Content-MD5` header; `SHA256SUMS` is uploaded last. Re-running a + successful upload fails; after a partial failure, an operator must inspect + and remove the partial upload before retrying. 4. Bump the Mutagen version in Coder Desktop: `$mutagenVersion` in `scripts/Get-Mutagen.ps1` (coder/coder-desktop-windows) and From 114c024f6aa1ab95bb7bae67d45b7d7b11bc5a32 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Mon, 14 Sep 2026 10:35:03 +0000 Subject: [PATCH 2/2] ci: restore gcloud release uploads Signed-off-by: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> --- .github/workflows/upload-release.yml | 24 ++++++++---------------- RELEASING.md | 17 +++++++++++------ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/.github/workflows/upload-release.yml b/.github/workflows/upload-release.yml index cf95b24a..aaeb6573 100644 --- a/.github/workflows/upload-release.yml +++ b/.github/workflows/upload-release.yml @@ -29,6 +29,9 @@ jobs: echo "Invalid release tag: $RELEASE_TAG" >&2 exit 1 fi + - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Download and verify release assets shell: bash env: @@ -50,25 +53,14 @@ jobs: cd artifacts sha256sum "${artifacts[@]}" | diff - SHA256SUMS - uses: google-github-actions/auth@v3 - id: gcloud_auth with: workload_identity_provider: ${{ vars.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }} service_account: ${{ vars.GCP_CODE_SIGNING_SERVICE_ACCOUNT }} - token_format: access_token - create_credentials_file: false + - uses: google-github-actions/setup-gcloud@v3 - name: Upload release assets shell: bash - env: - GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }} run: | - for artifact in artifacts/mutagen-* artifacts/SHA256SUMS; do - content_md5="$(openssl dgst -md5 -binary "$artifact" | openssl base64 -A)" - curl --fail-with-body --silent --show-error \ - --header "Authorization: Bearer $GCLOUD_ACCESS_TOKEN" \ - --header "Content-Type: application/octet-stream" \ - --header "x-goog-if-generation-match: 0" \ - --header "Content-MD5: $content_md5" \ - --upload-file "$artifact" \ - "https://storage.googleapis.com/coder-desktop/mutagen/$RELEASE_TAG/${artifact##*/}" - printf 'Uploaded gs://coder-desktop/mutagen/%s/%s\n' "$RELEASE_TAG" "${artifact##*/}" - done + gcloud storage cp --if-generation-match=0 artifacts/mutagen-* \ + "gs://coder-desktop/mutagen/$RELEASE_TAG/" + gcloud storage cp --if-generation-match=0 artifacts/SHA256SUMS \ + "gs://coder-desktop/mutagen/$RELEASE_TAG/" diff --git a/RELEASING.md b/RELEASING.md index 1adcb24a..744a3b40 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -30,11 +30,10 @@ Desktop. All of them are produced by the to `gs://coder-desktop/mutagen//` without rebuilding or re-signing them. The bucket permissions described below must be in place first. - Direct object uploads avoid bucket listing permissions and use generation - preconditions to refuse overwrites. Cloud Storage checks each upload against - its `Content-MD5` header; `SHA256SUMS` is uploaded last. Re-running a - successful upload fails; after a partial failure, an operator must inspect - and remove the partial upload before retrying. + Uploads use `gcloud storage cp` with generation preconditions to refuse + overwrites, and upload `SHA256SUMS` last. Re-running a successful upload + fails; after a partial failure, an operator must inspect and remove the + partial upload before retrying. 4. Bump the Mutagen version in Coder Desktop: `$mutagenVersion` in `scripts/Get-Mutagen.ps1` (coder/coder-desktop-windows) and @@ -75,8 +74,14 @@ account needs `roles/storage.objectCreator` on the `coder-desktop` bucket, with an IAM condition limiting writes to objects whose resource name starts with `projects/_/buckets/coder-desktop/objects/mutagen/`. +The account also needs `roles/storage.objectViewer` on the bucket so +`gcloud storage cp` can inspect destination objects and list the bucket. +This read binding must be unconditional: object-prefix conditions cannot +restrict bucket listing. It permits reading and listing the whole bucket, +while writes remain limited to `mutagen/`. Neither role permits deletion. + The `coder-desktop` bucket already exists in project `coder-ci`. Its bucket resource, IAM policy, and import declarations are managed in coder/gcp under -`projects/production/coder-ci`. Add the uploader binding to that existing +`projects/production/coder-ci`. Add both uploader bindings to that existing policy before running the upload workflow; do not create another bucket. No service account key or new GitHub secret is needed.