From 130dd801670dfe4c590c3fab349a544f7a7559ec Mon Sep 17 00:00:00 2001 From: Samuel Laferriere <9342524+samlaf@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:04:11 -0400 Subject: [PATCH] chore(attestation): gate azure generation on the target, not the feature alone `cargo check --all-features`, and anything else that turned on `azure-attester`, failed to build on any host that is not x86_64 linux. Two dependencies of the generation path do not compile elsewhere: tss-esapi-sys ships pregenerated bindings for a fixed list of target tuples that omits aarch64-darwin and panics on anything outside it, and az-tdx-vtpm takes az-cvm-vtpm with default features, whose `verifier` turns on `sev/openssl` and so rdrand 0.8, which has no non-x86 support (virtee/sev#369). That put a whole-crate build failure in front of anyone on an Apple Silicon or aarch64 machine running a feature-complete check, rust-analyzer and cargo doc included. Gating on the feature alone was the mistake: a feature records what the caller asked for, not what the target can provide. So declare az-tdx-vtpm and tss-esapi in a cfg(all(target_os = "linux", target_arch = "x86_64")) dependency table, and have build.rs derive an `azure_attester_x86_64_linux` cfg from the feature and those same two target values. The generation code moves from `cfg(feature = "azure-attester")` onto that cfg. Both halves are load bearing: the feature keeps the native tpm2-tss stack opt-in so verification never links it, which a build script cannot do since it can neither add nor remove a dependency, while the cfg is what asserts the code compiles here. The cfg carries the platform in its name so the gate sites need not each repeat the condition, and so that reading one makes clear it is not the feature. Enabling `azure-attester` off x86_64 linux is now a no-op rather than an error. Cargo still reports the feature as enabled; its two dependencies are simply absent from the graph and build.rs withholds the cfg, leaving the compiled surface equal to `azure-verifier` on its own: detect() never reports AzureTdx, and generation returns AttestationTypeNotSupported. Verification is untouched and still needs no TPM stack anywhere. Narrowing to x86_64 is not a workaround waiting on those upstream fixes. TDX is an Intel technology and the vTPM is read through a linux device, so an Azure TDX CVM is x86_64 linux by construction and the gate is permanent; the sev issue is linked as evidence for anyone who later tries to widen it. The upstream fix that would let us delete code is kinvolk/azure-cvm-tooling#95, so tpm_quote's retirement note now names it. The macOS CI job gains a `cargo check -p attestation --all-features` step, which is what would have caught this. The crate readme claimed it was impossible to compile with `azure-attester` on macOS, and now describes the no-op instead. One hazard when editing either file: the platform condition lives in both Cargo.toml's target table and build.rs, and nothing enforces that the two agree. A build.rs broader than the table fails the build outright; a narrower one silently drops the generation code. Comments in both places say so. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/test.yml | 7 ++++++ crates/attestation/Cargo.toml | 24 +++++++++++++++------ crates/attestation/README.md | 16 +++++++++++--- crates/attestation/build.rs | 16 ++++++++++++++ crates/attestation/src/azure/mod.rs | 26 +++++++++++------------ crates/attestation/src/azure/tpm_quote.rs | 8 ++++--- crates/attestation/src/lib.rs | 11 +++++----- readme.md | 5 +++-- 8 files changed, 81 insertions(+), 32 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 81bbb78..b956f64 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,5 +73,12 @@ jobs: - name: Check azure-verifier builds without the TPM stack run: cargo check -p attestation --no-default-features --features azure-verifier + # `azure-attester` carries its generation dependencies per-target, so off + # x86_64 linux it has to stay enabled and contribute nothing rather than + # fail to build. macos-latest is aarch64-darwin, the target + # tss-esapi-sys has no bindings for. + - name: Check azure-attester builds off x86_64 linux + run: cargo check -p attestation --all-features + - name: Test azure-verifier run: cargo test -p attestation --no-default-features --features azure-verifier diff --git a/crates/attestation/Cargo.toml b/crates/attestation/Cargo.toml index 764df23..46faf90 100644 --- a/crates/attestation/Cargo.toml +++ b/crates/attestation/Cargo.toml @@ -36,12 +36,23 @@ ureq = "2.12.1" webpki = { package = "rustls-webpki", version = "0.103.8" } x509-parser = "0.18.0" -# Used for azure vTPM attestation support. minimal cvm crate for verification -# and full tdx crate for generation (linux only because links tpm2-tss). +# Needed for verification of azure vTPM evidence. No features on purpose since az-cvm-vtpm's +# verifier feature implies its `tpm` one, which pulls tss-esapi and the native TPM libraries. +# See https://github.com/kinvolk/azure-cvm-tooling/issues/95 az-cvm-vtpm = { version = "0.7.4", default-features = false, optional = true } +openssl = { version = "0.10.79", optional = true } + +[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] +# Needed for generation of azure vTPM evidence, which reads the vTPM through tss-esapi and +# attests Intel TDX. An Azure TDX CVM is x86_64 linux, so the generation code is only +# meaningful there, and neither of these builds elsewhere in any case. +# Declaring them per-target rather than gating on the feature alone keeps `azure-attester` +# (and so --all-features) resolvable on every platform; off this target the feature stays +# enabled but contributes nothing, as the crate readme describes. build.rs derives the matching +# `azure_attester_x86_64_linux` cfg, and must keep the same condition as this table. +# See https://github.com/virtee/sev/issues/369 az-tdx-vtpm = { version = "0.7.4", optional = true } tss-esapi = { version = "7.6.0", optional = true } -openssl = { version = "0.10.79", optional = true } [dev-dependencies] mock-tdx = { workspace = true } @@ -58,9 +69,10 @@ serde_json = "1.0.145" [features] default = [] -# Generation of Microsoft Azure attestation evidence on an Azure TDX CVM. -# Implies `azure-verifier`. Reads the vTPM through tss-esapi, so it builds -# only where the native tpm2-tss libraries are available (Linux). +# Opts into generating Azure evidence, and so into the native tpm2-tss stack +# that tss-esapi links. Code should be gated on the `azure_attester_x86_64_linux` cfg +# injected by build.rs rather than on this feature, which says nothing about the target. +# The crate readme covers what enabling it does off x86_64 linux. azure-attester = ["azure-verifier", "dep:az-tdx-vtpm", "dep:tss-esapi"] # Verification of Microsoft Azure attestation evidence. Pure computation diff --git a/crates/attestation/README.md b/crates/attestation/README.md index c7df79e..068f15d 100644 --- a/crates/attestation/README.md +++ b/crates/attestation/README.md @@ -48,9 +48,19 @@ attestation support. This feature requires [tpm2](https://tpm2-software.github.io) and `openssl` to be installed. On Debian-based systems tpm2 is provided by [`libtss2-dev`](https://packages.debian.org/trixie/libtss2-dev), and on nix -`tpm2-tss`. This dependency is currently not packaged for MacOS, meaning -currently it is not possible to compile or run with the `azure-attester` -feature on MacOS. +`tpm2-tss`. + +The generation code is compiled on x86_64 Linux targets only — the platform +an Azure TDX CVM actually runs on, and the only one where the vTPM and the +native TPM stack exist. **On every other target, including MacOS and +aarch64 Linux, `azure-attester` stays enabled but contributes nothing.** +Cargo still reports the feature as on; what changes is that its generation +dependencies are target-gated out of the dependency graph and the +generation code is not compiled, leaving the same compiled surface as +`azure-verifier` on its own. So the crate builds (`--all-features` works +everywhere), but `AttestationType::detect` will not report `AzureTdx` and +generating Azure evidence fails with `AttestationTypeNotSupported`. Verifying +Azure evidence is unaffected. If you need generation, build on x86_64 Linux. **Note:** Azure support is currently **not actively maintained** as we do not have production CVMs deployed on Azure and so are unlikely to notice when this diff --git a/crates/attestation/build.rs b/crates/attestation/build.rs index 82362d1..c3d18df 100644 --- a/crates/attestation/build.rs +++ b/crates/attestation/build.rs @@ -8,6 +8,22 @@ const FIRMWARE_DIR: &str = "assets/ovmf"; const GENERATED_FIRMWARE: &str = "trusted-firmware.json"; fn main() { + // Gate for the Azure evidence generation code. It takes a cfg rather + // than the `azure-attester` feature alone because it only compiles + // where az-tdx-vtpm and tss-esapi resolve, so this condition has to + // stay identical to their target table in Cargo.toml. The + // CARGO_CFG_TARGET_* vars describe the target rather than the build + // host, which keeps the two in agreement when cross-compiling. The + // check-cfg goes outside the branch: the name is expected on every + // target, including those where the code it gates is switched off. + println!("cargo::rustc-check-cfg=cfg(azure_attester_x86_64_linux)"); + if env::var_os("CARGO_FEATURE_AZURE_ATTESTER").is_some() && + env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux") && + env::var("CARGO_CFG_TARGET_ARCH").as_deref() == Ok("x86_64") + { + println!("cargo::rustc-cfg=azure_attester_x86_64_linux"); + } + println!("cargo:rerun-if-changed={FIRMWARE_DIR}"); let mut paths = fs::read_dir(FIRMWARE_DIR) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index d0cfd10..fd0e778 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -1,12 +1,12 @@ //! Microsoft Azure vTPM attestation evidence generation and verification mod ak_certificate; -#[cfg(feature = "azure-attester")] +#[cfg(azure_attester_x86_64_linux)] mod attester; mod tpm_quote; mod tpms_attest; mod verify; -#[cfg(feature = "azure-attester")] +#[cfg(azure_attester_x86_64_linux)] pub use attester::{create_azure_attestation, detect_azure_cvm}; use az_cvm_vtpm::hcl; use openssl::error::ErrorStack; @@ -165,39 +165,39 @@ pub enum MaaError { DcapVerification(#[from] crate::dcap::DcapVerificationError), // Errors that can only occur during evidence generation on an Azure CVM - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("Report: {0}")] Report(#[from] az_tdx_vtpm::report::ReportError), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("IMDS: {0}")] Imds(#[from] az_tdx_vtpm::imds::ImdsError), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("vTPM report: {0}")] VtpmReport(#[from] az_tdx_vtpm::vtpm::ReportError), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("vTPM quote: {0}")] VtpmQuote(#[from] az_tdx_vtpm::vtpm::QuoteError), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("vTPM read: {0}")] TssEsapi(#[from] tss_esapi::Error), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("IO: {0}")] Io(#[from] std::io::Error), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("AIA URL is not HTTP(S): {url}")] UnsupportedAiaUrl { url: String }, - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("Failed to fetch AIA issuer certificate from {url}: {source}")] AiaFetch { url: String, source: Box }, - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error( "Azure vTPM AK issuer chain exceeded maximum intermediate certificate count: {max_depth}" )] AkIssuerChainTooDeep { max_depth: usize }, - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("Azure vTPM AK issuer chain could not be built to a pinned Azure root certificate")] AkIssuerChainIncomplete, - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error( "Azure metadata API returned a successful response with non-JSON content-type: {content_type:?}" )] diff --git a/crates/attestation/src/azure/tpm_quote.rs b/crates/attestation/src/azure/tpm_quote.rs index 8cefaab..16b08bb 100644 --- a/crates/attestation/src/azure/tpm_quote.rs +++ b/crates/attestation/src/azure/tpm_quote.rs @@ -14,9 +14,11 @@ //! extraction done by the `tpms_attest` parser instead of tss-esapi. It is //! vendored because az-cvm-vtpm's verifier feature currently requires its //! TPM device support (tss-esapi links the native tpm2-tss libraries, -//! making such builds Linux-only). If upstream decouples verification -//! from the TPM stack, this module can be retired in favour of depending -//! on az-cvm-vtpm's verifier again. +//! making such builds Linux-only). Tracked upstream as +//! ; if upstream +//! decouples verification from the TPM stack, this module and +//! [`super::tpms_attest`] can be retired in favour of depending on +//! az-cvm-vtpm's verifier again. use openssl::{ hash::MessageDigest, diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index b5706c4..0d432a0 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -2,7 +2,8 @@ // `azure-verifier` is the base Azure feature: it gates the whole module, // and `azure-attester` (which implies it) additionally enables the -// generation code inside. +// generation code inside, on the x86_64 linux targets where the vTPM it +// reads exists. #[cfg(feature = "azure-verifier")] pub mod azure; pub mod dcap; @@ -171,7 +172,7 @@ impl AttestationType { /// Detect what platform we are on by attempting an attestation pub fn detect() -> Result { // First attempt azure, if the feature is present - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] { if azure::detect_azure_cvm()? { return Ok(AttestationType::AzureTdx); @@ -282,7 +283,7 @@ impl AttestationGenerator { match self.attestation_type { AttestationType::None => Ok(AttestationExchangeMessage::without_attestation()), AttestationType::AzureTdx => { - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] { let platform = attest_measure::platform::metadata_for( self.attestation_type.try_into()?, @@ -294,10 +295,10 @@ impl AttestationGenerator { }), }) } - #[cfg(not(feature = "azure-attester"))] + #[cfg(not(azure_attester_x86_64_linux))] { tracing::error!( - "Attempted to generate an azure attestation but the `azure-attester` feature not enabled" + "Azure attestation generation requires the `azure-attester` feature on an x86_64 linux host" ); Err(AttestationError::AttestationTypeNotSupported) } diff --git a/readme.md b/readme.md index 2416f3d..ff2fbc9 100644 --- a/readme.md +++ b/readme.md @@ -51,6 +51,7 @@ More details in the individual READMEs of the provided crates: The included `shell.nix` file can be used with `nix-shell`, `direnv`, or `nix develop` to add the dependencies needed by the optional `azure-attester` -feature of the `attestation` crate on Linux. The `azure-verifier` feature -needs no extra system dependencies and builds on any platform. See the +feature of the `attestation` crate on x86_64 Linux, the only target its +generation code is compiled for. The `azure-verifier` feature needs no extra +system dependencies and builds on any platform. See the [`attestation` crate readme](./crates/attestation) for details.