diff --git a/.github/workflows/internal-image.yml b/.github/workflows/internal-image.yml index 5b3d507cd..4a91609f3 100644 --- a/.github/workflows/internal-image.yml +++ b/.github/workflows/internal-image.yml @@ -61,6 +61,7 @@ jobs: type=sha,prefix=sha-,format=long - name: Build and push image + id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a with: context: . @@ -71,3 +72,22 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=codex-lb-internal cache-to: type=gha,mode=max,scope=codex-lb-internal + + - name: Request CHEK Beijing mirror + env: + GH_PAT: ${{ secrets.GH_PAT }} + IMAGE_DIGEST: ${{ steps.build.outputs.digest }} + run: | + test -n "${GH_PAT}" + test -n "${IMAGE_DIGEST}" + payload=$(jq -nc \ + --arg sha "${GITHUB_SHA}" \ + --arg digest "${IMAGE_DIGEST}" \ + '{event_type:"codex-lb-image-published",client_payload:{sha:$sha,digest:$digest}}') + curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${GH_PAT}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/chekdata/ops-bootstrap/dispatches \ + --data "${payload}" diff --git a/openspec/changes/publish-chek-internal-images/proposal.md b/openspec/changes/publish-chek-internal-images/proposal.md index aa4729c0b..c8bb6673b 100644 --- a/openspec/changes/publish-chek-internal-images/proposal.md +++ b/openspec/changes/publish-chek-internal-images/proposal.md @@ -10,12 +10,14 @@ repository-owned image publisher. ## What Changes - Add a focused GitHub Actions workflow that publishes the fork to - `ghcr.io/chekdata/codex-lb` after a push to `main` or an explicit manual run. + `ghcr.io/chekdata/codex-lb`, then requests an authenticated mirror into + CHEK's Beijing production registry after a push to `main` or an explicit + manual run. - Publish both supported Linux architectures in one manifest. - Publish an immutable full-SHA tag for GitOps while granting the workflow only read-content and write-package access; do not publish a mutable `main` tag. -- Fail closed before building when the full-SHA tag already exists or the - registry cannot prove that it is absent. +- Keep production registry credentials in the private operations repository; + the public fork sends only the reviewed commit SHA and built image digest. - Serialize all runs for the same commit across branch and tag aliases so the absence check and first publication cannot race. @@ -23,5 +25,6 @@ repository-owned image publisher. - Affected capability: `github-automation` - Affected automation: `.github/workflows/internal-image.yml` -- Production manifests can pin a reviewed `sha-*` image without depending on - upstream publishing permissions or abbreviated-tag collisions. +- Production manifests can pin a reviewed `sha-*` image from the in-region + registry without giving its public source repository production registry + credentials or depending on cross-region runtime pulls. diff --git a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md index 2a9d0d354..9b106c6ab 100644 --- a/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md +++ b/openspec/changes/publish-chek-internal-images/specs/github-automation/spec.md @@ -3,18 +3,25 @@ ### Requirement: CHEK fork publishes internally owned container images The `chekdata/codex-lb` repository SHALL build and publish a container image to -`ghcr.io/chekdata/codex-lb` after changes land on `main` and when an operator -explicitly dispatches the workflow. The published manifest MUST support -`linux/amd64` and `linux/arm64`, MUST include an immutable `sha-` -tag, and MUST NOT publish a mutable `main` tag. Production GitOps consumers -MUST be able to select the immutable tag. - -Before publishing, the workflow MUST prove that the full-SHA tag does not -already exist. It MUST fail without building when the tag exists and MUST fail -closed when a registry or network error prevents that absence check. It MUST -NOT overwrite a previously published full-SHA tag. Runs selecting the same -commit through different branch or tag refs MUST be serialized by the full -commit SHA and MUST NOT cancel the run that currently owns publication. +`ghcr.io/chekdata/codex-lb`, then request an authenticated private operations +workflow to mirror that image into CHEK's Beijing production registry, after +changes land on `main` and when an operator explicitly dispatches the workflow. +Each published manifest MUST support `linux/amd64` and `linux/arm64`, MUST +include an immutable `sha-` tag, and MUST NOT publish a mutable +`main` tag. Production GitOps consumers MUST be able to select the immutable +in-region tag. + +Before publishing, the source workflow MUST prove that the GHCR full-SHA tag +does not already exist and MUST fail closed when a registry or network error +prevents that absence check. It MUST NOT overwrite a previously published +full-SHA tag. After the build succeeds, it MUST dispatch only the selected full +commit SHA and resulting image digest to the private operations repository; +production registry write credentials MUST NOT be configured in the public +fork. The private mirror workflow MUST verify that the selected commit belongs +to the `main` history and MUST use repository-scoped, temporary registry +credentials. Runs selecting the same commit through different branch or tag +refs MUST be serialized by the full commit SHA and MUST NOT cancel the run that +currently owns publication. The workflow MUST use pinned action revisions and MUST limit its repository permissions to reading contents and writing packages. Pull request events MUST @@ -24,6 +31,8 @@ NOT publish images. - **WHEN** a reviewed commit lands on `main` - **THEN** the workflow publishes `ghcr.io/chekdata/codex-lb:sha-` +- **AND** it requests the private operations workflow to mirror that exact SHA + and digest to CHEK's Beijing production registry - **AND** the image manifest supports `linux/amd64` and `linux/arm64` - **AND** the run does not publish a mutable `main` tag @@ -40,16 +49,23 @@ NOT publish images. - **THEN** the workflow builds and publishes the selected commit with its immutable full-SHA tag -#### Scenario: Repeated publication cannot change an immutable tag +#### Scenario: Production credentials stay private -- **GIVEN** the selected commit's full-SHA tag already exists in GHCR -- **WHEN** the workflow is rerun or manually dispatched for that commit -- **THEN** it fails before the image build -- **AND** the existing tag is not overwritten +- **WHEN** the public fork publishes a reviewed image +- **THEN** it sends only the full commit SHA and built digest to the private + operations repository +- **AND** the public fork does not receive production registry write credentials + +#### Scenario: Private mirror validates production provenance + +- **WHEN** the private operations workflow receives a mirror request +- **THEN** it verifies the commit belongs to the `chekdata/codex-lb` main history +- **AND** it verifies the GHCR tag resolves to the dispatched digest +- **AND** it uses temporary registry credentials scoped to `prod/codex-lb` #### Scenario: Registry uncertainty fails closed -- **GIVEN** the workflow cannot determine whether the full-SHA tag exists +- **GIVEN** the workflow cannot determine whether either full-SHA tag exists - **WHEN** the pre-publish check encounters a registry or network error - **THEN** the workflow fails without publishing diff --git a/openspec/changes/publish-chek-internal-images/tasks.md b/openspec/changes/publish-chek-internal-images/tasks.md index 2cdbc3c5b..bd214ff1e 100644 --- a/openspec/changes/publish-chek-internal-images/tasks.md +++ b/openspec/changes/publish-chek-internal-images/tasks.md @@ -8,9 +8,15 @@ - [x] 1.4 Refuse to overwrite an existing full-SHA tag and fail closed when its registry state cannot be confirmed - [x] 1.5 Serialize publication by full commit SHA across branch and tag refs +- [x] 1.6 Request a private operations workflow to mirror the reviewed GHCR + SHA and digest into CHEK's Beijing production registry +- [x] 1.7 Keep production registry credentials out of the public fork +- [x] 1.8 Require main-history provenance and scoped temporary credentials in + the private mirror workflow ## 2. Verification - [x] 2.1 Validate the workflow YAML and repository action policy - [x] 2.2 Run strict OpenSpec validation -- [ ] 2.3 Merge the focused PR and verify the first main-branch image manifest +- [x] 2.3 Merge the focused GHCR PR and verify the first main-branch image manifest +- [ ] 2.4 Merge the Beijing registry enhancement and verify both manifests