413 lines
21 KiB
YAML
413 lines
21 KiB
YAML
#
|
|
# .gitea/workflows/gitea-main-ci.yaml
|
|
#
|
|
# Builds the diskless fleet release: a fleet-generic UKI + erofs/dm-verity
|
|
# root, packaged as a FROM-scratch OCI artifact image (see CLAUDE.md and
|
|
# docs/diskless-fleet-design-v2.pdf). Packer kickstart-installs a throwaway
|
|
# Rocky 9 build VM (qemu), builds the --no-hostonly initramfs, then the
|
|
# shell-local post-processors extract artifacts, build the UKI (ukify), and
|
|
# build+push the OCI release image.
|
|
#
|
|
# This is the PRODUCTION build profile and it FAILS CLOSED (POA&M P-03): a
|
|
# "Production build gate" step aborts the run unless SB_KEY, SB_CERT and FLEET_CA
|
|
# are set — there is no unsigned fallback and no placeholder fleet CA. STIG
|
|
# hardening is mandatory (enforced by the Packer template's variable validation).
|
|
# Signing runs shell-local on the runner (not in the build VM); the key is staged
|
|
# to RUNNER_TEMP and shredded after. See docs/secure-boot-keys.md.
|
|
#
|
|
# Required Gitea repo secrets:
|
|
# REGISTRY_USERNAME - registry user for `oci_image` (oci_push=true)
|
|
# REGISTRY_TOKEN - registry password/token
|
|
# SSH_AUTHORIZED_KEY - public key for the sudo admin user; empty => no admin
|
|
# user + root left unlocked (lab). Set it to lock root.
|
|
# SB_KEY, SB_CERT - REQUIRED. Secure Boot signing: the `db` PRIVATE key (PEM)
|
|
# + its cert (PEM). PK/KEK are NOT here (offline/HSM).
|
|
# Generate per docs/secure-boot-keys.md. Missing => build fails.
|
|
# FLEET_CA - REQUIRED. Real fleet step-ca root PEM baked to
|
|
# radius-ca.pem (RADIUS EAP-TLS + fleet-agent verify).
|
|
# Missing => build fails.
|
|
#
|
|
# Runner: needs KVM (qemu accelerator=kvm) and the build-host tooling installed
|
|
# below (libguestfs, erofs-utils, cryptsetup, jq, ukify, buildah).
|
|
#
|
|
|
|
name: Build Diskless UKI
|
|
run-name: ${{ gitea.actor }} builds the diskless fleet UKI (${{ gitea.ref_name }})
|
|
on:
|
|
push:
|
|
branches:
|
|
- "**" # every branch — feature branches get a branch-name+sha tag
|
|
tags:
|
|
- "v*" # tag push -> clean semver tag (v1.2.3 -> 1.2.3)
|
|
# Skip this ~17-min VM build when a push touches ONLY things that are NOT
|
|
# rootfs/UKI build inputs. The image is built from diskless.pkr.hcl + http/
|
|
# (ks.cfg) + files/** + scripts/** — those are deliberately NOT listed here,
|
|
# so they always trigger. Everything below is runtime/serving infra, docs, or
|
|
# dev tooling that never lands in the rootfs. A commit that also touches a
|
|
# real build input still builds (paths-ignore only skips when EVERY changed
|
|
# path matches).
|
|
paths-ignore:
|
|
# Serving stack — served at runtime, built by its own image workflows /
|
|
# the kiosk-app submodule; none of it is baked into the rootfs.
|
|
- "serving/**"
|
|
- "serving/kiosk-app" # the submodule gitlink path (pointer bumps)
|
|
# Docs / notes / dev tooling — not build inputs.
|
|
- "docs/**"
|
|
- "*.md" # root README.md, CLAUDE.md, etc.
|
|
- ".devcontainer/**"
|
|
- ".gitignore"
|
|
- ".gitattributes"
|
|
- "LICENSE"
|
|
# Sibling image workflows build their OWN images, not the UKI. (Add new
|
|
# ones here; do NOT add gitea-main-ci.yaml — its edits must still build.)
|
|
- ".gitea/workflows/ipxe-image.yaml"
|
|
- ".gitea/workflows/dnsmasq-image.yaml"
|
|
- ".gitea/workflows/spdk-image.yaml"
|
|
- ".gitea/workflows/fleet-manager-image.yaml"
|
|
|
|
# One build per ref at a time; a newer push to the same branch cancels the
|
|
# in-flight build (each build is a ~17-min VM run, so don't stack them).
|
|
concurrency:
|
|
group: uki-build-${{ gitea.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
PRODUCT_VERSION: "1.15.4" # Packer version
|
|
PACKER_LOG: "1"
|
|
OCI_IMAGE: "git.bitasylum.org/audiobuzz/fleet-boot"
|
|
NIC_DRIVERS: "ice mlx5_core"
|
|
# NVMe/TCP discovery address baked into the (immutable) UKI cmdline
|
|
# (rd.nvmf.discover). For the open-network test this is the lab serving host's
|
|
# IP so the initramfs needs no DNS resolution. Production: switch to a
|
|
# per-environment DNS name (e.g. nvme.fleet.lab) resolved at boot so one UKI
|
|
# works across environments — the serving stack's dnsmasq resolves it.
|
|
NVMET_TRADDR: "192.168.50.10"
|
|
# URL the fullscreen Chromium kiosk loads at boot. Defaults to the kiosk SPA
|
|
# (serving/kiosk-app) reverse-proxied by the boot HTTP server at /kiosk/;
|
|
# boot.fleet.lab resolves to the serving host via the stack's dnsmasq. Empty
|
|
# disables the kiosk.
|
|
KIOSK_URL: "http://boot.fleet.lab/kiosk/"
|
|
# Plymouth boot splash. "false" => verbose console boot (rhgb quiet NOT baked;
|
|
# plymouth.enable=0 instead) — kept OFF in CI while the network-manager port is
|
|
# being bench-verified, so the boot chain stays debuggable. Flip to "true" to
|
|
# ship the graphical splash once boot is trusted. (Plymouth is in the image
|
|
# either way; this only flips the cmdline.)
|
|
BOOT_SPLASH: "true"
|
|
# DISA STIG hardening (OpenSCAP + SSG, diskless/UKI/kiosk tailoring). Report-only
|
|
# in CI: the scan + deviations report are published as artifacts; the build is
|
|
# never failed on score. Set "false" to skip hardening (boot/secure-path bench).
|
|
ENABLE_STIG: "true"
|
|
# ===========================================================================
|
|
# TEMPORARY: 802.1X + MACsec are DISABLED for this build (rd.dot1x=0) so the
|
|
# UKI / rootfs / NVMe-oF boot path can be validated on an OPEN switch port.
|
|
# The resulting UKI has NO link authentication or encryption — test networks
|
|
# only. Set back to "false" to restore the full secure boot chain.
|
|
# ===========================================================================
|
|
DISABLE_DOT1X: "true"
|
|
|
|
jobs:
|
|
uki-build:
|
|
name: Diskless UKI Build
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: git.bitasylum.org/audiobuzz/ubuntu-act-runner-qemu:main
|
|
options: --device /dev/kvm
|
|
steps:
|
|
-
|
|
name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
-
|
|
name: Derive image version
|
|
id: vars
|
|
# This becomes the OCI tag (build-oci.sh tags ${oci_image}:${image_version})
|
|
# and the artifact dir name. Two cases:
|
|
# - tag push (refs/tags/v1.2.3) -> "1.2.3" (release)
|
|
# - branch push (refs/heads/<name>) -> "<name>-<sha7>" (dev image)
|
|
# The branch name is sanitized to the OCI tag charset
|
|
# ([A-Za-z0-9._-], <=128, no leading . or -): e.g. feature/foo -> feature-foo,
|
|
# giving feature-foo-ab12cd3. So every branch yields a uniquely tagged,
|
|
# pushed image you can boot/test without clobbering main or releases.
|
|
run: |
|
|
short_sha="$(echo "${GITHUB_SHA}" | cut -c1-7)"
|
|
if [ "${GITHUB_REF#refs/tags/}" != "$GITHUB_REF" ]; then
|
|
version="${GITHUB_REF#refs/tags/}"
|
|
version="${version#v}"
|
|
else
|
|
branch="${GITHUB_REF#refs/heads/}"
|
|
# invalid chars -> '-'; trim leading '.'/'-'; cap length headroom for -sha7
|
|
safe_branch="$(printf '%s' "$branch" \
|
|
| sed -e 's#[^A-Za-z0-9._-]#-#g' -e 's#^[.-]*##' | cut -c1-100)"
|
|
[ -n "$safe_branch" ] || safe_branch="branch"
|
|
version="${safe_branch}-${short_sha}"
|
|
fi
|
|
# Lowercase the whole thing. image_version is BOTH the OCI tag AND the
|
|
# in-artifact UKI filename (fleet-<version>.efi) + verity.json + subnqn.
|
|
# A registry may lowercase the pushed TAG while the artifact keeps the
|
|
# original case, so a mixed-case branch (e.g. SPDK-permanence) yields a
|
|
# capital fleet-SPDK-….efi the lowercase boot URL can't fetch (iPXE
|
|
# "no such file"). Forcing lowercase keeps tag, filename, verity.json,
|
|
# and the dnsmasq boot URL all in agreement.
|
|
version="$(printf '%s' "$version" | tr '[:upper:]' '[:lower:]')"
|
|
echo "image_version=${version}" >> "$GITHUB_OUTPUT"
|
|
echo "Building image_version=${version}"
|
|
|
|
-
|
|
name: Production build gate (fail closed on missing security material)
|
|
# POA&M P-03 / F-01 (poam-hardening): the pipeline is the production build
|
|
# profile, so it FAILS CLOSED. There is no unsigned fallback and no
|
|
# placeholder fleet CA — the three security-critical secrets MUST be set:
|
|
# SB_KEY / SB_CERT Secure Boot `db` signing material (signed UKI)
|
|
# FLEET_CA real fleet step-ca root baked to radius-ca.pem
|
|
# (RADIUS EAP-TLS trust anchor + fleet-agent verify)
|
|
# enable_stig is enforced true by the Packer template itself (variable
|
|
# validation); it is asserted here too so a misconfigured ENABLE_STIG env
|
|
# fails before the ~17-min VM build rather than during it.
|
|
env:
|
|
SB_KEY: ${{ secrets.SB_KEY }}
|
|
SB_CERT: ${{ secrets.SB_CERT }}
|
|
FLEET_CA: ${{ secrets.FLEET_CA }}
|
|
run: |
|
|
miss=0
|
|
for v in SB_KEY SB_CERT FLEET_CA; do
|
|
if [ -z "${!v}" ]; then
|
|
echo "::error::required secret ${v} is empty — production builds are signed with a real fleet CA (no fallback). Set it in the Gitea repo secrets."
|
|
miss=1
|
|
fi
|
|
done
|
|
if [ "${ENABLE_STIG}" != "true" ]; then
|
|
echo "::error::ENABLE_STIG must be 'true' — production builds are hardening-mandatory (POA&M P-03)."
|
|
miss=1
|
|
fi
|
|
[ "$miss" = 0 ] || exit 1
|
|
echo "Production gate passed: SB_KEY, SB_CERT, FLEET_CA present; STIG enabled."
|
|
|
|
-
|
|
name: Install build-host tooling
|
|
# Tools the shell-local post-processors run on the RUNNER (not the VM):
|
|
# libguestfs (virt-*), erofs-utils (mkfs.erofs), cryptsetup
|
|
# (veritysetup), jq, systemd-ukify (ukify), sbsigntool (sbsign — used by
|
|
# ukify to sign the UKI when SB_KEY/SB_CERT are set), buildah (OCI), plus
|
|
# qemu + shellcheck for the lint gate.
|
|
# NB: the Debian/Ubuntu package is sbsigntool (singular); on Fedora/RHEL
|
|
# it's sbsigntools (plural).
|
|
#
|
|
# libguestfs builds a supermin appliance on first use, which needs a
|
|
# kernel + modules present on the runner (incl. xfs, to read the build
|
|
# VM's xfs root). The runner container ships none, so install one and
|
|
# make /boot readable (Ubuntu ships vmlinuz mode 0600).
|
|
run: |
|
|
sudo apt-get update
|
|
# systemd-boot-efi ships the UKI EFI stub
|
|
# (/usr/lib/systemd/boot/efi/linuxx64.efi.stub) that ukify wraps the
|
|
# kernel+initrd+cmdline around. It's only a *recommends* of
|
|
# systemd-ukify, so --no-install-recommends would otherwise skip it.
|
|
sudo apt-get install -y --no-install-recommends \
|
|
qemu-system-x86 qemu-utils libguestfs-tools linux-image-generic \
|
|
erofs-utils cryptsetup-bin jq systemd-ukify systemd-boot-efi \
|
|
sbsigntool buildah shellcheck attr
|
|
sudo chmod 0644 /boot/vmlinuz-* 2>/dev/null || true
|
|
|
|
-
|
|
name: Setup Packer
|
|
uses: hashicorp/setup-packer@main
|
|
with:
|
|
version: ${{ env.PRODUCT_VERSION }}
|
|
|
|
-
|
|
name: Shell lint gate
|
|
# Matches CLAUDE.md conventions; dracut hooks + post-processor scripts.
|
|
run: |
|
|
bash -n files/dracut/95dot1x-macsec/*.sh scripts/*.sh
|
|
# SC1091 (sourced /lib/dracut-lib.sh) and SC2154 (dracut-injected
|
|
# $moddir) are dracut-module false positives. They're also in
|
|
# .shellcheckrc, but excluded explicitly here so the gate doesn't
|
|
# depend on the runner's shellcheck finding that rc file.
|
|
shellcheck --exclude=SC1091,SC2154 files/dracut/95dot1x-macsec/*.sh scripts/*.sh
|
|
|
|
-
|
|
name: Set up Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: '1.26.4'
|
|
|
|
-
|
|
name: Build host Go binaries out-of-VM (fleet-enrol, fleet-hostapi, fleet-agent, fleet-ssh-hostcert)
|
|
# Built HERE on the runner, NOT inside the Packer VM, so the Rocky build
|
|
# image never carries the Go toolchain or the go-tpm dep tree. Packer
|
|
# file-provisions the resulting static binaries into the rootfs. Runs
|
|
# BEFORE packer validate/build so the file provisioners' sources exist.
|
|
# Reproducible: pinned Go (1.26.4) + go.sum-pinned deps + -trimpath/no buildid.
|
|
# fleet-enrol + fleet-agent fetch go-tpm/grpc from the proxy (pinned by
|
|
# go.sum); hostapi is stdlib-only (GOPROXY=off).
|
|
# shell: bash is explicit — the Gitea/act runner defaults run steps to sh
|
|
# (dash), where `set -o pipefail` is an illegal option. `set -e` is
|
|
# load-bearing: without it a failed go build/test in the first ( … ) group
|
|
# is masked by the trailing `file` success and a broken binary reaches Packer.
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
( cd files/enrol \
|
|
&& CGO_ENABLED=0 GOTOOLCHAIN=local \
|
|
go build -trimpath -buildvcs=false -ldflags '-s -w -buildid=' -o bin/fleet-enrol . \
|
|
&& CGO_ENABLED=0 go test ./... )
|
|
( cd files/hostapi \
|
|
&& CGO_ENABLED=0 GOPROXY=off GOTOOLCHAIN=local \
|
|
go build -trimpath -buildvcs=false -ldflags '-s -w -buildid=' -o bin/fleet-hostapi . )
|
|
( cd files/agent \
|
|
&& CGO_ENABLED=0 GOTOOLCHAIN=local \
|
|
go build -trimpath -buildvcs=false -ldflags '-s -w -buildid=' -o bin/fleet-agent . \
|
|
&& CGO_ENABLED=0 go vet ./... && CGO_ENABLED=0 go test ./... )
|
|
( cd files/ssh-hostcert \
|
|
&& CGO_ENABLED=0 GOTOOLCHAIN=local \
|
|
go build -trimpath -buildvcs=false -ldflags '-s -w -buildid=' -o bin/fleet-ssh-hostcert . \
|
|
&& CGO_ENABLED=0 go vet ./... && CGO_ENABLED=0 go test ./... )
|
|
file files/enrol/bin/fleet-enrol files/hostapi/bin/fleet-hostapi files/agent/bin/fleet-agent files/ssh-hostcert/bin/fleet-ssh-hostcert
|
|
|
|
-
|
|
name: TPM ceremony + agent mTLS tests (go-tpm software simulator)
|
|
# Exercises the real key-gen/sign/NV path (enrol) AND the reused-TPM mTLS
|
|
# client-auth handshake (agent) against a software TPM (CGO in separate
|
|
# modules, so the simulator never enters the shipped enrol/agent builds).
|
|
run: |
|
|
( cd files/enrol/simtest && CGO_ENABLED=1 go test ./... )
|
|
( cd files/agent/simtest && CGO_ENABLED=1 go test ./... )
|
|
|
|
-
|
|
name: packer init
|
|
run: packer init .
|
|
|
|
-
|
|
name: packer validate
|
|
run: packer validate .
|
|
|
|
-
|
|
name: Verify libguestfs appliance (fast gate)
|
|
# Fail in ~1 min if supermin can't build/boot the appliance, instead of
|
|
# after the ~17-min VM build when extract-artifacts.sh first runs virt-ls.
|
|
env:
|
|
LIBGUESTFS_BACKEND: direct
|
|
run: libguestfs-test-tool
|
|
|
|
-
|
|
name: Registry login
|
|
# Enables oci_push=true in the build step below.
|
|
run: |
|
|
buildah login \
|
|
--username "${{ secrets.PACKAGE_USER }}" \
|
|
--password "${{ secrets.PACKAGE_TOKEN }}" \
|
|
git.bitasylum.org
|
|
|
|
-
|
|
name: Materialize Secure Boot signing material
|
|
# sb_key/sb_cert are consumed by ukify as FILE PATHS (not values), and the
|
|
# signing post-processor runs shell-local on THIS runner (not in the build
|
|
# VM), so write the secrets to runner-temp files and pass their paths. The
|
|
# `db` PRIVATE key is the only signing key here; PK/KEK stay offline (see
|
|
# docs/secure-boot-keys.md). Presence is guaranteed by the Production build
|
|
# gate above (fail-closed) — there is no unsigned fallback.
|
|
env:
|
|
SB_KEY: ${{ secrets.SB_KEY }} # db private key (PEM) — secret
|
|
SB_CERT: ${{ secrets.SB_CERT }} # db cert (PEM) — public, kept as a secret for convenience
|
|
run: |
|
|
umask 077
|
|
d="$RUNNER_TEMP/sb"; mkdir -p "$d"
|
|
printf '%s\n' "$SB_KEY" > "$d/db.key"
|
|
printf '%s\n' "$SB_CERT" > "$d/db.crt"
|
|
echo "SB_KEY_FILE=$d/db.key" >> "$GITHUB_ENV"
|
|
echo "SB_CERT_FILE=$d/db.crt" >> "$GITHUB_ENV"
|
|
echo "Secure Boot signing material staged (signed UKI build)."
|
|
|
|
-
|
|
name: Materialize fleet CA (radius-ca.pem trust anchor)
|
|
# The real fleet step-ca root is baked to /etc/dot1x/radius-ca.pem (RADIUS
|
|
# EAP-TLS + fleet-agent server-verify). Written to a runner-temp FILE and
|
|
# passed as a build-host PATH (var.fleet_ca). Presence is guaranteed by the
|
|
# Production build gate above (fail-closed) — the committed placeholder is
|
|
# NOT a usable fallback here. Never commit real internal CA material (CLAUDE.md).
|
|
env:
|
|
FLEET_CA: ${{ secrets.FLEET_CA }} # fleet step-ca root PEM (root, or root+intermediate)
|
|
run: |
|
|
umask 077
|
|
printf '%s\n' "$FLEET_CA" > "$RUNNER_TEMP/fleet-ca.pem"
|
|
echo "FLEET_CA_FILE=$RUNNER_TEMP/fleet-ca.pem" >> "$GITHUB_ENV"
|
|
echo "Fleet CA staged (real trust anchor baked into radius-ca.pem)."
|
|
|
|
-
|
|
name: Materialize Secure Boot enrollment certs
|
|
# PUBLIC PK/KEK/db .auth files baked into the image for fleet-sb-enroll.
|
|
# They're binary, so stored base64 in repo secrets; decode into
|
|
# files/secureboot/keys/ (gitignored) before the build picks them up. If
|
|
# the secrets are unset this no-ops and the helper ships without keys
|
|
# (reports "no baked keys" until supplied). NOT signing keys — see
|
|
# docs/secure-boot-keys.md; PK/KEK private keys never come near CI.
|
|
if: ${{ secrets.SB_DB_AUTH_B64 != '' }}
|
|
env:
|
|
SB_PK_AUTH_B64: ${{ secrets.SB_PK_AUTH_B64 }}
|
|
SB_KEK_AUTH_B64: ${{ secrets.SB_KEK_AUTH_B64 }}
|
|
SB_DB_AUTH_B64: ${{ secrets.SB_DB_AUTH_B64 }}
|
|
run: |
|
|
d=files/secureboot/keys; mkdir -p "$d"
|
|
printf '%s' "$SB_PK_AUTH_B64" | base64 -d > "$d/PK.auth"
|
|
printf '%s' "$SB_KEK_AUTH_B64" | base64 -d > "$d/KEK.auth"
|
|
printf '%s' "$SB_DB_AUTH_B64" | base64 -d > "$d/db.auth"
|
|
echo "Staged PK/KEK/db .auth for in-image enrollment:"; ls -l "$d"/*.auth
|
|
|
|
-
|
|
name: packer build (UKI + OCI release image)
|
|
# Always a SIGNED build: SB_KEY/SB_CERT/FLEET_CA presence is enforced by the
|
|
# Production build gate, and enable_stig=true is enforced by the template's
|
|
# variable validation. sb_key/sb_cert are PATHS, never values — see the
|
|
# materialize step and docs/secure-boot-keys.md.
|
|
run: |
|
|
packer build -color=false -on-error=abort \
|
|
-var image_version="${{ steps.vars.outputs.image_version }}" \
|
|
-var nic_drivers="${{ env.NIC_DRIVERS }}" \
|
|
-var oci_image="${{ env.OCI_IMAGE }}" \
|
|
-var oci_push=true \
|
|
-var disable_dot1x=${{ env.DISABLE_DOT1X }} \
|
|
-var nvmet_traddr="${{ env.NVMET_TRADDR }}" \
|
|
-var ssh_authorized_key="${{ secrets.SSH_AUTHORIZED_KEY }}" \
|
|
-var sb_key="${SB_KEY_FILE}" \
|
|
-var sb_cert="${SB_CERT_FILE}" \
|
|
-var fleet_ca="${FLEET_CA_FILE}" \
|
|
-var kiosk_url="${{ env.KIOSK_URL }}" \
|
|
-var boot_splash=${{ env.BOOT_SPLASH }} \
|
|
-var enable_stig=${{ env.ENABLE_STIG }} \
|
|
.
|
|
|
|
-
|
|
name: Shred Secure Boot signing key
|
|
# Remove the private key from the runner FS even if the build failed.
|
|
# (RUNNER_TEMP is also wiped per-job on ephemeral runners — belt + braces.)
|
|
if: always()
|
|
run: shred -u "$RUNNER_TEMP/sb/db.key" 2>/dev/null || rm -f "$RUNNER_TEMP/sb/db.key" 2>/dev/null || true
|
|
|
|
-
|
|
name: STIG deviations report (report-only)
|
|
# The build downloaded the post-remediation scan (stig-arf.xml +
|
|
# stig-report.html) into artifacts/<version>/. Turn the ARF into a
|
|
# readable deviations report. Never fails the build (report-only).
|
|
if: ${{ env.ENABLE_STIG == 'true' }}
|
|
run: |
|
|
dir="artifacts/${{ steps.vars.outputs.image_version }}"
|
|
if [ -s "$dir/stig-arf.xml" ]; then
|
|
bash scripts/stig-deviations.sh "$dir/stig-arf.xml" "$dir/stig-deviations.md" || true
|
|
echo "----- STIG deviations (summary) -----"
|
|
sed -n '1,40p' "$dir/stig-deviations.md" || true
|
|
else
|
|
echo "no STIG ARF found (enable_stig off or scan failed) — skipping report"
|
|
fi
|
|
|
|
-
|
|
name: Publish STIG report artifacts
|
|
if: ${{ env.ENABLE_STIG == 'true' }}
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: stig-report-${{ steps.vars.outputs.image_version }}
|
|
path: |
|
|
artifacts/${{ steps.vars.outputs.image_version }}/stig-report.html
|
|
artifacts/${{ steps.vars.outputs.image_version }}/stig-arf.xml
|
|
artifacts/${{ steps.vars.outputs.image_version }}/stig-deviations.md
|
|
if-no-files-found: warn
|