From 758963ac2fba4342f2d03188cb83d6ac380970f4 Mon Sep 17 00:00:00 2001 From: "XXV.CC" Date: Mon, 27 Jul 2026 20:15:10 +0800 Subject: [PATCH] ci: isolate release root test workspace --- .github/workflows/mirror-release.yml | 2 +- .github/workflows/release.yml | 25 +++++++++++++++----- CHANGELOG.md | 7 +++++- docs/releasing.md | 18 +++++++------- internal/selfmanage/release_pipeline_test.go | 17 +++++++++++++ 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/.github/workflows/mirror-release.yml b/.github/workflows/mirror-release.yml index f75a5f5..faad884 100644 --- a/.github/workflows/mirror-release.yml +++ b/.github/workflows/mirror-release.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: inputs: tag: - description: Immutable release tag to mirror, for example v2.8.0 + description: Immutable release tag to mirror, for example v2.8.1 required: true type: string diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ff6903..0f3cfc7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,10 +77,22 @@ jobs: GO111MODULE: 'on' run: | set -Eeuo pipefail - mkdir -p "$GOCACHE" "$GOMODCACHE" "$GOPATH" "$GOTMPDIR" \ - "${RUNNER_TEMP}/lta-root-test-cache" "${RUNNER_TEMP}/lta-root-module-cache" \ - "${RUNNER_TEMP}/lta-root-go-path" "${RUNNER_TEMP}/lta-root-go-tmp" + mkdir -p "$GOCACHE" "$GOMODCACHE" "$GOPATH" "$GOTMPDIR" + root_go_root="$(sudo mktemp -d /tmp/lta-release-root-go.XXXXXXXXXX)" + [[ "$root_go_root" =~ ^/tmp/lta-release-root-go\.[A-Za-z0-9]{10}$ \ + && -d "$root_go_root" && ! -L "$root_go_root" \ + && "$(sudo stat -Lc '%u %g %a' -- "$root_go_root")" == '0 0 700' ]] \ + || { echo "root Go workspace has unsafe metadata" >&2; exit 1; } + readonly root_go_root + sudo install -d -o 0 -g 0 -m 0700 \ + "$root_go_root/gocache" "$root_go_root/gomodcache" \ + "$root_go_root/gopath" "$root_go_root/gotmp" index_file="$(mktemp)" + cleanup() { + rm -f -- "$index_file" || : + sudo rm -rf -- "$root_go_root" || : + } + trap cleanup EXIT git ls-files -s -z > "$index_file" while IFS= read -r -d '' index_entry; do mode="${index_entry%% *}" @@ -91,11 +103,12 @@ jobs: go vet -mod=readonly -printf.funcs=printf,errorf,warnf ./... env GOROOT= GOENV=off GOTOOLCHAIN=local GOFLAGS= GOWORK=off \ go test -mod=readonly -count=1 -race ./... - sudo -E env "PATH=$PATH" GOROOT= GOENV=off GOTOOLCHAIN=local GOFLAGS= GOWORK=off \ + sudo env "PATH=$PATH" HOME=/root TMPDIR=/tmp \ + GOROOT= GOENV=off GOTOOLCHAIN=local GOFLAGS= GOWORK=off \ GO111MODULE=on GOEXPERIMENT= GOFIPS140=off GOTELEMETRY=off GOAUTH=off GOVCS='*:off' \ GOPROXY="$GOPROXY" GOSUMDB="$GOSUMDB" GONOSUMDB= GOPRIVATE= GONOPROXY= GOINSECURE= \ - GOCACHE="${RUNNER_TEMP}/lta-root-test-cache" GOMODCACHE="${RUNNER_TEMP}/lta-root-module-cache" \ - GOPATH="${RUNNER_TEMP}/lta-root-go-path" GOTMPDIR="${RUNNER_TEMP}/lta-root-go-tmp" \ + GOCACHE="$root_go_root/gocache" GOMODCACHE="$root_go_root/gomodcache" \ + GOPATH="$root_go_root/gopath" GOTMPDIR="$root_go_root/gotmp" \ go test -mod=readonly -count=1 -race -tags integration ./... test -z "$(gofmt -l .)" sh -n scripts/install.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 4537edf..70b75b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,12 @@ All notable changes to this project are documented here. -## v2.8.0 - 2026-07-27 +## v2.8.1 - 2026-07-27 + +- Keep the release-only root integration workspace below a fresh root-owned + `/tmp` directory. The signed `v2.8.0` tag exposed the runner-ownership gate + before any binary, draft, or Release was created and remains only as an audit + record; no published v2.8.0 artifacts exist. - Make account creation and revocation fail closed across UID reuse and process races. New users remain explicitly pending until their UID is durably diff --git a/docs/releasing.md b/docs/releasing.md index 6a56e38..8bc56f0 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -419,10 +419,10 @@ git -c user.name='XXV.CC' \ -c user.signingkey="${TAG_SIGNING_FPR}!" \ -c gpg.format=openpgp \ -c gpg.program=/usr/bin/gpg \ - tag -s v2.8.0 "$RELEASE_COMMIT" -m 'linux-temp-admin v2.8.0' + tag -s v2.8.1 "$RELEASE_COMMIT" -m 'linux-temp-admin v2.8.1' git -c gpg.format=openpgp -c gpg.program=/usr/bin/gpg \ - verify-tag --raw v2.8.0 -git push origin v2.8.0 + verify-tag --raw v2.8.1 +git push origin v2.8.1 ``` Before pushing, the `VALIDSIG` record from `verify-tag --raw` must identify the @@ -485,7 +485,7 @@ printf '\n' >/dev/tty || fail "GH_TOKEN must be one non-empty token without whitespace" export GH_TOKEN exec /opt/lta-release-tools/prepare-release.sh \ - v2.8.0 /srv/linux-temp-admin /srv/release-transfer/v2.8.0-prepared + v2.8.1 /srv/linux-temp-admin /srv/release-transfer/v2.8.1-prepared LTA_PREPARE_RELEASE ``` @@ -505,7 +505,7 @@ the candidate or transfer media: LTA_SIGN_KEY=/offline/keys/release-v1.key LTA_TRUSTED_SIGNER=/opt/lta-release-tools/lta-release LTA_TRUSTED_SIGNER_SHA256='' -LTA_EXPECTED_TAG=v2.8.0 +LTA_EXPECTED_TAG=v2.8.1 LTA_EXPECTED_COMMIT='' LTA_EXPECTED_PREPARED_MANIFEST_SHA256='' LTA_EXPECTED_RELEASE_SIGNER_PUBKEY='' @@ -517,7 +517,7 @@ LTA_EXPECTED_RELEASE_SIGNER_PUBKEY='/dev/tty || fail "GH_TOKEN must be one non-empty token without whitespace" export GH_TOKEN exec /opt/lta-release-tools/publish-release.sh \ - /srv/release-transfer/v2.8.0-signed /srv/linux-temp-admin + /srv/release-transfer/v2.8.1-signed /srv/linux-temp-admin LTA_PUBLISH_RELEASE ``` @@ -633,7 +633,7 @@ noncanonical published stable tag, excludes the failed `TAG`, and verifies the exact resulting Latest state: ```bash -TAG=v2.8.0 # the failed release; verify this value before running +TAG=v2.8.1 # the failed release; verify this value before running /usr/bin/sudo /usr/bin/env -i \ HOME=/root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin LC_ALL=C \ TAG="$TAG" /bin/bash -p <<'LTA_LATEST_RECOVERY' @@ -992,7 +992,7 @@ the release audit/signing record and a separate authenticated channel, then run: ```bash INSTALLER_COMMIT='replace-with-the-audited-40-hex-commit' INSTALLER_SHA256='replace-with-the-independent-64-hex-script-hash' -LTA_RELEASE_TAG='v2.8.0' +LTA_RELEASE_TAG='v2.8.1' /usr/bin/sudo /usr/bin/env -i \ HOME=/root PATH=/usr/sbin:/usr/bin:/sbin:/bin LC_ALL=C \ INSTALLER_COMMIT="$INSTALLER_COMMIT" INSTALLER_SHA256="$INSTALLER_SHA256" \ diff --git a/internal/selfmanage/release_pipeline_test.go b/internal/selfmanage/release_pipeline_test.go index 32aead4..c6fa916 100644 --- a/internal/selfmanage/release_pipeline_test.go +++ b/internal/selfmanage/release_pipeline_test.go @@ -73,6 +73,23 @@ func TestReleaseWriterIsSeparatedFromCandidateWorkflow(t *testing.T) { strings.Count(stage, "timeout-minutes:") != 2 { t.Fatal("release workflows must bound every release-critical job") } + for _, required := range []string{ + `root_go_root="$(sudo mktemp -d /tmp/lta-release-root-go.XXXXXXXXXX)"`, + `sudo stat -Lc '%u %g %a' -- "$root_go_root"`, + `sudo install -d -o 0 -g 0 -m 0700`, + `sudo env "PATH=$PATH" HOME=/root TMPDIR=/tmp`, + `GOCACHE="$root_go_root/gocache"`, + `GOMODCACHE="$root_go_root/gomodcache"`, + `GOPATH="$root_go_root/gopath" GOTMPDIR="$root_go_root/gotmp"`, + } { + if !strings.Contains(release, required) { + t.Fatalf("release root test gate is missing safe root workspace guard %q", required) + } + } + if strings.Contains(release, `${RUNNER_TEMP}/lta-root-`) || + strings.Contains(release, `sudo -E env "PATH=$PATH"`) { + t.Fatal("release root test gate inherits or stores privileged Go state below the runner account") + } } func TestMirrorReleaseWorkflowPublishesVerifiedImmutableContentFailClosed(t *testing.T) {