feat: add Google Metadata support and TDX container test

- Introduced `google-metadata` binary for reading GCP instance attributes.
- Added TDX container test with new `container-test-tdx` package.
- Updated Nix workflow and deployment scripts for Google Metadata integration.
- Bumped `anyhow` to 1.0.95 and updated Cargo.lock.

Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
Harald Hoyer 2025-01-21 16:42:52 +01:00
parent e2c31919c9
commit 11a22c9e67
Signed by: harald
GPG key ID: F519A1143B3FBE32
16 changed files with 286 additions and 52 deletions

View file

@ -73,6 +73,7 @@ jobs:
- { nixpackage: 'container-self-attestation-test-sgx-azure' }
- { nixpackage: 'container-verify-attestation-sgx' }
- { nixpackage: 'container-verify-era-proof-attestation-sgx' }
- { nixpackage: 'container-test-tdx' }
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v30

133
Cargo.lock generated
View file

@ -337,9 +337,9 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anyhow"
version = "1.0.94"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "argon2"
@ -1927,8 +1927,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
@ -1999,6 +2001,18 @@ dependencies = [
"web-sys",
]
[[package]]
name = "google-metadata"
version = "0.3.0"
dependencies = [
"anyhow",
"reqwest 0.12.9",
"reqwest-middleware",
"reqwest-retry",
"serde_json",
"tokio",
]
[[package]]
name = "gpt"
version = "4.0.0"
@ -2596,6 +2610,18 @@ dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "intel-tee-quote-verification-sys"
version = "0.2.1"
@ -3575,6 +3601,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
@ -3582,7 +3619,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
"parking_lot_core 0.9.10",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
]
[[package]]
@ -3593,7 +3644,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"redox_syscall 0.5.8",
"smallvec",
"windows-targets 0.52.6",
]
@ -3905,7 +3956,7 @@ checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca"
dependencies = [
"dtoa",
"itoa",
"parking_lot",
"parking_lot 0.12.3",
"prometheus-client-derive-encode",
]
@ -4099,6 +4150,15 @@ dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.8"
@ -4242,6 +4302,52 @@ dependencies = [
"windows-registry",
]
[[package]]
name = "reqwest-middleware"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3"
dependencies = [
"anyhow",
"async-trait",
"http 1.2.0",
"reqwest 0.12.9",
"serde",
"thiserror",
"tower-service",
]
[[package]]
name = "reqwest-retry"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178"
dependencies = [
"anyhow",
"async-trait",
"futures",
"getrandom",
"http 1.2.0",
"hyper 1.5.2",
"parking_lot 0.11.2",
"reqwest 0.12.9",
"reqwest-middleware",
"retry-policies",
"thiserror",
"tokio",
"tracing",
"wasm-timer",
]
[[package]]
name = "retry-policies"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c"
dependencies = [
"rand",
]
[[package]]
name = "rfc6979"
version = "0.3.1"
@ -5543,7 +5649,7 @@ dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"parking_lot 0.12.3",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@ -6200,6 +6306,21 @@ version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "wasm-timer"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f"
dependencies = [
"futures",
"js-sys",
"parking_lot 0.11.2",
"pin-utils",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.76"

View file

@ -42,6 +42,8 @@ pgp = "0.14.2"
pkcs8 = { version = "0.10" }
rand = "0.8"
reqwest = { version = "0.12", features = ["json"] }
reqwest-middleware = "0.4.0"
reqwest-retry = "0.7.0"
rsa = { version = "0.9.6", features = ["sha2", "pem"] }
rustls = { version = "0.23.20" }
secp256k1 = { version = "0.29", features = ["rand-std", "global-context"] }

4
assets/config.json Normal file
View file

@ -0,0 +1,4 @@
{
"foo": "bar",
"bar": "baz"
}

View file

@ -7,8 +7,12 @@
set -ex
BASE_DIR=${0%/*}
NO=${NO:-1}
ZONE=${ZONE:-us-central1-c}
nix build -L .#tdx_google
gsutil cp result/tdx_base_1.vmdk gs://tdx_vms/
@ -21,8 +25,8 @@ gcloud migration vms image-imports create \
--source-file=gs://tdx_vms/tdx_base_1.vmdk \
tdx-img-pre-"${NO}"
gcloud compute instances stop tdx-pilot --zone us-central1-c --project tdx-pilot || :
gcloud compute instances delete tdx-pilot --zone us-central1-c --project tdx-pilot || :
gcloud compute instances stop tdx-pilot --zone ${ZONE} --project tdx-pilot || :
gcloud compute instances delete tdx-pilot --zone ${ZONE} --project tdx-pilot || :
while gcloud migration vms image-imports list --location=us-central1 --project=tdx-pilot | grep -F RUNNING; do
sleep 1
@ -36,10 +40,11 @@ gcloud compute images create \
tdx-img-f-"${NO}"
gcloud compute instances create tdx-pilot \
--machine-type c3-standard-4 --zone us-central1-c \
--machine-type c3-standard-4 --zone ${ZONE} \
--confidential-compute-type=TDX \
--maintenance-policy=TERMINATE \
--image-project=tdx-pilot \
--project tdx-pilot \
--metadata=container_hub="docker.io",container_image="amd64/hello-world@sha256:e2fc4e5012d16e7fe466f5291c476431beaa1f9b90a5c2125b493ed28e2aba57" \
--metadata=container_hub="docker.io",container_image="matterlabsrobot/test-tdx:117p5y281limw0w7b03v802ij00c5gzw" \
--metadata-from-file=container_config=$BASE_DIR/config.json \
--image tdx-img-f-"${NO}"

View file

@ -0,0 +1,16 @@
[package]
name = "google-metadata"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
[dependencies]
anyhow.workspace = true
reqwest.workspace = true
reqwest-middleware.workspace = true
reqwest-retry.workspace = true
serde_json.workspace = true
tokio.workspace = true

View file

@ -0,0 +1,74 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2025 Matter Labs
use anyhow::{bail, Result};
use reqwest::Client;
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use reqwest_retry::{policies::ExponentialBackoff, Jitter, RetryTransientMiddleware};
use serde_json::Value;
use std::time::Duration;
const DEFAULT_INSTANCE_METADATA_BASE_URL: &str =
"http://metadata.google.internal/computeMetadata/v1/instance/attributes";
async fn fetch_gcp_metadata(
http_client: &ClientWithMiddleware,
metadata_key: &str,
) -> Result<Value> {
// Validate the metadata key:
if metadata_key.is_empty() {
bail!("Empty metadata_key");
}
let url = format!("{DEFAULT_INSTANCE_METADATA_BASE_URL}/{metadata_key}");
// Make an HTTP GET request:
let response = http_client
.get(url)
.header("Metadata-Flavor", "Google")
.send()
.await?;
// Handle response:
if response.status().is_success() {
let metadata_text = response.text().await?;
serde_json::from_str(&metadata_text)
.map_err(|e| anyhow::format_err!("Failed to parse metadata JSON: {}", e))
} else {
let status = response.status();
let error_body = response
.text()
.await
.unwrap_or_else(|_| "<empty>".to_string());
bail!(
"Failed to fetch metadata: {}, Response body: {}",
status,
error_body
);
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Build the client with retry middleware and exponential backoff:
let retry_policy = ExponentialBackoff::builder()
.retry_bounds(Duration::from_secs(1), Duration::from_secs(32))
.jitter(Jitter::Bounded)
.base(2)
.build_with_total_retry_duration(Duration::from_secs(60));
let client = ClientBuilder::new(Client::builder().build()?) // Underlying reqwest client
.with(RetryTransientMiddleware::new_with_policy(retry_policy)) // Add retry middleware
.build();
// Fetch and display metadata:
match fetch_gcp_metadata(&client, "container_config").await {
Ok(container_config) => {
println!("Container config:\n{:#?}", container_config);
}
Err(e) => {
eprintln!("Error fetching container config: {}", e);
}
}
Ok(())
}

View file

@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs
{ teepot }: teepot.teepot.passthru.craneLib.cargoClippy (
teepot.teepot.passthru.commonArgs // {
pname = "teepot";
inherit (teepot.teepot.passthru) cargoArtifacts;
}
)

View file

@ -1,7 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs
{ teepotCrate }: teepotCrate.craneLib.cargoFmt (
teepotCrate.commonArgs // {
{ teepot }: teepot.teepot.passthru.craneLib.cargoDeny (
teepot.teepot.passthru.commonArgs // {
pname = "teepot";
}
)

View file

@ -1,7 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs
{ teepotCrate }: teepotCrate.craneLib.cargoDeny (
teepotCrate.commonArgs // {
{ teepot }: teepot.teepot.passthru.craneLib.cargoFmt (
teepot.teepot.passthru.commonArgs // {
pname = "teepot";
}
)

View file

@ -25,12 +25,9 @@
};
outputs = inputs:
let
src = ./.;
in
inputs.snowfall-lib.mkFlake {
inherit inputs;
inherit src;
src = ./.;
snowfall.namespace = "teepot";
@ -42,8 +39,6 @@
nixsgx-flake.overlays.default
vault-auth-tee-flake.overlays.default
rust-overlay.overlays.default
# somehow the original `src` is not available anymore
(final: prev: { teepotCrate = prev.pkgs.callPackage ./teepot-crate.nix { inherit inputs; inherit src; }; })
];
alias = {
@ -59,16 +54,7 @@
};
outputs-builder = channels: {
formatter = channels.nixpkgs.nixpkgs-fmt;
checks = {
inherit
(channels.nixpkgs.teepot) cargoFmt;
inherit
(channels.nixpkgs.teepot) cargoClippy;
inherit
(channels.nixpkgs.teepot) cargoDeny;
};
formatter = channels.nixpkgs.nixfmt-rfc-style;
};
};
}

View file

@ -1,8 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs
{ teepotCrate }: teepotCrate.craneLib.cargoClippy (
teepotCrate.commonArgs // {
pname = "teepot";
inherit (teepotCrate) cargoArtifacts;
}
)

View file

@ -0,0 +1,24 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs
{ dockerTools
, buildEnv
, teepot
}:
dockerTools.buildLayeredImage {
name = "test-tdx";
config.Entrypoint = [ "${teepot.teepot.google_metadata}/bin/google-metadata" ];
config.Env = [ "LD_LIBRARY_PATH=/lib" ];
contents = buildEnv {
name = "image-root";
paths = with dockerTools;[
teepot.teepot.google_metadata
usrBinEnv
binSh
caCertificates
fakeNss
];
pathsToLink = [ "/bin" "/lib" "/etc" "/share" ];
};
}

View file

@ -1,6 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs
{ lib, pkgs, makeWrapper, teepotCrate }: teepotCrate.craneLib.buildPackage (
{ lib, pkgs, makeWrapper, teepot }:
let teepotCrate = teepot.teepotCrate; in
teepotCrate.craneLib.buildPackage (
teepotCrate.commonArgs // {
pname = "teepot";
inherit (teepotCrate) cargoArtifacts;
@ -17,6 +19,7 @@
outputs = [
"out"
"google_metadata"
"rtmr_calc"
"sha384_extend"
"tdx_extend"

View file

@ -7,11 +7,10 @@
, pkg-config
, rust-bin
, pkgs
, src
, openssl
}:
let
rustVersion = rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
rustVersion = rust-bin.fromRustupToolchainFile (inputs.src + "/rust-toolchain.toml");
rustPlatform = makeRustPlatform {
cargo = rustVersion;
rustc = rustVersion;
@ -34,15 +33,15 @@ let
strictDeps = true;
src = with lib.fileset; toSource {
root = src;
root = inputs.src;
fileset = unions [
./Cargo.lock
./Cargo.toml
./bin
./crates
./rust-toolchain.toml
./deny.toml
./taplo.toml
# Default files from crane (Rust and cargo files)
(craneLib.fileset.commonCargoSources inputs.src)
(fileFilter (file: file.hasExt "hcl") (inputs.src + "/bin"))
# deny.toml and friends
(fileFilter (file: file.hasExt "toml") inputs.src)
# Custom test data files
(maybeMissing (inputs.src + "/crates/teepot/tests/data"))
];
};

View file

@ -6,10 +6,9 @@
, teepot
, nixsgx
, stdenv
, teepotCrate
}:
let
toolchain_with_src = (teepotCrate.rustVersion.override {
toolchain_with_src = (teepot.teepot.passthru.rustVersion.override {
extensions = [ "rustfmt" "clippy" "rust-src" ];
});
in
@ -19,7 +18,7 @@ mkShell {
nativeBuildInputs = with pkgs; [
toolchain_with_src
pkg-config
teepotCrate.rustPlatform.bindgenHook
teepot.teepot.passthru.rustPlatform.bindgenHook
];
packages = with pkgs; [