From 7b89f352da5270a1b9a80554619d55e89faade6b Mon Sep 17 00:00:00 2001 From: wdower <57142072+wdower@users.noreply.github.com> Date: Sat, 6 Jun 2026 00:47:15 +0000 Subject: [PATCH 1/2] ci(release): open Iron Bank merge request on release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an automated Iron Bank (repo1.dso.mil) release step to the docker-release job, modelled on mitre/heimdall2's release-server-to-docker workflow. On each published GitHub release, after the multi-arch mitre/vulcan image is built and pushed, it files a GitLab issue + MR against Vulcan's Iron Bank mirror (project 17073) bumping the manifest to the freshly published image. - Uses mitre/ironbank_release_action@v1 (SHA-pinned). - Version comes from docker/metadata-action's {{version}} output (semver, v-stripped) — equivalent to Heimdall's format-tag preprocessing. - Resolves per-architecture digests (resources[0]=amd64, resources[1]=arm64) via `docker buildx imagetools inspect`, since build-push-action only exposes the multi-arch index digest. - update_commands escapes double-quotes for the action's `eval`. --- .github/workflows/release.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 94fe65ae5..d18816922 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,3 +51,35 @@ jobs: image: mitre/vulcan:${{ steps.meta.outputs.version }} artifact-name: image.spdx.json dependency-snapshot: true + + # ─── IRON BANK RELEASE STEPS ─── + + # Vulcan on IB has both arm and amd builds; we need hashes of both to provide to the hardening manifest in repo1 + - name: Resolve per-architecture image digests for the Iron Bank manifest + id: digests + shell: bash + run: | + set -euo pipefail + tag="mitre/vulcan:${{ steps.meta.outputs.version }}" + amd64="$(docker buildx imagetools inspect "$tag" --format '{{range .Manifest.Manifests}}{{if and (eq .Platform.OS "linux") (eq .Platform.Architecture "amd64")}}{{.Digest}}{{end}}{{end}}')" + arm64="$(docker buildx imagetools inspect "$tag" --format '{{range .Manifest.Manifests}}{{if and (eq .Platform.OS "linux") (eq .Platform.Architecture "arm64")}}{{.Digest}}{{end}}{{end}}')" + [[ "$amd64" == sha256:* ]] || { echo "Failed to resolve amd64 digest for $tag"; exit 1; } + [[ "$arm64" == sha256:* ]] || { echo "Failed to resolve arm64 digest for $tag"; exit 1; } + echo "amd64=$amd64" >> "$GITHUB_OUTPUT" + echo "arm64=$arm64" >> "$GITHUB_OUTPUT" + echo "Resolved digests: amd64=$amd64 arm64=$arm64" + + # Opens a MR on Vulcan's repo1 repository with the new release + - name: Open Iron Bank merge request + uses: mitre/ironbank_release_action@1c14c27db1c37e894f8042ac2edcde08d445882d # v1 + with: + name: Vulcan + version: ${{ steps.meta.outputs.version }} + ironbank_pat: ${{ secrets.SAF_IRONBANK_REPO1_PAT }} + ironbank_username: ${{ secrets.SAF_IRONBANK_REPO1_USERNAME }} + ironbank_project_id: 17073 + ironbank_project_clone_url: repo1.dso.mil/dsop/mitre/security-automation-framework/vulcan.git + git_commit_author_name: "Automated Vulcan Release" + git_commit_author_email: "saf@mitre.org" + update_commands: | + yq e -i '.tags[0]=\"${{ steps.meta.outputs.version }}\" | .labels.\"org.opencontainers.image.version\"=\"${{ steps.meta.outputs.version }}\" | .resources[0].tag=\"mitre/vulcan:${{ steps.meta.outputs.version }}\" | .resources[0].url=\"docker://docker.io/mitre/vulcan@${{ steps.digests.outputs.amd64 }}\" | .resources[1].tag=\"mitre/vulcan:${{ steps.meta.outputs.version }}.arm64\" | .resources[1].url=\"docker://docker.io/mitre/vulcan@${{ steps.digests.outputs.arm64 }}\"' hardening_manifest.yaml From 8a6bc588eb28fca49e58fa047a82aca096fbe3be Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 18 Jun 2026 10:42:25 -0400 Subject: [PATCH 2/2] ci: track main in Iron Bank via vulcan-mainline; default to QEMU multi-arch builds - mainline.yml: on push to master, build/push mitre/vulcan: + :latest and open an Iron Bank MR against the vulcan-mainline repo1 project (19019) - release.yml: publish the stable moving tag as release-latest (was latest); latest now means the bleeding-edge mainline build - both workflows default to free QEMU emulated multi-arch builds; Docker Build Cloud is opt-in via the use_build_cloud workflow_dispatch input - build via docker/bake-action + a TAG_SUFFIXES-driven 'registry' bake target; remove the now-unused 'release' bake target - docs: point stable docker pull/run examples at mitre/vulcan:release-latest Signed-off-by: Will --- .github/workflows/mainline.yml | 104 ++++++++++++++++++++++++++++ .github/workflows/release.yml | 55 ++++++++++----- docker-bake.hcl | 38 +++++----- docs/deployment/docker.md | 6 +- docs/development/release-process.md | 9 ++- docs/getting-started/quick-start.md | 4 +- docs/index.md | 4 +- docs/security/compliance.md | 2 +- 8 files changed, 177 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/mainline.yml diff --git a/.github/workflows/mainline.yml b/.github/workflows/mainline.yml new file mode 100644 index 000000000..0f95285a6 --- /dev/null +++ b/.github/workflows/mainline.yml @@ -0,0 +1,104 @@ +name: Mainline + +# Tracks every push to the default branch: builds and pushes a bleeding-edge +# multi-arch image tagged mitre/vulcan: and :latest, then opens an Iron +# Bank merge request against the vulcan-mainline repo1 project. Mirrors +# release.yml, which publishes the stable mitre/vulcan: + :release-latest. + +on: + push: + branches: [master] + paths-ignore: + - 'docs/**' + - '*.md' + - 'LICENSE' + - '.github/workflows/ci.yml' + - '.github/workflows/docs.yml' + - '.github/workflows/release.yml' + - '.github/workflows/dependabot.yml' + workflow_dispatch: + inputs: + use_build_cloud: + description: 'Use Docker Build Cloud (fast, but consumes paid build minutes)' + required: false + type: boolean + default: false + +concurrency: + group: mainline-${{ github.ref }} + cancel-in-progress: true + +jobs: + # ─── DOCKER MAINLINE (multi-arch; QEMU by default, Build Cloud opt-in) ─── + docker-mainline: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Login to DockerHub + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + username: ${{ vars.DOCKER_USER }} + password: ${{ secrets.DOCKER_PAT }} + + # Default: free QEMU-emulated multi-arch build on the runner. + # Opt into Docker Build Cloud (faster, consumes minutes) via the + # workflow_dispatch `use_build_cloud` input. + - name: Set up QEMU (for emulated multi-arch builds) + if: github.event.inputs.use_build_cloud != 'true' + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + + - name: Set up Docker Buildx (local + QEMU emulation) + if: github.event.inputs.use_build_cloud != 'true' + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Set up Docker Buildx (Docker Build Cloud) + if: github.event.inputs.use_build_cloud == 'true' + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + with: + driver: cloud + endpoint: "mitre/mitre-builder" + + - name: Build and push multi-arch image (tags + latest) + uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0 + env: + TAG_SUFFIXES: "${{ github.sha }},latest" + with: + files: docker-bake.hcl + targets: registry + push: true + + # ─── IRON BANK MAINLINE STEPS ─── + + # Vulcan on IB has both arm and amd builds; we need hashes of both to provide to the hardening manifest in repo1 + - name: Resolve per-architecture image digests for the Iron Bank manifest + id: digests + shell: bash + run: | + set -euo pipefail + tag="mitre/vulcan:${{ github.sha }}" + amd64="$(docker buildx imagetools inspect "$tag" --format '{{range .Manifest.Manifests}}{{if and (eq .Platform.OS "linux") (eq .Platform.Architecture "amd64")}}{{.Digest}}{{end}}{{end}}')" + arm64="$(docker buildx imagetools inspect "$tag" --format '{{range .Manifest.Manifests}}{{if and (eq .Platform.OS "linux") (eq .Platform.Architecture "arm64")}}{{.Digest}}{{end}}{{end}}')" + [[ "$amd64" == sha256:* ]] || { echo "Failed to resolve amd64 digest for $tag"; exit 1; } + [[ "$arm64" == sha256:* ]] || { echo "Failed to resolve arm64 digest for $tag"; exit 1; } + echo "amd64=$amd64" >> "$GITHUB_OUTPUT" + echo "arm64=$arm64" >> "$GITHUB_OUTPUT" + echo "Resolved digests: amd64=$amd64 arm64=$arm64" + + # Opens a MR on Vulcan's vulcan-mainline repo1 repository with the latest main build + - name: Open Iron Bank mainline merge request + uses: mitre/ironbank_release_action@1c14c27db1c37e894f8042ac2edcde08d445882d # v1 + with: + name: Vulcan + version: ${{ github.sha }} + ironbank_pat: ${{ secrets.SAF_IRONBANK_REPO1_PAT }} + ironbank_username: ${{ secrets.SAF_IRONBANK_REPO1_USERNAME }} + ironbank_project_id: 19019 + ironbank_project_clone_url: repo1.dso.mil/dsop/mitre/security-automation-framework/vulcan-mainline.git + git_commit_author_name: "Automated Vulcan Mainline" + git_commit_author_email: "saf@mitre.org" + update_commands: | + yq e -i '.tags[0]=\"${{ github.sha }}\" | .labels.\"org.opencontainers.image.version\"=\"${{ github.sha }}\" | .resources[0].tag=\"mitre/vulcan:${{ github.sha }}\" | .resources[0].url=\"docker://docker.io/mitre/vulcan@${{ steps.digests.outputs.amd64 }}\" | .resources[1].tag=\"mitre/vulcan:${{ github.sha }}.arm64\" | .resources[1].url=\"docker://docker.io/mitre/vulcan@${{ steps.digests.outputs.arm64 }}\"' hardening_manifest.yaml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d18816922..be777751f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,6 +3,13 @@ name: Release on: release: types: [published] + workflow_dispatch: + inputs: + use_build_cloud: + description: 'Use Docker Build Cloud (fast, but consumes paid build minutes)' + required: false + type: boolean + default: false jobs: # ─── DOCKER RELEASE (multi-arch via Build Cloud) ─── @@ -21,34 +28,44 @@ jobs: username: ${{ vars.DOCKER_USER }} password: ${{ secrets.DOCKER_PAT }} - - name: Set up Docker Buildx (Build Cloud) + # Default: free QEMU-emulated multi-arch build on the runner. + # Opt into Docker Build Cloud (faster, consumes minutes) via the + # workflow_dispatch `use_build_cloud` input. + - name: Set up QEMU (for emulated multi-arch builds) + if: github.event.inputs.use_build_cloud != 'true' + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + + - name: Set up Docker Buildx (local + QEMU emulation) + if: github.event.inputs.use_build_cloud != 'true' + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Set up Docker Buildx (Docker Build Cloud) + if: github.event.inputs.use_build_cloud == 'true' uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 with: driver: cloud endpoint: "mitre/mitre-builder" - - name: Docker metadata - id: meta - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 - with: - images: mitre/vulcan - tags: | - type=semver,pattern={{version}} - type=raw,value=latest + - name: Resolve release version + id: version + shell: bash + run: | + raw="${{ github.event.release.tag_name || github.ref_name }}" + echo "version=${raw#v}" >> "$GITHUB_OUTPUT" - - name: Build and push multi-arch image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + - name: Build and push multi-arch image (tags + release-latest) + uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0 + env: + TAG_SUFFIXES: "${{ steps.version.outputs.version }},release-latest" with: - context: . - platforms: linux/amd64,linux/arm64 + files: docker-bake.hcl + targets: registry push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - name: SBOM scan and dependency submission uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0 with: - image: mitre/vulcan:${{ steps.meta.outputs.version }} + image: mitre/vulcan:${{ steps.version.outputs.version }} artifact-name: image.spdx.json dependency-snapshot: true @@ -60,7 +77,7 @@ jobs: shell: bash run: | set -euo pipefail - tag="mitre/vulcan:${{ steps.meta.outputs.version }}" + tag="mitre/vulcan:${{ steps.version.outputs.version }}" amd64="$(docker buildx imagetools inspect "$tag" --format '{{range .Manifest.Manifests}}{{if and (eq .Platform.OS "linux") (eq .Platform.Architecture "amd64")}}{{.Digest}}{{end}}{{end}}')" arm64="$(docker buildx imagetools inspect "$tag" --format '{{range .Manifest.Manifests}}{{if and (eq .Platform.OS "linux") (eq .Platform.Architecture "arm64")}}{{.Digest}}{{end}}{{end}}')" [[ "$amd64" == sha256:* ]] || { echo "Failed to resolve amd64 digest for $tag"; exit 1; } @@ -74,7 +91,7 @@ jobs: uses: mitre/ironbank_release_action@1c14c27db1c37e894f8042ac2edcde08d445882d # v1 with: name: Vulcan - version: ${{ steps.meta.outputs.version }} + version: ${{ steps.version.outputs.version }} ironbank_pat: ${{ secrets.SAF_IRONBANK_REPO1_PAT }} ironbank_username: ${{ secrets.SAF_IRONBANK_REPO1_USERNAME }} ironbank_project_id: 17073 @@ -82,4 +99,4 @@ jobs: git_commit_author_name: "Automated Vulcan Release" git_commit_author_email: "saf@mitre.org" update_commands: | - yq e -i '.tags[0]=\"${{ steps.meta.outputs.version }}\" | .labels.\"org.opencontainers.image.version\"=\"${{ steps.meta.outputs.version }}\" | .resources[0].tag=\"mitre/vulcan:${{ steps.meta.outputs.version }}\" | .resources[0].url=\"docker://docker.io/mitre/vulcan@${{ steps.digests.outputs.amd64 }}\" | .resources[1].tag=\"mitre/vulcan:${{ steps.meta.outputs.version }}.arm64\" | .resources[1].url=\"docker://docker.io/mitre/vulcan@${{ steps.digests.outputs.arm64 }}\"' hardening_manifest.yaml + yq e -i '.tags[0]=\"${{ steps.version.outputs.version }}\" | .labels.\"org.opencontainers.image.version\"=\"${{ steps.version.outputs.version }}\" | .resources[0].tag=\"mitre/vulcan:${{ steps.version.outputs.version }}\" | .resources[0].url=\"docker://docker.io/mitre/vulcan@${{ steps.digests.outputs.amd64 }}\" | .resources[1].tag=\"mitre/vulcan:${{ steps.version.outputs.version }}.arm64\" | .resources[1].url=\"docker://docker.io/mitre/vulcan@${{ steps.digests.outputs.arm64 }}\"' hardening_manifest.yaml diff --git a/docker-bake.hcl b/docker-bake.hcl index 121b113d9..a484d0369 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -31,6 +31,10 @@ variable "VERSION" { default = "latest" } +variable "TAG_SUFFIXES" { + default = "latest" +} + variable "VULCAN_BUNDLER_VERSION" { default = "2.7.2" } @@ -105,6 +109,24 @@ target "production-multiarch" { ] } +// ============================================================================ +// Registry Push - For the release + mainline CI workflows +// ============================================================================ +// The tag list is supplied by the workflow as a comma-separated TAG_SUFFIXES: +// release: TAG_SUFFIXES=",release-latest" +// mainline: TAG_SUFFIXES=",latest" +// The first suffix is the immutable version and is surfaced as the image label. + +target "registry" { + inherits = ["production-multiarch"] + + tags = [for s in split(",", TAG_SUFFIXES) : "${REGISTRY}/${IMAGE_NAME}:${trimspace(s)}"] + + labels = { + "org.opencontainers.image.version" = trimspace(split(",", TAG_SUFFIXES)[0]) + } +} + // ============================================================================ // Development Target - Full dev environment with all dependencies // ============================================================================ @@ -152,19 +174,3 @@ target "ci-multiarch" { "linux/arm64" ] } - -// ============================================================================ -// Release Build - For tagged releases -// ============================================================================ - -target "release" { - inherits = ["production-multiarch"] - - // Override tags for release - tags = [ - "${REGISTRY}/${IMAGE_NAME}:${VERSION}", - "${REGISTRY}/${IMAGE_NAME}:latest", - "ghcr.io/${REGISTRY}/${IMAGE_NAME}:${VERSION}", - "ghcr.io/${REGISTRY}/${IMAGE_NAME}:latest" - ] -} diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 1d1056f21..ca58b8336 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -56,8 +56,8 @@ Vulcan provides three ways to create the initial admin: ### Using Docker Run ```bash -# Pull the latest image -docker pull mitre/vulcan:latest +# Pull the latest released image +docker pull mitre/vulcan:release-latest # Run with PostgreSQL docker run -d \ @@ -65,7 +65,7 @@ docker run -d \ -p 3000:3000 \ -e DATABASE_URL="postgresql://user:pass@host/vulcan" \ -e SECRET_KEY_BASE="your-secret-key" \ - mitre/vulcan:latest + mitre/vulcan:release-latest ``` ## Image Details diff --git a/docs/development/release-process.md b/docs/development/release-process.md index 0f21bacd9..731590f55 100644 --- a/docs/development/release-process.md +++ b/docs/development/release-process.md @@ -125,14 +125,19 @@ That push triggers everything. No further manual steps are required. - Logs in to DockerHub - Uses Docker Build Cloud (`mitre/mitre-builder`) for native multi-arch builds - Builds `linux/amd64` and `linux/arm64` images - - Pushes `mitre/vulcan:v2.3.2` and `mitre/vulcan:latest` to DockerHub + - Pushes `mitre/vulcan:v2.3.2` and `mitre/vulcan:release-latest` to DockerHub - Generates SBOM (SPDX format) and submits to GitHub dependency graph +> **Tag convention:** `release-latest` is the moving pointer to the most recent +> stable release; `latest` is the bleeding-edge build of `master` published by +> `mainline.yml` on every push. Use `release-latest` (or a pinned `vX.Y.Z`) for +> production; `latest` only for trying the newest unreleased code. + ### 5. Verify the release 1. Check [Actions](https://github.com/mitre/vulcan/actions) — `release.yml` and `ci.yml` runs should both be green 2. Check [Releases](https://github.com/mitre/vulcan/releases) — new release should exist with changelog populated -3. Check [DockerHub](https://hub.docker.com/r/mitre/vulcan/tags) — new version tag and `latest` should be present +3. Check [DockerHub](https://hub.docker.com/r/mitre/vulcan/tags) — new version tag and `release-latest` should be present 4. Pull and smoke-test the image: ```bash diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index aeb4f873b..bd1fdbc75 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -20,8 +20,8 @@ Before installing, you can try Vulcan directly: ### 1. Pull and Run with Docker ```bash -# Pull the latest Docker image -docker pull mitre/vulcan:latest +# Pull the latest released image +docker pull mitre/vulcan:release-latest # Or use docker compose for a complete setup wget https://raw.githubusercontent.com/mitre/vulcan/master/docker-compose.yml diff --git a/docs/index.md b/docs/index.md index 6cf541bbb..8227f2282 100644 --- a/docs/index.md +++ b/docs/index.md @@ -75,8 +75,8 @@ const involvement = [ ### Quick Test with Docker ```bash -docker pull mitre/vulcan:latest -docker run -p 3000:3000 mitre/vulcan:latest +docker pull mitre/vulcan:release-latest +docker run -p 3000:3000 mitre/vulcan:release-latest ``` ### Full Setup with Docker Compose diff --git a/docs/security/compliance.md b/docs/security/compliance.md index d0d21b04b..ad5dac91e 100644 --- a/docs/security/compliance.md +++ b/docs/security/compliance.md @@ -426,7 +426,7 @@ spec: containers: - name: vulcan - image: mitre/vulcan:latest + image: mitre/vulcan:release-latest # Security Settings securityContext: