adding ssh host signing

This commit is contained in:
Audiobuzz
2026-07-11 16:20:21 +09:30
parent b5d6c790c4
commit 2e318fba00
24 changed files with 1626 additions and 90 deletions
+60 -22
View File
@@ -8,19 +8,24 @@
# shell-local post-processors extract artifacts, build the UKI (ukify), and
# build+push the OCI release image.
#
# Signed when the SB_KEY/SB_CERT secrets are set; otherwise an UNSIGNED (lab)
# build where build-oci.sh emits its loud unsigned-UKI warning. 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.
# 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 - Secure Boot signing: the `db` PRIVATE key (PEM) + its
# cert (PEM). Empty => unsigned lab build. PK/KEK are NOT
# here (offline/HSM). Generate per docs/secure-boot-keys.md.
# 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).
@@ -147,6 +152,36 @@ jobs:
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):
@@ -197,7 +232,7 @@ jobs:
go-version: '1.26.4'
-
name: Build host Go binaries out-of-VM (fleet-enrol, fleet-hostapi, fleet-agent)
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
@@ -223,7 +258,11 @@ jobs:
&& 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 ./... )
file files/enrol/bin/fleet-enrol files/hostapi/bin/fleet-hostapi files/agent/bin/fleet-agent
( 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)
@@ -265,9 +304,8 @@ jobs:
# 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). If SB_KEY is unset, this no-ops and the build
# falls back to an UNSIGNED lab UKI (the loud warning stays).
if: ${{ secrets.SB_KEY != '' }}
# 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
@@ -284,10 +322,9 @@ jobs:
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). If FLEET_CA is unset this
# no-ops and the build falls back to the committed PLACEHOLDER (auth cannot
# succeed — lab only). Never commit real internal CA material (CLAUDE.md).
if: ${{ secrets.FLEET_CA != '' }}
# 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: |
@@ -318,9 +355,10 @@ jobs:
-
name: packer build (UKI + OCI release image)
# Signed when SB_KEY/SB_CERT secrets are set (paths exported above);
# UNSIGNED lab build (loud warning) when they're empty. sb_key/sb_cert are
# PATHS, never values — see the materialize step and docs/secure-boot-keys.md.
# 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 }}" \
@@ -330,9 +368,9 @@ jobs:
-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:-files/etc/dot1x/radius-ca.pem}" \
-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 }} \