Security profiles (Phase 6)
CreateVmRequest.security_profile selects the launch and evidence path.
The VM record stores requested and achieved profiles separately so
a control-plane accept cannot be read as a hardware attestation claim.
How to test (same as CI): ./scripts/test-security-profiles.sh Β·
guide Β·
GitHub Actions.
Example create body: examples/qemu-measured.json.
| Profile | What it requires | Evidence class | Hardware attestation? |
|---|---|---|---|
standard (default) | Nothing new | none | no |
measured | QEMU + Secure Boot + swtpm + approved signed catalog image | software-test | never |
confidential-snp | QEMU + SEV-SNP CPU/firmware on the node | sev-snp only after a verified hardware run | gated |
confidential-tdx | QEMU + TDX CPU/firmware on the node | tdx only after a verified hardware run | gated |
Why measured is not hardware attestationβ
FluxVM already attaches a real UEFI Secure Boot chain and an swtpm-backed
vTPM (see secure-boot-tpm.md). That document is explicit: FluxVM does not consume PCR
quotes or perform remote attestation, and an swtpm process is controlled
by the host. A host-controlled TPM cannot prove that the host cannot
inspect the guest.
measured therefore:
- Forces
secure_boot+tpmon the QEMU backend. - Requires the image to be a catalog alias whose Ed25519 signature
verifies against
catalog.trusted_signers. - Collects a software measurement transcript (image / firmware / kernel SHA-256 plus synthetic PCR0/PCR4/PCR7).
- Evaluates
measurement_policyand will releasemeasurement_policy.test_secretonly on a full match (POST /v1/vms/{id}/secrets/release). - Labels every bundle
evidence.class = "software-test"andhardware_attestation = false.
This is the hardware-free development mode. It is real control-plane coverage for the measurement and secret-release flow. It is not SNP/TDX.
Confidential control plane vs. the security claimβ
SNP and TDX launch argument generation and evidence verification live
behind separate QEMU provider traits (SnpLaunchProvider,
TdxLaunchProvider in fluxvm-qemu). Unit tests cover:
- argument generation (machine +
-object) - malformed evidence (
SNP\x01/TDX\x01headers) - policy denial
- fail-closed behavior when
hardware_attestationis false - rejection of
extra_argsas βproofβ of a confidential launch
Real launch and host-memory protection stay unverified until an operator flips, after a hardware integration run:
[security]
snp_launch_verified = false
tdx_launch_verified = false
# Exercise arg generation / admission only. Does not assert memory protection.
allow_unverified_confidential = false
Until those verified flags are true:
requested_security_profilemay beconfidential-snp/confidential-tdxachieved_security_profilestaysmeasured(software-test) orstandard- the record notes that the hardware claim is unverified
Operations a confidential profile must not inheritβ
The ordinary QEMU backend supports memory hotplug, CPU hotplug, NIC/share
hotplug, snapshots, loadvm, hugepages, shared memory, and extra_args.
A confidential profile does not inherit them. Each operation has an
explicit compatibility check and fails closed.
extra_args containing -object sev-snp-guest... is not evidence that a
confidential launch succeeded.
Host capability discovery and fleet placementβ
GET /v1/security/capabilities reports what this node can do.
fluxvm-agent node heartbeats that report to the fleet registry.
Automatic placement only lands a measured / confidential-* request on
a capable node.
An explicit "node" on POST /fleet/vms still bypasses cordon and
nodeSelector β that is existing FluxVM fleet behavior β but it does
not bypass the security-profile check. A confidential request is
rejected on an ineligible node even when the caller named that node.
The node's own POST /v1/vms repeats the same admission check, so a
direct create (no fleet) cannot skip it either.
APIβ
GET /v1/security/capabilities
GET /v1/vms/{id}/attest
POST /v1/vms/{id}/secrets/release
Create body:
{
"name": "measured-dev",
"backend": "qemu",
"image": "ubuntu-24.04",
"security_profile": "measured",
"measurement_policy": {
"test_secret": "dev-only-not-a-production-secret"
}
}