feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,122 @@
name: QingLong 3.0 Approval Management Kubernetes live evidence
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ql3-approval-management-live-${{ github.ref }}
cancel-in-progress: false
jobs:
approval-management-live:
name: Three-node Approval, OIDC, mTLS and CloudNativePG
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Require enough ephemeral disk for three isolated K3s nodes
shell: bash
run: |
set -euo pipefail
available_kib="$(df -Pk / | awk 'NR == 2 { print $4 }')"
minimum_kib="$((25 * 1024 * 1024))"
if (( available_kib < minimum_kib )); then
echo "Approval live gate requires at least 25 GiB free; found ${available_kib} KiB" >&2
exit 1
fi
docker volume ls --quiet --filter dangling=true | sort > \
"${RUNNER_TEMP}/ql3-dangling-volumes.before"
docker system df
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Install verified kubectl v1.32.8
shell: bash
run: |
set -euo pipefail
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/kubectl" \
https://dl.k8s.io/release/v1.32.8/bin/linux/amd64/kubectl
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/kubectl.sha256" \
https://dl.k8s.io/release/v1.32.8/bin/linux/amd64/kubectl.sha256
test "$(cat "${RUNNER_TEMP}/kubectl.sha256")" = \
"$(sha256sum "${RUNNER_TEMP}/kubectl" | cut -d ' ' -f 1)"
chmod 0755 "${RUNNER_TEMP}/kubectl"
- name: Fetch checksum-locked CloudNativePG operator manifest
shell: bash
run: |
set -euo pipefail
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/cloudnative-pg.yaml" \
https://github.com/cloudnative-pg/cloudnative-pg/releases/download/v1.30.0/cnpg-1.30.0.yaml
echo "f8bede43fe4ee0d478c2355b204a36876b2ae4faac60f2a9452280b293da3b88 ${RUNNER_TEMP}/cloudnative-pg.yaml" | sha256sum --check
- name: Recheck static Approval and evidence contracts
run: |
pnpm audit:cluster-deployment:ql3
node --test \
test/back/ql3ApprovalManagementKubernetesLiveContract.test.cjs \
test/back/ql3ApprovalManagementKubernetesLiveAudit.test.cjs
pnpm --filter @qinglong/runtime-core build
- name: Preload the digest-bound K3s distribution
run: docker pull rancher/k3s:v1.34.3-k3s1
- name: Prove Approval over three nodes, mTLS, OIDC and CloudNativePG
env:
QL3_APPROVAL_MANAGEMENT_KUBERNETES_LIVE: '1'
QL3_KUBECTL_BIN: ${{ runner.temp }}/kubectl
QL3_CNPG_OPERATOR_MANIFEST_FILE: ${{ runner.temp }}/cloudnative-pg.yaml
QL3_APPROVAL_REPORT: ${{ runner.temp }}/ql3-approval/report.json
shell: bash
run: |
set -euo pipefail
umask 077
mkdir -m 0700 "$(dirname "${QL3_APPROVAL_REPORT}")"
node scripts/ql3-approval-management-kubernetes-live-contract.cjs \
"--report=${QL3_APPROVAL_REPORT}"
- name: Re-audit private report and isolated cleanup
env:
QL3_APPROVAL_REPORT: ${{ runner.temp }}/ql3-approval/report.json
shell: bash
run: |
set -euo pipefail
test "$(stat -c '%a' "${QL3_APPROVAL_REPORT}")" = '600'
pnpm audit:approval-management-kubernetes-live:ql3 \
"--report=${QL3_APPROVAL_REPORT}"
sha256sum "${QL3_APPROVAL_REPORT}"
test -z "$(docker ps -aq --filter name=ql3-approval-live-)"
test -z "$(docker network ls -q --filter name=ql3-approval-live-)"
docker volume ls --quiet --filter dangling=true | sort > \
"${RUNNER_TEMP}/ql3-dangling-volumes.after"
diff --unified \
"${RUNNER_TEMP}/ql3-dangling-volumes.before" \
"${RUNNER_TEMP}/ql3-dangling-volumes.after"
docker system df
- name: Upload audited content-free Approval evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-approval-management-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/ql3-approval/report.json
if-no-files-found: error
retention-days: 14
compression-level: 9
overwrite: false
include-hidden-files: false
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,134 @@
name: QingLong 3.0 CloudNativePG DR live evidence
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ql3-cloudnativepg-dr-live-${{ github.ref }}
cancel-in-progress: false
jobs:
cloudnativepg-dr-live:
name: Barman WAL, latest and PITR recovery
runs-on: ubuntu-24.04
timeout-minutes: 120
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Require enough ephemeral disk for four isolated K3s nodes
shell: bash
run: |
set -euo pipefail
available_kib="$(df -Pk / | awk 'NR == 2 { print $4 }')"
minimum_kib="$((35 * 1024 * 1024))"
if (( available_kib < minimum_kib )); then
echo "CloudNativePG DR live gate requires at least 35 GiB free; found ${available_kib} KiB" >&2
exit 1
fi
docker volume ls --quiet --filter dangling=true | sort > \
"${RUNNER_TEMP}/ql3-dangling-volumes.before"
docker system df
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Install verified kubectl v1.32.8
shell: bash
run: |
set -euo pipefail
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/kubectl" \
https://dl.k8s.io/release/v1.32.8/bin/linux/amd64/kubectl
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/kubectl.sha256" \
https://dl.k8s.io/release/v1.32.8/bin/linux/amd64/kubectl.sha256
test "$(cat "${RUNNER_TEMP}/kubectl.sha256")" = \
"$(sha256sum "${RUNNER_TEMP}/kubectl" | cut -d ' ' -f 1)"
chmod 0755 "${RUNNER_TEMP}/kubectl"
- name: Fetch checksum-locked operator manifests
shell: bash
run: |
set -euo pipefail
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/cert-manager.yaml" \
https://github.com/cert-manager/cert-manager/releases/download/v1.20.3/cert-manager.yaml
echo "7ee74ba06845213e96d8ceaff3d20dd51e682765c1418eddda4e8780ba082261 ${RUNNER_TEMP}/cert-manager.yaml" | sha256sum --check
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/barman-cloud.yaml" \
https://github.com/cloudnative-pg/plugin-barman-cloud/releases/download/v0.13.0/manifest.yaml
echo "d2e71e7b06822448f1a421f05781846cfdb9cc621e7ef32eef5e20c5133213b0 ${RUNNER_TEMP}/barman-cloud.yaml" | sha256sum --check
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/cloudnative-pg.yaml" \
https://github.com/cloudnative-pg/cloudnative-pg/releases/download/v1.30.0/cnpg-1.30.0.yaml
echo "f8bede43fe4ee0d478c2355b204a36876b2ae4faac60f2a9452280b293da3b88 ${RUNNER_TEMP}/cloudnative-pg.yaml" | sha256sum --check
- name: Recheck the static backup and supply-chain contracts
run: |
pnpm audit:cloudnativepg-backup:ql3
pnpm audit:barman-cloud-supply-chain:ql3
pnpm audit:cert-manager-selection:ql3
node --test \
test/back/ql3CloudNativePgBarmanLiveContract.test.cjs \
test/back/ql3CloudNativePgDrEvidenceAudit.test.cjs \
test/back/ql3CloudNativePgBarmanWorkflow.test.cjs
- name: Prove TLS object storage, continuous WAL, latest restore and PITR
env:
QL3_CLOUDNATIVEPG_BARMAN_LIVE: '1'
QL3_SOURCE_REVISION: ${{ github.sha }}
QL3_KUBECTL_BIN: ${{ runner.temp }}/kubectl
QL3_CERT_MANAGER_MANIFEST_FILE: ${{ runner.temp }}/cert-manager.yaml
QL3_BARMAN_MANIFEST_FILE: ${{ runner.temp }}/barman-cloud.yaml
QL3_CNPG_OPERATOR_MANIFEST_FILE: ${{ runner.temp }}/cloudnative-pg.yaml
QL3_DR_REPORT: ${{ runner.temp }}/ql3-cloudnativepg-dr/report.json
shell: bash
run: |
set -euo pipefail
umask 077
mkdir -m 0700 "$(dirname "${QL3_DR_REPORT}")"
pnpm test:cloudnativepg-barman-live:ql3 -- \
"--report=${QL3_DR_REPORT}"
- name: Re-audit the exact private report and isolated cleanup
env:
QL3_DR_REPORT: ${{ runner.temp }}/ql3-cloudnativepg-dr/report.json
shell: bash
run: |
set -euo pipefail
test "$(stat -c '%a' "${QL3_DR_REPORT}")" = '600'
pnpm audit:cloudnativepg-dr-evidence:ql3 -- \
"--report=${QL3_DR_REPORT}"
sha256sum "${QL3_DR_REPORT}"
test -z "$(docker ps -aq --filter name=ql3-barman-dr-)"
test -z "$(docker network ls -q --filter name=ql3-barman-dr-)"
test -z "$(docker ps -aq --filter label=io.qinglong.ql3.live=cloudnativepg-barman-disaster-recovery)"
test -z "$(docker network ls -q --filter label=io.qinglong.ql3.live=cloudnativepg-barman-disaster-recovery)"
docker volume ls --quiet --filter dangling=true | sort > \
"${RUNNER_TEMP}/ql3-dangling-volumes.after"
diff --unified \
"${RUNNER_TEMP}/ql3-dangling-volumes.before" \
"${RUNNER_TEMP}/ql3-dangling-volumes.after"
docker system df
- name: Upload the audited content-free DR evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-cloudnativepg-dr-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/ql3-cloudnativepg-dr/report.json
if-no-files-found: error
retention-days: 14
compression-level: 9
overwrite: false
include-hidden-files: false
+532
View File
@@ -0,0 +1,532 @@
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
@@ -0,0 +1,122 @@
name: QingLong 3.0 Worker Kubernetes rollout live evidence
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ql3-worker-kubernetes-rollout-live-${{ github.ref }}
cancel-in-progress: false
jobs:
worker-kubernetes-rollout-live:
name: Production Worker, Recreate, mTLS, PVC and Session drain
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: '8.3.1'
- uses: actions/setup-node@v6
with:
node-version: '24.18.0'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Require isolated fixture capacity
shell: bash
run: |
set -euo pipefail
available_kib="$(df -Pk / | awk 'NR == 2 { print $4 }')"
minimum_kib="$((15 * 1024 * 1024))"
if (( available_kib < minimum_kib )); then
echo "Worker rollout live gate requires at least 15 GiB free; found ${available_kib} KiB" >&2
exit 1
fi
docker volume ls --quiet --filter dangling=true | sort > \
"${RUNNER_TEMP}/ql3-dangling-volumes.before"
docker system df
- name: Install workspace dependencies without lifecycle scripts
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Install verified kubectl v1.34.3
shell: bash
run: |
set -euo pipefail
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/kubectl" \
https://dl.k8s.io/release/v1.34.3/bin/linux/amd64/kubectl
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/kubectl.sha256" \
https://dl.k8s.io/release/v1.34.3/bin/linux/amd64/kubectl.sha256
test "$(cat "${RUNNER_TEMP}/kubectl.sha256")" = \
"$(sha256sum "${RUNNER_TEMP}/kubectl" | cut -d ' ' -f 1)"
chmod 0755 "${RUNNER_TEMP}/kubectl"
- name: Recheck Worker runtime and evidence contracts
run: |
pnpm --filter @qinglong/worker-runtime test
node --test test/back/ql3WorkerKubernetesRolloutLiveAudit.test.cjs
pnpm audit:worker-deployment:ql3
- name: Preload digest-bound fixture images
shell: bash
run: |
set -euo pipefail
docker pull \
rancher/k3s@sha256:71abd3a56f57884c62732e0e0d87606052cb5f8555b7db7e8e33c04570b8175c
docker tag \
rancher/k3s@sha256:71abd3a56f57884c62732e0e0d87606052cb5f8555b7db7e8e33c04570b8175c \
rancher/k3s:v1.34.3-k3s1
docker pull \
postgres@sha256:1961f96e6029a02c3812d7cb329a3b03a3ac2bb067058dec17b0f5596aca9296
docker tag \
postgres@sha256:1961f96e6029a02c3812d7cb329a3b03a3ac2bb067058dec17b0f5596aca9296 \
postgres:18.4-bookworm
- name: Prove production Worker Kubernetes lifecycle
env:
QL3_WORKER_KUBERNETES_ROLLOUT_LIVE: '1'
QL3_KUBECTL_BIN: ${{ runner.temp }}/kubectl
QL3_WORKER_KUBERNETES_REPORT: ${{ runner.temp }}/ql3-worker/report.json
shell: bash
run: |
set -euo pipefail
umask 077
mkdir -m 0700 "$(dirname "${QL3_WORKER_KUBERNETES_REPORT}")"
pnpm test:worker-kubernetes-rollout-live:ql3 \
"--report=${QL3_WORKER_KUBERNETES_REPORT}"
- name: Re-audit private report and isolated cleanup
env:
QL3_WORKER_KUBERNETES_REPORT: ${{ runner.temp }}/ql3-worker/report.json
shell: bash
run: |
set -euo pipefail
test "$(stat -c '%a' "${QL3_WORKER_KUBERNETES_REPORT}")" = '600'
pnpm audit:worker-kubernetes-rollout-live:ql3 \
"--report=${QL3_WORKER_KUBERNETES_REPORT}"
sha256sum "${QL3_WORKER_KUBERNETES_REPORT}"
test -z "$(docker ps -aq --filter name=ql3-worker-rollout-live-)"
test -z "$(docker ps -aq --filter name=ql3-worker-rollout-postgres-live-)"
docker volume ls --quiet --filter dangling=true | sort > \
"${RUNNER_TEMP}/ql3-dangling-volumes.after"
diff --unified \
"${RUNNER_TEMP}/ql3-dangling-volumes.before" \
"${RUNNER_TEMP}/ql3-dangling-volumes.after"
docker system df
- name: Upload audited content-free Worker evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ql3-worker-kubernetes-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/ql3-worker/report.json
if-no-files-found: error
retention-days: 14
compression-level: 9
overwrite: false
include-hidden-files: false