Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ on:
type: boolean
description: Publish the saved container image to the configured Staging registry after all gates pass.
default: false
attest_image:
type: boolean
description: Publish GitHub provenance and SBOM attestations to the target registry after image publication.
default: true
container_repository:
type: string
description: Fully qualified image repository without a tag.
Expand All @@ -39,6 +43,7 @@ jobs:
run-mac-tests: ${{ steps.vars.outputs.run-mac-tests }}
run-privileged-jobs: ${{ steps.vars.outputs.run-privileged-jobs }}
publish-image: ${{ steps.vars.outputs.publish-image }}
attest-image: ${{ steps.vars.outputs.attest-image }}
container-repository: ${{ steps.vars.outputs.container-repository }}
container-registry: ${{ steps.vars.outputs.container-registry }}
promote-dockerhub: ${{ steps.vars.outputs.promote-dockerhub }}
Expand All @@ -51,6 +56,7 @@ jobs:
EVENT_NAME: ${{ github.event_name }}
RUN_MAC_INPUT: ${{ inputs.run_mac_tests }}
PUBLISH_IMAGE_INPUT: ${{ inputs.publish_image }}
ATTEST_IMAGE_INPUT: ${{ inputs.attest_image }}
PROMOTE_DOCKERHUB_INPUT: ${{ inputs.promote_dockerhub }}
CONTAINER_REPOSITORY_INPUT: ${{ inputs.container_repository }}
DOCKERHUB_REPOSITORY_INPUT: ${{ inputs.dockerhub_repository }}
Expand All @@ -72,6 +78,12 @@ jobs:
echo "publish-image=false" >> "$GITHUB_OUTPUT"
fi

if [[ "$EVENT_NAME" == "workflow_dispatch" && "$ATTEST_IMAGE_INPUT" == "false" ]]; then
echo "attest-image=false" >> "$GITHUB_OUTPUT"
else
echo "attest-image=true" >> "$GITHUB_OUTPUT"
fi

if [[ "$EVENT_NAME" == "workflow_dispatch" && "$PROMOTE_DOCKERHUB_INPUT" == "true" ]]; then
if [[ "$PUBLISH_IMAGE_INPUT" != "true" ]]; then
echo "::error::promote_dockerhub requires publish_image to be true."
Expand Down Expand Up @@ -433,7 +445,7 @@ jobs:
run: docker logout "$REGISTRY"

container_promote_dockerhub:
if: ${{ always() && needs.init.outputs.publish-image == 'true' && needs.init.outputs.promote-dockerhub == 'true' && github.event_name != 'pull_request' && needs.container_attest.result == 'success' }}
if: ${{ always() && needs.init.outputs.publish-image == 'true' && needs.init.outputs.promote-dockerhub == 'true' && github.event_name != 'pull_request' && (needs.init.outputs.attest-image != 'true' || needs.container_attest.result == 'success') }}
name: promote-container-dockerhub
needs: [init, container_build, container_publish, container_attest]
runs-on: ubuntu-24.04
Expand Down Expand Up @@ -544,10 +556,11 @@ jobs:
run: docker logout "$REGISTRY"

container_attest_dockerhub:
if: ${{ always() && needs.init.outputs.publish-image == 'true' && needs.init.outputs.promote-dockerhub == 'true' && github.event_name != 'pull_request' && needs.container_promote_dockerhub.result == 'success' }}
if: ${{ always() && needs.init.outputs.attest-image == 'true' && needs.init.outputs.publish-image == 'true' && needs.init.outputs.promote-dockerhub == 'true' && github.event_name != 'pull_request' && needs.container_promote_dockerhub.result == 'success' }}
name: attest-container-dockerhub
needs: [init, container_build, container_promote_dockerhub]
runs-on: ubuntu-24.04
environment: Production

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Duplicate Production approval gate

When Production requires reviewers as documented, container_promote_dockerhub first passes that environment’s approval and publishes the image, then this downstream job creates a separate Production deployment that requires another approval before provenance and SBOM attestations are published, leaving the release unattested and the workflow incomplete until a second approval occurs.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/ci-pipeline.yml
Line: 563

Comment:
**Duplicate Production approval gate**

When `Production` requires reviewers as documented, `container_promote_dockerhub` first passes that environment’s approval and publishes the image, then this downstream job creates a separate Production deployment that requires another approval before provenance and SBOM attestations are published, leaving the release unattested and the workflow incomplete until a second approval occurs.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex

permissions:
contents: read
id-token: write
Expand All @@ -558,6 +571,7 @@ jobs:
IMAGE_REPOSITORY: docker.io/${{ needs.init.outputs.dockerhub-repository }}
IMAGE_DIGEST: ${{ needs.container_promote_dockerhub.outputs.image-digest }}
SBOM_ARTIFACT_NAME: ${{ needs.container_build.outputs.sbom-artifact-name }}
REGISTRY: docker.io
steps:
- name: Check out local actions
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -570,6 +584,13 @@ jobs:
name: ${{ env.SBOM_ARTIFACT_NAME }}
path: ${{ runner.temp }}/container-sbom

- name: Log in to Docker Hub for attestation
uses: ./.github/actions/docker-login
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Attest Docker Hub container provenance
uses: ./.github/actions/container-attest-provenance
with:
Expand All @@ -583,11 +604,19 @@ jobs:
subject-digest: ${{ env.IMAGE_DIGEST }}
sbom-path: ${{ runner.temp }}/container-sbom/sbom.spdx.json

- name: Log out of Docker Hub after attestation
if: ${{ always() }}
shell: bash
env:
REGISTRY: ${{ env.REGISTRY }}
run: docker logout "$REGISTRY"

container_attest:
if: ${{ always() && needs.init.outputs.publish-image == 'true' && github.event_name != 'pull_request' && needs.container_publish.result == 'success' }}
if: ${{ always() && needs.init.outputs.attest-image == 'true' && needs.init.outputs.publish-image == 'true' && github.event_name != 'pull_request' && needs.container_publish.result == 'success' }}
name: attest-container
needs: [init, build, container_build, container_publish]
runs-on: ubuntu-24.04
environment: Staging
permissions:
contents: read
id-token: write
Expand All @@ -598,6 +627,7 @@ jobs:
IMAGE_REPOSITORY: ${{ needs.init.outputs.container-repository }}
IMAGE_DIGEST: ${{ needs.container_publish.outputs.image-digest }}
SBOM_ARTIFACT_NAME: ${{ needs.container_build.outputs.sbom-artifact-name }}
REGISTRY: ${{ needs.init.outputs.container-registry }}
steps:
- name: Check out local actions
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -610,6 +640,13 @@ jobs:
name: ${{ env.SBOM_ARTIFACT_NAME }}
path: ${{ runner.temp }}/container-sbom

- name: Log in to image registry for attestation
uses: ./.github/actions/docker-login
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}

- name: Attest container provenance
uses: ./.github/actions/container-attest-provenance
with:
Expand All @@ -622,3 +659,10 @@ jobs:
subject-name: ${{ env.IMAGE_REPOSITORY }}
subject-digest: ${{ env.IMAGE_DIGEST }}
sbom-path: ${{ runner.temp }}/container-sbom/sbom.spdx.json

- name: Log out of image registry after attestation
if: ${{ always() }}
shell: bash
env:
REGISTRY: ${{ env.REGISTRY }}
run: docker logout "$REGISTRY"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ Use a manually dispatched run with `publish_image: true` to publish the saved ta

To keep the exact same run artifact, set both `publish_image: true` and `promote_dockerhub: true` on the manual dispatch. The workflow publishes to Staging and then pauses at the protected `Production` environment, allowing you to verify JCR before approving Docker Hub publication. Configure required reviewers for `Production` and add the exact `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` secret names to that environment. `DOCKERHUB_TOKEN` must be a Docker Hub access token with permission to push to the target namespace; a normal account password is not expected. The default Docker Hub repository is `codebeltnet/web-cdn-origin`; override `dockerhub_repository` when needed. A run with `promote_dockerhub: false` intentionally stops after Staging; enabling it on a later run creates a new build artifact.

This is a promotion of one immutable build artifact: the Docker Hub job downloads the same `docker save` tarball, uses `docker load`, retags it for Docker Hub, and pushes both tags. It does not rebuild or pull a new image. The build job carries the image and SBOM artifact names to downstream jobs, so rerunning only a failed publish or attestation job reuses the artifact from the successful build attempt. Digest gates verify that the SemVer and TrunkVer tags point to the same image in each registry and that the Docker Hub digest matches Staging. Attestation jobs publish GitHub build-provenance and SBOM attestations for the pushed digest, so each target registry must accept OCI attestation artifacts.
This is a promotion of one immutable build artifact: the Docker Hub job downloads the same `docker save` tarball, uses `docker load`, retags it for Docker Hub, and pushes both tags. It does not rebuild or pull a new image. The build job carries the image and SBOM artifact names to downstream jobs, so rerunning only a failed publish or attestation job reuses the artifact from the successful build attempt. Digest gates verify that the SemVer and TrunkVer tags point to the same image in each registry and that the Docker Hub digest matches Staging. Attestation jobs publish GitHub build-provenance and SBOM attestations for the pushed digest, so each target registry must accept OCI attestation artifacts. Attestation is enabled by default; set the manual `attest_image` input to `false` only as an explicit release fallback when registry attestation is unavailable. This skips both attestation jobs and removes the attestation-success gate from Docker Hub promotion, so the resulting release has no registry-published GitHub attestations.

## Kubernetes

Expand Down
Loading