ci: evaluate one-job Windows test workers #516
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: container-release | |
| on: | |
| pull_request: | |
| paths: | |
| - .dockerignore | |
| - .github/workflows/container-release.yml | |
| - Dockerfile | |
| - Dockerfile.dockerignore | |
| - compose.yaml | |
| - compose.apparmor.yaml | |
| - docker/** | |
| - sdk/typescript/** | |
| push: | |
| tags: | |
| - "container-v*" | |
| workflow_dispatch: | |
| concurrency: | |
| group: container-release-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'publish' }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| validate: | |
| if: github.repository == 'openai/codex-security' | |
| name: validate-linux-${{ matrix.architecture }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - architecture: amd64 | |
| runner: ubuntu-24.04 | |
| - architecture: arm64 | |
| runner: ubuntu-24.04-arm | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 | |
| - name: Build native customer image | |
| uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 | |
| env: | |
| DOCKER_BUILD_RECORD_UPLOAD: "false" | |
| with: | |
| context: . | |
| load: true | |
| platforms: linux/${{ matrix.architecture }} | |
| push: false | |
| tags: codex-security:release-candidate | |
| cache-from: type=gha,scope=codex-security-${{ matrix.architecture }} | |
| cache-to: ${{ github.event_name != 'pull_request' && format('type=gha,mode=max,scope=codex-security-{0}', matrix.architecture) || '' }} | |
| - name: Verify native image and bundled scanner | |
| env: | |
| EXPECTED_ARCHITECTURE: ${{ matrix.architecture }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| actual_architecture="$(docker image inspect --format '{{.Architecture}}' codex-security:release-candidate)" | |
| if [[ "$actual_architecture" != "$EXPECTED_ARCHITECTURE" ]]; then | |
| echo "Expected a native $EXPECTED_ARCHITECTURE image; found $actual_architecture." >&2 | |
| exit 1 | |
| fi | |
| docker run --rm codex-security:release-candidate --version | |
| docker run --rm codex-security:release-candidate bulk-scan --help | |
| docker run --rm codex-security:release-candidate info --json | |
| [[ "$(docker run --rm --entrypoint id codex-security:release-candidate -u)" == 10001 ]] | |
| - name: Verify host-aware AppArmor sandbox selection | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker run --rm --entrypoint /bin/sh codex-security:release-candidate -ec ' | |
| command_directory="$(mktemp -d)" | |
| trap '\''rm -rf "$command_directory"'\'' EXIT | |
| printf "%s\\n" "#!/bin/sh" '\''printf "%s\\n" "$@"'\'' > "$command_directory/codex-security" | |
| chmod 755 "$command_directory/codex-security" | |
| actual="$( | |
| PATH="$command_directory:$PATH" \ | |
| /usr/local/bin/codex-security-entrypoint \ | |
| bulk-scan /input/repositories.csv --output-dir /output | |
| )" | |
| restricted_user_namespaces= | |
| if [ -r /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]; then | |
| IFS= read -r restricted_user_namespaces \ | |
| < /proc/sys/kernel/apparmor_restrict_unprivileged_userns || true | |
| fi | |
| apparmor_profile= | |
| if [ -r /proc/self/attr/current ]; then | |
| IFS= read -r apparmor_profile < /proc/self/attr/current || true | |
| fi | |
| if [ "$restricted_user_namespaces" = 1 ] && | |
| [ "$apparmor_profile" != "codex-security-container (enforce)" ]; then | |
| printf "%s\\n" "$actual" | grep -Fxq features.use_legacy_landlock=true | |
| elif printf "%s\\n" "$actual" | grep -Fxq features.use_legacy_landlock=true; then | |
| printf "%s\\n" "Landlock must not be forced when the preferred sandbox is available." >&2 | |
| exit 1 | |
| fi | |
| ' | |
| - name: Verify hardened Codex command sandbox | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| command=( | |
| docker run --rm | |
| --cap-drop ALL | |
| --security-opt no-new-privileges | |
| --security-opt "seccomp=$GITHUB_WORKSPACE/docker/codex-security-seccomp.json" | |
| --entrypoint node | |
| codex-security:release-candidate | |
| /usr/local/lib/node_modules/@openai/codex-security/node_modules/@openai/codex/bin/codex.js | |
| ) | |
| if output="$("${command[@]}" sandbox /usr/bin/true 2>&1)"; then | |
| printf '%s\n' "$output" | |
| elif grep -Eq 'bwrap: (Failed to make / slave: Permission denied|loopback: Failed RTM_NEW(ADDR|LINK): Operation not permitted|setting up uid map: Permission denied|No permissions to create a new namespace)' <<< "$output"; then | |
| echo '::notice::This Docker host blocks nested Bubblewrap namespaces; verifying the supported Landlock fallback.' | |
| "${command[@]}" sandbox --enable use_legacy_landlock /usr/bin/true | |
| else | |
| printf 'The hardened Codex sandbox failed unexpectedly:\n%s\n' "$output" >&2 | |
| exit 1 | |
| fi | |
| - name: Verify host-scoped Git credentials | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker run --rm \ | |
| --entrypoint /bin/sh \ | |
| --env GH_TOKEN=SYNTHETIC_GITHUB_TOKEN \ | |
| codex-security:release-candidate \ | |
| -ec 'actual="$(printf "protocol=https\nhost=github.com\n\n" | /usr/local/bin/codex-security-git-credential get)"; test "$actual" = "$(printf "username=x-access-token\npassword=SYNTHETIC_GITHUB_TOKEN")"; test -z "$(printf "protocol=https\nhost=untrusted.example\n\n" | /usr/local/bin/codex-security-git-credential get)"' | |
| - name: Verify hardened customer Compose configuration | |
| env: | |
| CODEX_SECURITY_IMAGE: codex-security:release-candidate | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p results state | |
| chmod 700 results state | |
| printf 'id,repository,revision\n' > repositories.csv | |
| CODEX_SECURITY_USER="$(id -u):$(id -g)" | |
| export CODEX_SECURITY_USER | |
| docker compose config --quiet | |
| docker compose run --rm codex-security --version | |
| if output="$(docker compose run --rm codex-security 2>&1)"; then | |
| echo 'An empty repository CSV must not start a security scan.' >&2 | |
| exit 1 | |
| else | |
| status=$? | |
| fi | |
| if [[ "$status" -ne 2 ]] || ! grep -Fq 'Multiscan CSV must contain at least one repository.' <<< "$output"; then | |
| printf 'Unexpected empty-repository scan behavior:\n%s\n' "$output" >&2 | |
| exit 1 | |
| fi | |
| - name: Verify optional hardened AppArmor Compose override | |
| env: | |
| CODEX_SECURITY_IMAGE: codex-security:release-candidate | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| CODEX_SECURITY_USER="$(id -u):$(id -g)" | |
| export CODEX_SECURITY_USER | |
| compose=(docker compose -f compose.yaml -f compose.apparmor.yaml) | |
| "${compose[@]}" config --format json | | |
| jq --exit-status ' | |
| .services["codex-security"].security_opt as $options | | |
| ($options | index("apparmor=codex-security-container")) != null and | |
| ($options | index("no-new-privileges:true")) != null and | |
| any($options[]; startswith("seccomp=")) | |
| ' > /dev/null | |
| if ! docker info --format '{{json .SecurityOptions}}' | | |
| grep -Fq '"name=apparmor"'; then | |
| echo '::notice::This Docker host does not expose AppArmor; the default customer workflow remains available.' | |
| exit 0 | |
| fi | |
| sudo install -m 0644 docker/codex-security.apparmor \ | |
| /etc/apparmor.d/codex-security-container | |
| sudo apparmor_parser -r -W /etc/apparmor.d/codex-security-container | |
| sudo grep -Fxq 'codex-security-container (enforce)' \ | |
| /sys/kernel/security/apparmor/profiles | |
| # The single-quoted program is evaluated inside the customer container. | |
| # shellcheck disable=SC2016 | |
| "${compose[@]}" run --rm --entrypoint /bin/sh codex-security -ec ' | |
| test "$(cat /proc/self/attr/current)" = "codex-security-container (enforce)" | |
| command_directory="$(mktemp -d)" | |
| trap '\''rm -rf "$command_directory"'\'' EXIT | |
| printf "%s\\n" "#!/bin/sh" '\''printf "%s\\n" "$@"'\'' \ | |
| > "$command_directory/codex-security" | |
| chmod 755 "$command_directory/codex-security" | |
| actual="$( | |
| PATH="$command_directory:$PATH" \ | |
| /usr/local/bin/codex-security-entrypoint \ | |
| bulk-scan /input/repositories.csv --output-dir /output | |
| )" | |
| if printf "%s\\n" "$actual" | | |
| grep -Fxq features.use_legacy_landlock=true; then | |
| printf "%s\\n" "The AppArmor profile must retain the preferred Codex sandbox." >&2 | |
| exit 1 | |
| fi | |
| ' | |
| "${compose[@]}" run --rm --entrypoint node codex-security \ | |
| /usr/local/lib/node_modules/@openai/codex-security/node_modules/@openai/codex/bin/codex.js \ | |
| sandbox /usr/bin/true | |
| authorize: | |
| if: github.event_name != 'pull_request' | |
| name: authorize-container-publication | |
| needs: validate | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| environment: container | |
| permissions: | |
| contents: read | |
| packages: read | |
| outputs: | |
| image: ${{ steps.release.outputs.image }} | |
| version: ${{ steps.release.outputs.version }} | |
| steps: | |
| - name: Checkout release source | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Validate protected release source and version | |
| id: release | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| package_version="$(node -p 'require("./sdk/typescript/package.json").version')" | |
| if [[ "$GITHUB_EVENT_NAME" == workflow_dispatch ]]; then | |
| if [[ "$GITHUB_REF" != refs/heads/main ]]; then | |
| echo 'Manual image releases must use the protected main branch.' >&2 | |
| exit 1 | |
| fi | |
| version="$package_version" | |
| elif [[ "$GITHUB_REF_NAME" =~ ^container-v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then | |
| version="${GITHUB_REF_NAME#container-v}" | |
| else | |
| echo 'Container release tags must identify a stable version such as container-v0.1.0.' >&2 | |
| exit 1 | |
| fi | |
| if [[ "$version" != "$package_version" ]]; then | |
| echo "Container version $version must match the CLI package version $package_version." >&2 | |
| exit 1 | |
| fi | |
| git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main | |
| if ! git merge-base --is-ancestor "$GITHUB_SHA" refs/remotes/origin/main; then | |
| echo 'Container releases must be built from a commit on the protected main branch.' >&2 | |
| exit 1 | |
| fi | |
| printf 'image=ghcr.io/%s\n' "${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" | |
| printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT" | |
| - name: Preflight public package and immutable release version | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VERSION: ${{ steps.release.outputs.version }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| owner="${GITHUB_REPOSITORY_OWNER,,}" | |
| package="${GITHUB_REPOSITORY#*/}" | |
| endpoint="orgs/$owner/packages/container/$package" | |
| if ! metadata="$(gh api "$endpoint" 2>/dev/null)"; then | |
| echo "::error::A repository administrator must bootstrap ghcr.io/$owner/$package, make the package public, and grant this repository package access before approving publication." | |
| exit 1 | |
| fi | |
| if [[ "$(jq -r '.visibility' <<< "$metadata")" != public ]]; then | |
| echo "::error::ghcr.io/$owner/$package must be public before any release image is pushed." | |
| exit 1 | |
| fi | |
| sh docker/verify-container-release-version.sh "$endpoint" "$VERSION" | |
| publish-platform: | |
| name: publish-linux-${{ matrix.architecture }} | |
| needs: authorize | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - architecture: amd64 | |
| runner: ubuntu-24.04 | |
| - architecture: arm64 | |
| runner: ubuntu-24.04-arm | |
| steps: | |
| - name: Checkout approved release source | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 | |
| - name: Sign in to GitHub Container Registry | |
| uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Publish native image by immutable digest | |
| id: build | |
| uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 | |
| with: | |
| context: . | |
| platforms: linux/${{ matrix.architecture }} | |
| outputs: type=image,name=${{ needs.authorize.outputs.image }},push-by-digest=true,name-canonical=true,push=true | |
| provenance: mode=max | |
| sbom: true | |
| cache-from: type=gha,scope=codex-security-${{ matrix.architecture }} | |
| cache-to: type=gha,mode=max,scope=codex-security-${{ matrix.architecture }} | |
| labels: | | |
| org.opencontainers.image.source=https://github.com/${{ github.repository }} | |
| org.opencontainers.image.version=${{ needs.authorize.outputs.version }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| - name: Verify the exact published native image | |
| env: | |
| EXPECTED_ARCHITECTURE: ${{ matrix.architecture }} | |
| IMAGE: ${{ needs.authorize.outputs.image }} | |
| IMAGE_DIGEST: ${{ steps.build.outputs.digest }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "$IMAGE_DIGEST" =~ ^sha256:[[:xdigit:]]{64}$ ]]; then | |
| echo 'The registry did not return a valid immutable platform digest.' >&2 | |
| exit 1 | |
| fi | |
| reference="$IMAGE@$IMAGE_DIGEST" | |
| docker logout ghcr.io | |
| docker pull "$reference" | |
| actual_architecture="$(docker image inspect --format '{{.Architecture}}' "$reference")" | |
| if [[ "$actual_architecture" != "$EXPECTED_ARCHITECTURE" ]]; then | |
| echo "Expected a native $EXPECTED_ARCHITECTURE image; found $actual_architecture." >&2 | |
| exit 1 | |
| fi | |
| docker run --rm "$reference" --version | |
| docker run --rm "$reference" bulk-scan --help | |
| docker run --rm "$reference" info --json | |
| [[ "$(docker run --rm --entrypoint id "$reference" -u)" == 10001 ]] | |
| - name: Record verified platform digest | |
| env: | |
| IMAGE_DIGEST: ${{ steps.build.outputs.digest }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "$IMAGE_DIGEST" =~ ^sha256:[[:xdigit:]]{64}$ ]]; then | |
| echo 'The registry did not return a valid immutable platform digest.' >&2 | |
| exit 1 | |
| fi | |
| mkdir -p "$RUNNER_TEMP/codex-security-platform-digests" | |
| touch "$RUNNER_TEMP/codex-security-platform-digests/${IMAGE_DIGEST#sha256:}" | |
| - name: Upload platform digest | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: codex-security-image-digest-${{ matrix.architecture }} | |
| path: ${{ runner.temp }}/codex-security-platform-digests/* | |
| if-no-files-found: error | |
| retention-days: 7 | |
| compression-level: 0 | |
| manifest: | |
| name: publish-and-verify-multiarchitecture-candidate | |
| needs: | |
| - authorize | |
| - publish-platform | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| digest: ${{ steps.manifest.outputs.digest }} | |
| candidate: ${{ steps.manifest.outputs.candidate }} | |
| steps: | |
| - name: Checkout approved customer configuration | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 | |
| - name: Download verified platform digests | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| path: ${{ runner.temp }}/codex-security-platform-digests | |
| pattern: codex-security-image-digest-* | |
| merge-multiple: true | |
| - name: Sign in to GitHub Container Registry | |
| uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Publish provisional multiarchitecture candidate | |
| id: manifest | |
| env: | |
| IMAGE: ${{ needs.authorize.outputs.image }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mapfile -t digest_files < <( | |
| find "$RUNNER_TEMP/codex-security-platform-digests" \ | |
| -maxdepth 1 -type f -printf '%f\n' | sort | |
| ) | |
| if [[ "${#digest_files[@]}" -ne 2 ]]; then | |
| echo 'A release must contain exactly one verified amd64 and arm64 image.' >&2 | |
| exit 1 | |
| fi | |
| references=() | |
| for digest in "${digest_files[@]}"; do | |
| if [[ ! "$digest" =~ ^[[:xdigit:]]{64}$ ]]; then | |
| echo 'The release contains an invalid platform digest.' >&2 | |
| exit 1 | |
| fi | |
| references+=("$IMAGE@sha256:$digest") | |
| done | |
| candidate="$IMAGE:release-candidate-$GITHUB_SHA" | |
| docker buildx imagetools create \ | |
| --tag "$candidate" \ | |
| "${references[@]}" | |
| manifest_digest="$(docker buildx imagetools inspect --format '{{.Manifest.Digest}}' "$candidate")" | |
| if [[ ! "$manifest_digest" =~ ^sha256:[[:xdigit:]]{64}$ ]]; then | |
| echo 'The registry did not return a valid multiarchitecture image digest.' >&2 | |
| exit 1 | |
| fi | |
| printf 'digest=%s\n' "$manifest_digest" >> "$GITHUB_OUTPUT" | |
| printf 'candidate=%s\n' "$candidate" >> "$GITHUB_OUTPUT" | |
| docker buildx imagetools inspect "$candidate" --raw | | |
| jq --exit-status ' | |
| [.manifests[] | select(.platform.os == "linux") | .platform.architecture] | |
| | (index("amd64") != null and index("arm64") != null) | |
| ' > /dev/null | |
| - name: Verify customers can pull the candidate without GitHub credentials | |
| env: | |
| CANDIDATE: ${{ steps.manifest.outputs.candidate }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker logout ghcr.io | |
| if ! docker pull "$CANDIDATE"; then | |
| echo '::error::The verified candidate cannot be pulled anonymously; no stable release tags have been published.' | |
| exit 1 | |
| fi | |
| docker run --rm "$CANDIDATE" --version | |
| docker run --rm "$CANDIDATE" bulk-scan --help | |
| docker run --rm "$CANDIDATE" info --json | |
| - name: Verify hardened customer Compose against the public candidate | |
| env: | |
| CODEX_SECURITY_IMAGE: ${{ steps.manifest.outputs.candidate }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p results state | |
| chmod 700 results state | |
| printf 'id,repository,revision\n' > repositories.csv | |
| CODEX_SECURITY_USER="$(id -u):$(id -g)" | |
| export CODEX_SECURITY_USER | |
| docker compose config --quiet | |
| docker compose run --rm codex-security --version | |
| if output="$(docker compose run --rm codex-security 2>&1)"; then | |
| echo 'An empty repository CSV must not start a security scan.' >&2 | |
| exit 1 | |
| else | |
| status=$? | |
| fi | |
| if [[ "$status" -ne 2 ]] || ! grep -Fq 'Multiscan CSV must contain at least one repository.' <<< "$output"; then | |
| printf 'Unexpected empty-repository scan behavior:\n%s\n' "$output" >&2 | |
| exit 1 | |
| fi | |
| attest: | |
| name: attest-verified-multiarchitecture-candidate | |
| needs: | |
| - authorize | |
| - manifest | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| permissions: | |
| attestations: write | |
| contents: read | |
| id-token: write | |
| packages: write | |
| steps: | |
| - name: Sign in to GitHub Container Registry | |
| uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Sign verified multiarchitecture candidate provenance | |
| uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 | |
| with: | |
| push-to-registry: true | |
| subject-name: ${{ needs.authorize.outputs.image }} | |
| subject-digest: ${{ needs.manifest.outputs.digest }} | |
| promote: | |
| name: promote-verified-and-attested-release | |
| needs: | |
| - authorize | |
| - manifest | |
| - attest | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout approved immutable-version verifier | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 | |
| - name: Sign in to GitHub Container Registry | |
| uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Promote the verified, attested, immutable image digest | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| IMAGE: ${{ needs.authorize.outputs.image }} | |
| VERSION: ${{ needs.authorize.outputs.version }} | |
| MANIFEST_DIGEST: ${{ needs.manifest.outputs.digest }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "$MANIFEST_DIGEST" =~ ^sha256:[[:xdigit:]]{64}$ ]]; then | |
| echo 'The verified release did not provide a valid immutable digest.' >&2 | |
| exit 1 | |
| fi | |
| owner="${GITHUB_REPOSITORY_OWNER,,}" | |
| package="${GITHUB_REPOSITORY#*/}" | |
| endpoint="orgs/$owner/packages/container/$package" | |
| sh docker/verify-container-release-version.sh "$endpoint" "$VERSION" | |
| docker buildx imagetools create \ | |
| --tag "$IMAGE:$VERSION" \ | |
| --tag "$IMAGE:sha-$GITHUB_SHA" \ | |
| --tag "$IMAGE:latest" \ | |
| "$IMAGE@$MANIFEST_DIGEST" | |
| actual_digest="$(docker buildx imagetools inspect --format '{{.Manifest.Digest}}' "$IMAGE:$VERSION")" | |
| if [[ "$actual_digest" != "$MANIFEST_DIGEST" ]]; then | |
| echo '::error::The promoted stable tag does not reference the verified and attested candidate digest.' | |
| exit 1 | |
| fi | |
| - name: Verify the stable release is publicly pullable | |
| env: | |
| IMAGE: ${{ needs.authorize.outputs.image }} | |
| VERSION: ${{ needs.authorize.outputs.version }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker logout ghcr.io | |
| docker pull "$IMAGE:$VERSION" | |
| docker run --rm "$IMAGE:$VERSION" --version |