mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 08:05:22 +08:00
533 lines
22 KiB
YAML
533 lines
22 KiB
YAML
name: 'QingLong 3.0 Image Release'
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: Exact QingLong 3 SemVer tag without the v prefix
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ql3-image-release-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
worker-management-release-evidence:
|
|
name: Audit private Worker management release evidence
|
|
runs-on: [self-hosted, linux, ql3-release-evidence-ephemeral]
|
|
environment: ql3-production-release-evidence
|
|
timeout-minutes: 10
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
|
with:
|
|
node-version: '24.18.0'
|
|
|
|
- name: Re-audit commit-scoped private production evidence
|
|
env:
|
|
RELEASE_VERSION: ${{ inputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
umask 077
|
|
if [[ ! "${GITHUB_SHA}" =~ ^[0-9a-f]{40,64}$ ]]; then
|
|
echo "release source commit is malformed" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! "${RELEASE_VERSION}" =~ ^3\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$ ]]; then
|
|
echo "release version must be QingLong 3 SemVer" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${GITHUB_REF_TYPE}" != "tag" || "${GITHUB_REF_NAME}" != "v${RELEASE_VERSION}" ]]; then
|
|
echo "workflow dispatch must target the exact protected release tag" >&2
|
|
exit 1
|
|
fi
|
|
evidence_dir="/run/qinglong3-release-evidence/${GITHUB_SHA}"
|
|
node scripts/ql3-worker-credential-management-release-gate.cjs \
|
|
--report="${evidence_dir}/worker-management-release-evidence.json" \
|
|
--ceremony-report="${evidence_dir}/worker-management-ceremony.json" \
|
|
--durable-audit-report="${evidence_dir}/worker-management-durable-audit.json" \
|
|
--pki-rotation-report="${evidence_dir}/worker-management-pki-rotation-v2.json" \
|
|
--ca-rollover-report="${evidence_dir}/worker-management-ca-rollover.json" \
|
|
--source-commit="${GITHUB_SHA}" \
|
|
--release-version="${RELEASE_VERSION}"
|
|
|
|
cluster-dr-release-evidence:
|
|
name: Audit private CloudNativePG disaster-recovery evidence
|
|
runs-on: [self-hosted, linux, ql3-release-evidence-ephemeral]
|
|
environment: ql3-production-release-evidence
|
|
timeout-minutes: 10
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
|
with:
|
|
node-version: '24.18.0'
|
|
|
|
- name: Re-audit commit-scoped private disaster-recovery evidence
|
|
env:
|
|
RELEASE_VERSION: ${{ inputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
umask 077
|
|
if [[ ! "${GITHUB_SHA}" =~ ^[0-9a-f]{40,64}$ ]]; then
|
|
echo "release source commit is malformed" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! "${RELEASE_VERSION}" =~ ^3\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$ ]]; then
|
|
echo "release version must be QingLong 3 SemVer" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${GITHUB_REF_TYPE}" != "tag" || "${GITHUB_REF_NAME}" != "v${RELEASE_VERSION}" ]]; then
|
|
echo "workflow dispatch must target the exact protected release tag" >&2
|
|
exit 1
|
|
fi
|
|
evidence_dir="/run/qinglong3-release-evidence/${GITHUB_SHA}"
|
|
report="${evidence_dir}/cloudnativepg-dr-evidence.json"
|
|
node scripts/ql3-cloudnativepg-backup-audit.cjs
|
|
node scripts/ql3-barman-cloud-supply-chain-audit.cjs
|
|
node scripts/ql3-cert-manager-selection-audit.cjs
|
|
node scripts/ql3-cloudnativepg-dr-release-gate.cjs \
|
|
--report="${report}" \
|
|
--source-commit="${GITHUB_SHA}" \
|
|
--release-version="${RELEASE_VERSION}"
|
|
|
|
os-vulnerability:
|
|
name: Scan ${{ matrix.image }} OS packages on ${{ matrix.image_arch }}
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 45
|
|
permissions:
|
|
contents: read
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- image: control
|
|
runner: ubuntu-24.04
|
|
node_arch: x64
|
|
image_arch: amd64
|
|
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
|
|
target: runtime
|
|
- image: control
|
|
runner: ubuntu-24.04-arm
|
|
node_arch: arm64
|
|
image_arch: arm64
|
|
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
|
|
target: runtime
|
|
- image: control-ai
|
|
runner: ubuntu-24.04
|
|
node_arch: x64
|
|
image_arch: amd64
|
|
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
|
|
target: runtime-ai
|
|
- image: control-ai
|
|
runner: ubuntu-24.04-arm
|
|
node_arch: arm64
|
|
image_arch: arm64
|
|
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
|
|
target: runtime-ai
|
|
- image: admin
|
|
runner: ubuntu-24.04
|
|
node_arch: x64
|
|
image_arch: amd64
|
|
dockerfile: deploy/containers/ql3-cluster-admin/Dockerfile
|
|
target: runtime
|
|
- image: admin
|
|
runner: ubuntu-24.04-arm
|
|
node_arch: arm64
|
|
image_arch: arm64
|
|
dockerfile: deploy/containers/ql3-cluster-admin/Dockerfile
|
|
target: runtime
|
|
- image: local
|
|
runner: ubuntu-24.04
|
|
node_arch: x64
|
|
image_arch: amd64
|
|
dockerfile: deploy/containers/ql3-local-application/Dockerfile
|
|
target: runtime
|
|
- image: local
|
|
runner: ubuntu-24.04-arm
|
|
node_arch: arm64
|
|
image_arch: arm64
|
|
dockerfile: deploy/containers/ql3-local-application/Dockerfile
|
|
target: runtime
|
|
steps:
|
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
|
with:
|
|
node-version: '24.18.0'
|
|
|
|
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
|
|
|
- name: Verify native scanner architecture
|
|
run: node -e "if (process.arch !== '${{ matrix.node_arch }}') throw new Error('unexpected architecture ' + process.arch)"
|
|
|
|
- name: Audit the bounded exception lifecycle
|
|
run: node scripts/ql3-image-os-vulnerability-policy.cjs
|
|
|
|
- name: Build the exact native OCI release artifact once
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p "${RUNNER_TEMP}/ql3-native"
|
|
docker buildx build \
|
|
--platform linux/${{ matrix.image_arch }} \
|
|
--file ${{ matrix.dockerfile }} \
|
|
--target ${{ matrix.target }} \
|
|
--build-arg SOURCE_REVISION=${{ github.sha }} \
|
|
--attest type=provenance,mode=max \
|
|
--attest type=sbom \
|
|
--output "type=oci,dest=${RUNNER_TEMP}/ql3-native/image.oci.tar" \
|
|
.
|
|
|
|
- name: Materialize image-scoped Trivy exceptions
|
|
run: >-
|
|
node scripts/ql3-image-os-vulnerability-policy.cjs
|
|
--image=${{ matrix.image }}
|
|
--output=${{ runner.temp }}/ql3-${{ matrix.image }}-${{ matrix.image_arch }}.trivyignore.yaml
|
|
|
|
- name: Reject unexcepted high or critical OS vulnerabilities
|
|
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
with:
|
|
version: 'v0.70.0'
|
|
input: ${{ runner.temp }}/ql3-native/image.oci.tar
|
|
scanners: 'vuln'
|
|
vuln-type: 'os'
|
|
severity: 'HIGH,CRITICAL'
|
|
ignore-unfixed: 'false'
|
|
exit-code: '1'
|
|
format: 'table'
|
|
hide-progress: 'true'
|
|
timeout: '10m0s'
|
|
cache: 'false'
|
|
trivyignores: ${{ runner.temp }}/ql3-${{ matrix.image }}-${{ matrix.image_arch }}.trivyignore.yaml
|
|
|
|
- name: Bind the successful native scan to the OCI digest
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir "${RUNNER_TEMP}/ql3-native/layout"
|
|
tar -xf "${RUNNER_TEMP}/ql3-native/image.oci.tar" \
|
|
-C "${RUNNER_TEMP}/ql3-native/layout"
|
|
node scripts/ql3-image-release-bundle.cjs \
|
|
--mode=record-native \
|
|
--image=${{ matrix.image }} \
|
|
--platform=linux/${{ matrix.image_arch }} \
|
|
--layout=${{ runner.temp }}/ql3-native/layout \
|
|
--expected-revision=${{ github.sha }} \
|
|
--evidence=${{ runner.temp }}/ql3-native/evidence.json
|
|
rm "${RUNNER_TEMP}/ql3-native/image.oci.tar"
|
|
|
|
- name: Upload the scanned immutable native OCI artifact
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: ql3-release-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.image }}-${{ matrix.image_arch }}
|
|
path: ${{ runner.temp }}/ql3-native
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
compression-level: 0
|
|
overwrite: false
|
|
include-hidden-files: false
|
|
|
|
publish:
|
|
name: Publish immutable multi-architecture ${{ matrix.image }} image
|
|
needs:
|
|
- worker-management-release-evidence
|
|
- cluster-dr-release-evidence
|
|
- os-vulnerability
|
|
runs-on: ubuntu-24.04
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
id-token: write
|
|
attestations: write
|
|
artifact-metadata: write
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- image: control
|
|
repository: qinglong3-cluster-control
|
|
runtime_root: deploy/containers/ql3-cluster-control/runtime-dependencies
|
|
- image: control-ai
|
|
repository: qinglong3-cluster-control-ai
|
|
runtime_root: deploy/containers/ql3-cluster-control/runtime-dependencies
|
|
- image: admin
|
|
repository: qinglong3-cluster-admin
|
|
runtime_root: deploy/containers/ql3-cluster-admin/runtime-dependencies
|
|
- image: local
|
|
repository: qinglong3-local-application
|
|
runtime_root: deploy/containers/ql3-local-application/runtime-dependencies
|
|
steps:
|
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
|
with:
|
|
node-version: '24.18.0'
|
|
|
|
- name: Resolve and validate release identity
|
|
id: identity
|
|
env:
|
|
DISPATCH_VERSION: ${{ inputs.version }}
|
|
IMAGE_REPOSITORY: ${{ matrix.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
owner="${GITHUB_REPOSITORY_OWNER,,}"
|
|
image="ghcr.io/${owner}/${IMAGE_REPOSITORY}"
|
|
version="${DISPATCH_VERSION}"
|
|
if [[ ! "${version}" =~ ^3\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$ ]]; then
|
|
echo "release version must be QingLong 3 SemVer" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${GITHUB_REF_TYPE}" != "tag" || "${GITHUB_REF_NAME}" != "v${version}" ]]; then
|
|
echo "release source must be the exact requested tag" >&2
|
|
exit 1
|
|
fi
|
|
echo "image=${image}" >> "${GITHUB_OUTPUT}"
|
|
echo "version=${version}" >> "${GITHUB_OUTPUT}"
|
|
|
|
- name: Generate and audit the reviewed application SBOM
|
|
run: >-
|
|
node scripts/ql3-cluster-image-sbom.cjs
|
|
--image=${{ matrix.image }}
|
|
--output=${{ runner.temp }}/${{ matrix.repository }}.cdx.json
|
|
|
|
- name: Reject high or critical production dependency advisories
|
|
run: >-
|
|
npm audit
|
|
--omit=dev
|
|
--audit-level=high
|
|
--prefix=${{ matrix.runtime_root }}
|
|
|
|
- name: Download the exact scanned amd64 OCI artifact
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: ql3-release-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.image }}-amd64
|
|
path: ${{ runner.temp }}/native/amd64
|
|
|
|
- name: Download the exact scanned arm64 OCI artifact
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: ql3-release-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.image }}-arm64
|
|
path: ${{ runner.temp }}/native/arm64
|
|
|
|
- name: Merge and re-audit the scanned OCI digest without rebuilding
|
|
id: bundle
|
|
run: |
|
|
set -euo pipefail
|
|
node scripts/ql3-image-release-bundle.cjs \
|
|
--mode=merge \
|
|
--image=${{ matrix.image }} \
|
|
--amd64-layout=${RUNNER_TEMP}/native/amd64/layout \
|
|
--amd64-evidence=${RUNNER_TEMP}/native/amd64/evidence.json \
|
|
--arm64-layout=${RUNNER_TEMP}/native/arm64/layout \
|
|
--arm64-evidence=${RUNNER_TEMP}/native/arm64/evidence.json \
|
|
--expected-revision=${GITHUB_SHA} \
|
|
--output=${RUNNER_TEMP}/${{ matrix.repository }}-release-layout \
|
|
--predicate=${RUNNER_TEMP}/${{ matrix.repository }}-os-vulnerability.json \
|
|
--report=${RUNNER_TEMP}/${{ matrix.repository }}-bundle.json
|
|
digest="$(node -e 'const fs=require("node:fs");const value=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));if(!/^sha256:[0-9a-f]{64}$/.test(value.rootIndexDigest))process.exit(1);process.stdout.write(value.rootIndexDigest)' "${RUNNER_TEMP}/${{ matrix.repository }}-bundle.json")"
|
|
echo "digest=${digest}" >> "${GITHUB_OUTPUT}"
|
|
tar -C "${RUNNER_TEMP}/${{ matrix.repository }}-release-layout" \
|
|
-cf "${RUNNER_TEMP}/${{ matrix.repository }}-release.oci.tar" .
|
|
|
|
- name: Install the exact checksum-pinned OCI copier
|
|
env:
|
|
REGCTL: ${{ runner.temp }}/regctl
|
|
run: |
|
|
set -euo pipefail
|
|
curl --fail --location --proto '=https' --tlsv1.2 \
|
|
--output "${REGCTL}" \
|
|
https://github.com/regclient/regclient/releases/download/v0.11.5/regctl-linux-amd64
|
|
echo "c93aa7638749f5aaac1a8e01787321889c78f0101809bb2880343478d0ba0467 ${REGCTL}" | sha256sum --check --strict
|
|
chmod 0755 "${REGCTL}"
|
|
"${REGCTL}" version
|
|
|
|
- uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c # v4
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Import the scanned OCI graph by digest without a public tag
|
|
id: push
|
|
env:
|
|
IMAGE: ${{ steps.identity.outputs.image }}
|
|
DIGEST: ${{ steps.bundle.outputs.digest }}
|
|
ARCHIVE: ${{ runner.temp }}/${{ matrix.repository }}-release.oci.tar
|
|
REGCTL: ${{ runner.temp }}/regctl
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ ! "${DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
|
|
echo "bundle did not return an immutable SHA-256 digest" >&2
|
|
exit 1
|
|
fi
|
|
"${REGCTL}" image import "${IMAGE}@${DIGEST}" "${ARCHIVE}"
|
|
remote_digest="$("${REGCTL}" image digest "${IMAGE}@${DIGEST}")"
|
|
if [[ "${remote_digest}" != "${DIGEST}" ]]; then
|
|
echo "registry changed the scanned OCI digest" >&2
|
|
exit 1
|
|
fi
|
|
echo "digest=${DIGEST}" >> "${GITHUB_OUTPUT}"
|
|
|
|
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
|
|
|
- name: Keylessly sign the immutable manifest digest
|
|
env:
|
|
IMAGE: ${{ steps.identity.outputs.image }}
|
|
DIGEST: ${{ steps.push.outputs.digest }}
|
|
run: cosign sign --yes "${IMAGE}@${DIGEST}"
|
|
|
|
- name: Attest SLSA build provenance
|
|
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
|
|
with:
|
|
subject-name: ${{ steps.identity.outputs.image }}
|
|
subject-digest: ${{ steps.push.outputs.digest }}
|
|
push-to-registry: true
|
|
|
|
- name: Attest the reviewed CycloneDX application SBOM
|
|
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
|
|
with:
|
|
subject-name: ${{ steps.identity.outputs.image }}
|
|
subject-digest: ${{ steps.push.outputs.digest }}
|
|
sbom-path: ${{ runner.temp }}/${{ matrix.repository }}.cdx.json
|
|
push-to-registry: true
|
|
|
|
- name: Attest the digest-bound OS vulnerability evidence
|
|
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
|
|
with:
|
|
subject-name: ${{ steps.identity.outputs.image }}
|
|
subject-digest: ${{ steps.push.outputs.digest }}
|
|
predicate-type: https://qinglong.dev/attestations/image-os-vulnerability/v1
|
|
predicate-path: ${{ runner.temp }}/${{ matrix.repository }}-os-vulnerability.json
|
|
push-to-registry: true
|
|
|
|
- name: Verify the published manifest and attestation bindings
|
|
env:
|
|
IMAGE: ${{ steps.identity.outputs.image }}
|
|
DIGEST: ${{ steps.push.outputs.digest }}
|
|
run: |
|
|
set -euo pipefail
|
|
manifest="${RUNNER_TEMP}/${{ matrix.repository }}-index.json"
|
|
docker buildx imagetools inspect --raw "${IMAGE}@${DIGEST}" > "${manifest}"
|
|
node scripts/ql3-cluster-remote-manifest-audit.cjs \
|
|
--manifest="${manifest}" \
|
|
--expected-image="${IMAGE}" \
|
|
--expected-digest="${DIGEST}"
|
|
|
|
- name: Verify local rollout compatibility against the pushed digest
|
|
if: matrix.image == 'local'
|
|
env:
|
|
IMAGE: ${{ steps.identity.outputs.image }}
|
|
DIGEST: ${{ steps.push.outputs.digest }}
|
|
run: |
|
|
set -euo pipefail
|
|
corepack enable
|
|
pnpm install --frozen-lockfile --ignore-scripts
|
|
pnpm --filter @qinglong/local-owner-cli check
|
|
docker pull "${IMAGE}@${DIGEST}"
|
|
docker_executable="$(realpath "$(command -v docker)")"
|
|
docker_socket="$(realpath /var/run/docker.sock)"
|
|
node scripts/ql3-local-compose-rollout-live-contract.cjs \
|
|
--image="${IMAGE}@${DIGEST}" \
|
|
--docker-executable="${docker_executable}" \
|
|
--docker-socket="${docker_socket}" \
|
|
--profile=edge
|
|
node scripts/ql3-local-compose-rollout-live-contract.cjs \
|
|
--image="${IMAGE}@${DIGEST}" \
|
|
--docker-executable="${docker_executable}" \
|
|
--docker-socket="${docker_socket}" \
|
|
--profile=standalone
|
|
|
|
- name: Verify the keyless signature identity
|
|
env:
|
|
IMAGE: ${{ steps.identity.outputs.image }}
|
|
DIGEST: ${{ steps.push.outputs.digest }}
|
|
run: |
|
|
set -euo pipefail
|
|
certificate_identity="https://github.com/${GITHUB_REPOSITORY}/.github/workflows/ql3-image-release.yml@${GITHUB_REF}"
|
|
cosign verify \
|
|
--certificate-identity "${certificate_identity}" \
|
|
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
|
"${IMAGE}@${DIGEST}"
|
|
|
|
- name: Verify GitHub SLSA provenance from the published OCI registry
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
IMAGE: ${{ steps.identity.outputs.image }}
|
|
DIGEST: ${{ steps.push.outputs.digest }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh attestation verify "oci://${IMAGE}@${DIGEST}" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/ql3-image-release.yml" \
|
|
--source-digest "${GITHUB_SHA}" \
|
|
--source-ref "${GITHUB_REF}" \
|
|
--deny-self-hosted-runners \
|
|
--bundle-from-oci
|
|
|
|
- name: Verify the reviewed CycloneDX attestation from the OCI registry
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
IMAGE: ${{ steps.identity.outputs.image }}
|
|
DIGEST: ${{ steps.push.outputs.digest }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh attestation verify "oci://${IMAGE}@${DIGEST}" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/ql3-image-release.yml" \
|
|
--source-digest "${GITHUB_SHA}" \
|
|
--source-ref "${GITHUB_REF}" \
|
|
--predicate-type "https://cyclonedx.org/bom" \
|
|
--deny-self-hosted-runners \
|
|
--bundle-from-oci
|
|
|
|
- name: Verify the OS vulnerability evidence from the OCI registry
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
IMAGE: ${{ steps.identity.outputs.image }}
|
|
DIGEST: ${{ steps.push.outputs.digest }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh attestation verify "oci://${IMAGE}@${DIGEST}" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/ql3-image-release.yml" \
|
|
--source-digest "${GITHUB_SHA}" \
|
|
--source-ref "${GITHUB_REF}" \
|
|
--predicate-type "https://qinglong.dev/attestations/image-os-vulnerability/v1" \
|
|
--deny-self-hosted-runners \
|
|
--bundle-from-oci
|
|
|
|
- name: Promote only the verified digest to immutable release tags
|
|
env:
|
|
IMAGE: ${{ steps.identity.outputs.image }}
|
|
VERSION: ${{ steps.identity.outputs.version }}
|
|
DIGEST: ${{ steps.push.outputs.digest }}
|
|
REGCTL: ${{ runner.temp }}/regctl
|
|
run: |
|
|
set -euo pipefail
|
|
"${REGCTL}" image copy "${IMAGE}@${DIGEST}" "${IMAGE}:${VERSION}"
|
|
"${REGCTL}" image copy "${IMAGE}@${DIGEST}" "${IMAGE}:sha-${GITHUB_SHA}"
|
|
if [[ "$("${REGCTL}" image digest "${IMAGE}:${VERSION}")" != "${DIGEST}" ]]; then
|
|
echo "release version tag does not resolve to the verified digest" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "$("${REGCTL}" image digest "${IMAGE}:sha-${GITHUB_SHA}")" != "${DIGEST}" ]]; then
|
|
echo "release source tag does not resolve to the verified digest" >&2
|
|
exit 1
|
|
fi
|