name: Signed container release on: release: types: [published] workflow_dispatch: permissions: contents: read concurrency: group: release-${{ github.ref }} cancel-in-progress: false jobs: policy: name: Release policy runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: 22 - name: Validate vulnerability waivers run: node scripts/check-trivy-waivers.mjs - name: Require a clean commit release run: test -z "$(git status --porcelain --untracked-files=all)" image: name: ${{ matrix.component }} needs: policy runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write attestations: write strategy: fail-fast: false matrix: include: - component: backend image: meshcore-backend context: . file: Dockerfile.backend build_args: '' - component: combined image: meshcore-combined context: . file: Dockerfile build_args: VITE_APP_HOSTNAME=app.ukmesh.com - component: app image: meshcore-app context: . file: Dockerfile.app build_args: | VITE_APP_HOSTNAME=app.ukmesh.com VITE_NETWORK=ukmesh VITE_SITE=ukmesh VITE_RF_COVERAGE_ENABLED=true - component: website image: meshcore-website context: . file: Dockerfile.website build_args: | VITE_APP_HOSTNAME=app.ukmesh.com VITE_SITE=ukmesh - component: rf-worker image: meshcore-rf-worker context: viewshed-worker file: viewshed-worker/Dockerfile build_args: '' - component: hopreach image: meshcore-hopreach context: third_party/hopreach file: third_party/hopreach/Dockerfile source_revision: 0230702be70a2729c5acc5640401f56ab9d65fd4 build_args: VERSION=v0.1.32-ukmesh.3 - component: mosquitto-reloader image: meshcore-mosquitto-reloader context: . file: Dockerfile.mosquitto-reloader build_args: '' - component: health-check image: meshcore-health-check context: . file: Dockerfile.mesh-health-check build_args: MESH_HEALTH_CHECK_REF=952eec8e0c55d0749f9abef211c266f992c0bfc7 env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository_owner }}/${{ matrix.image }} SOURCE_REVISION: ${{ matrix.source_revision || github.sha }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - id: meta uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | org.opencontainers.image.revision=${{ env.SOURCE_REVISION }} tags: | type=sha,format=long type=ref,event=tag - id: build name: Build and push immutable image uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: ${{ matrix.context }} file: ${{ matrix.file }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | SOURCE_REVISION=${{ env.SOURCE_REVISION }} ${{ matrix.build_args }} provenance: false sbom: false cache-from: type=gha,scope=${{ matrix.component }} cache-to: type=gha,mode=max,scope=${{ matrix.component }} - name: Gate exact digest on critical vulnerabilities uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: scan-type: image image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} format: json output: trivy-${{ matrix.component }}.json exit-code: '1' severity: CRITICAL ignore-unfixed: false trivyignores: .trivyignore.yaml - name: Generate SPDX SBOM for exact digest uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0 with: image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} format: spdx-json output-file: sbom-${{ matrix.component }}.spdx.json upload-artifact: false - uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3 - name: Sign and attach SBOM attestation env: DIGEST: ${{ steps.build.outputs.digest }} run: | image="${REGISTRY}/${IMAGE_NAME}@${DIGEST}" cosign sign --yes "$image" cosign attest --yes \ --predicate "sbom-${{ matrix.component }}.spdx.json" \ --type spdxjson "$image" cosign verify "$image" \ --certificate-identity-regexp "^https://github.com/${GITHUB_REPOSITORY}/.github/workflows/release.yml@" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ > "signature-${{ matrix.component }}.json" - id: provenance name: Publish registry provenance uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 with: subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: true - name: Write release inventory env: DIGEST: ${{ steps.build.outputs.digest }} ATTESTATION_URL: ${{ steps.provenance.outputs.attestation-url }} run: | jq -n \ --arg component "${{ matrix.component }}" \ --arg source_revision "$SOURCE_REVISION" \ --arg release_revision "$GITHUB_SHA" \ --arg image "${REGISTRY}/${IMAGE_NAME}@${DIGEST}" \ --arg sbom "sbom-${{ matrix.component }}.spdx.json" \ --arg scan "trivy-${{ matrix.component }}.json" \ --arg signature "signature-${{ matrix.component }}.json" \ --arg provenance "$ATTESTATION_URL" \ '{ format: "meshcore-release-inventory-v1", component: $component, source_revision: $source_revision, release_revision: $release_revision, image: $image, sbom: $sbom, scan: $scan, signature: $signature, provenance: $provenance }' > "inventory-${{ matrix.component }}.json" - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: release-${{ github.sha }}-${{ matrix.component }} if-no-files-found: error retention-days: 90 path: | inventory-${{ matrix.component }}.json sbom-${{ matrix.component }}.spdx.json trivy-${{ matrix.component }}.json signature-${{ matrix.component }}.json