From 90d28911ccc4b41e4008587d6843234263b6e073 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 9 Jul 2026 15:34:39 -0500 Subject: [PATCH] Honor EdgeZero app config store default --- .../src/app.rs | 90 +++++++++++++++++-- .../wrangler.toml | 6 +- .../trusted-server-adapter-fastly/src/main.rs | 12 ++- crates/trusted-server-core/Cargo.toml | 3 + crates/trusted-server-core/build.rs | 23 ++++- .../trusted-server-core/src/config_payload.rs | 4 +- .../trusted-server-core/src/settings_data.rs | 67 +++++++++++++- .../fixtures/configs/viceroy-template.toml | 2 +- .../src/bin/generate-viceroy-config.rs | 19 ++-- .../tests/common/config.rs | 7 +- .../tests/environments/axum.rs | 10 ++- docs/guide/configuration.md | 29 +++--- edgezero.toml | 2 +- 13 files changed, 220 insertions(+), 54 deletions(-) diff --git a/crates/trusted-server-adapter-cloudflare/src/app.rs b/crates/trusted-server-adapter-cloudflare/src/app.rs index c931360f6..65427dab9 100644 --- a/crates/trusted-server-adapter-cloudflare/src/app.rs +++ b/crates/trusted-server-adapter-cloudflare/src/app.rs @@ -10,6 +10,8 @@ use edgezero_core::router::RouterService; use error_stack::Report; use trusted_server_core::auction::endpoints::handle_auction; use trusted_server_core::auction::{AuctionOrchestrator, build_orchestrator}; +#[cfg(any(test, target_arch = "wasm32"))] +use trusted_server_core::config_payload::CONFIG_BLOB_KEY; #[cfg(target_arch = "wasm32")] use trusted_server_core::config_payload::settings_from_config_blob; use trusted_server_core::ec::EcContext; @@ -78,7 +80,9 @@ fn settings_from_cloudflare_config_json() -> Result Result Option<&str> { + const LEGACY_CONFIG_BLOB_KEY: &str = "app_config"; + + match value.get(CONFIG_BLOB_KEY) { + Some(envelope) => envelope.as_str(), + None if CONFIG_BLOB_KEY != LEGACY_CONFIG_BLOB_KEY => value + .get(LEGACY_CONFIG_BLOB_KEY) + .and_then(serde_json::Value::as_str), + None => None, + } +} + /// Build the application state from explicit settings. /// /// # Errors @@ -554,3 +570,59 @@ fn build_router(state: &Arc) -> RouterService { router.build() } } + +#[cfg(test)] +mod tests { + use super::*; + + fn config_value(entries: &[(&str, &str)]) -> serde_json::Value { + serde_json::Value::Object( + entries + .iter() + .map(|(key, value)| { + ( + (*key).to_string(), + serde_json::Value::String((*value).to_string()), + ) + }) + .collect(), + ) + } + + #[test] + fn cloudflare_config_prefers_manifest_default_key() { + let value = config_value(&[ + ("app_config", "legacy-envelope"), + (CONFIG_BLOB_KEY, "manifest-envelope"), + ]); + + assert_eq!( + cloudflare_config_envelope(&value), + Some("manifest-envelope"), + "manifest-derived key should take precedence" + ); + } + + #[test] + fn cloudflare_config_accepts_legacy_app_config_key() { + let value = config_value(&[("app_config", "legacy-envelope")]); + + assert_eq!( + cloudflare_config_envelope(&value), + Some("legacy-envelope"), + "legacy app_config key should remain compatible" + ); + } + + #[test] + fn cloudflare_config_does_not_mask_malformed_manifest_value() { + let mut value = config_value(&[("app_config", "legacy-envelope")]); + value[CONFIG_BLOB_KEY] = serde_json::Value::Bool(true); + + assert_eq!( + cloudflare_config_envelope(&value), + None, + "malformed manifest-derived value should not fall back" + ); + } +} diff --git a/crates/trusted-server-adapter-cloudflare/wrangler.toml b/crates/trusted-server-adapter-cloudflare/wrangler.toml index 4d35e5c91..52295c413 100644 --- a/crates/trusted-server-adapter-cloudflare/wrangler.toml +++ b/crates/trusted-server-adapter-cloudflare/wrangler.toml @@ -18,6 +18,6 @@ id = "REPLACE_WITH_YOUR_KV_NAMESPACE_ID" [vars] # TRUSTED_SERVER_CONFIG is required at startup. Replace this intentionally -# invalid placeholder with JSON containing an `app_config` blob envelope before -# deploying or running `wrangler dev` against real traffic. -TRUSTED_SERVER_CONFIG = '{"app_config":""}' +# invalid placeholder with JSON containing the manifest-default app-config blob +# envelope before deploying or running `wrangler dev` against real traffic. +TRUSTED_SERVER_CONFIG = '{"trusted_server_config":""}' diff --git a/crates/trusted-server-adapter-fastly/src/main.rs b/crates/trusted-server-adapter-fastly/src/main.rs index d20de533d..299dfaae1 100644 --- a/crates/trusted-server-adapter-fastly/src/main.rs +++ b/crates/trusted-server-adapter-fastly/src/main.rs @@ -24,6 +24,7 @@ use trusted_server_core::platform::PlatformGeo as _; use trusted_server_core::platform::RuntimeServices; use trusted_server_core::proxy::{stream_asset_body, AssetProxyCachePolicy}; use trusted_server_core::settings::Settings; +use trusted_server_core::settings_data::default_config_store_name; mod app; mod backend; @@ -42,18 +43,15 @@ use crate::middleware::{apply_finalize_headers, resolve_geo_for_response, HEADER use crate::platform::{client_info_from_request, FastlyPlatformGeo}; use crate::rate_limiter::{FastlyRateLimiter, RATE_COUNTER_NAME}; -const TRUSTED_SERVER_CONFIG_STORE: &str = "trusted_server_config"; - -/// Opens the Fastly Config Store used by the `EdgeZero` dispatcher. +/// Opens the manifest-default Fastly Config Store used by the `EdgeZero` dispatcher. /// /// # Errors /// /// Returns [`fastly::Error`] if the config store cannot be opened. fn open_trusted_server_config_store() -> Result { - let store = EdgeZeroFastlyConfigStore::try_open(TRUSTED_SERVER_CONFIG_STORE).map_err(|e| { - fastly::Error::msg(format!( - "failed to open config store `{TRUSTED_SERVER_CONFIG_STORE}`: {e}" - )) + let store_name = default_config_store_name(); + let store = EdgeZeroFastlyConfigStore::try_open(store_name.as_ref()).map_err(|e| { + fastly::Error::msg(format!("failed to open config store `{store_name}`: {e}")) })?; Ok(ConfigStoreHandle::new(Arc::new(store))) } diff --git a/crates/trusted-server-core/Cargo.toml b/crates/trusted-server-core/Cargo.toml index ba88f361f..40b68182c 100644 --- a/crates/trusted-server-core/Cargo.toml +++ b/crates/trusted-server-core/Cargo.toml @@ -57,6 +57,9 @@ web-time = { workspace = true } getrandom = { workspace = true, features = ["js"] } uuid = { workspace = true, features = ["js"] } +[build-dependencies] +edgezero-core = { workspace = true } + [features] default = [] # Exposes test-only constructors (e.g. `IntegrationRegistry::from_request_filters`) diff --git a/crates/trusted-server-core/build.rs b/crates/trusted-server-core/build.rs index c2bce4fe2..f8b9d38ba 100644 --- a/crates/trusted-server-core/build.rs +++ b/crates/trusted-server-core/build.rs @@ -1,3 +1,24 @@ +use std::env; +use std::path::PathBuf; + +use edgezero_core::manifest::ManifestLoader; + fn main() { - println!("cargo:rerun-if-changed=build.rs"); + let manifest_path = PathBuf::from( + env::var("CARGO_MANIFEST_DIR").expect("should receive CARGO_MANIFEST_DIR from Cargo"), + ) + .join("../..") + .join("edgezero.toml"); + println!("cargo:rerun-if-changed={}", manifest_path.display()); + + let manifest = ManifestLoader::from_path(&manifest_path) + .expect("should load the repository EdgeZero manifest"); + let config_store = manifest + .manifest() + .stores + .config + .as_ref() + .expect("should declare [stores.config] in edgezero.toml"); + let default_store_id = config_store.default_id(); + println!("cargo:rustc-env=TRUSTED_SERVER_DEFAULT_CONFIG_STORE_ID={default_store_id}"); } diff --git a/crates/trusted-server-core/src/config_payload.rs b/crates/trusted-server-core/src/config_payload.rs index dd0b35337..d42fff67a 100644 --- a/crates/trusted-server-core/src/config_payload.rs +++ b/crates/trusted-server-core/src/config_payload.rs @@ -12,7 +12,9 @@ use crate::error::TrustedServerError; use crate::settings::Settings; /// Default config-store key containing the Trusted Server app-config blob. -pub const CONFIG_BLOB_KEY: &str = "trusted_server_config"; +/// +/// Derived from `[stores.config].default` in `edgezero.toml` at build time. +pub const CONFIG_BLOB_KEY: &str = env!("TRUSTED_SERVER_DEFAULT_CONFIG_STORE_ID"); /// Reconstruct validated [`Settings`] from a serialized config blob envelope. /// diff --git a/crates/trusted-server-core/src/settings_data.rs b/crates/trusted-server-core/src/settings_data.rs index 06ea548fc..1fdd38185 100644 --- a/crates/trusted-server-core/src/settings_data.rs +++ b/crates/trusted-server-core/src/settings_data.rs @@ -3,12 +3,11 @@ use error_stack::{Report, ResultExt}; use serde::Deserialize; use sha2::{Digest as _, Sha256}; -use crate::config_payload::settings_from_config_blob; +use crate::config_payload::{settings_from_config_blob, CONFIG_BLOB_KEY}; use crate::error::TrustedServerError; use crate::platform::{PlatformConfigStore, StoreName}; use crate::settings::Settings; -const DEFAULT_CONFIG_STORE_ID: &str = "trusted_server_config"; const FASTLY_CHUNK_POINTER_KIND: &str = "fastly_config_chunks"; const FASTLY_CONFIG_ENTRY_LIMIT: usize = 8_000; @@ -31,13 +30,21 @@ struct FastlyChunkRef { /// Returns the default `EdgeZero` app-config store name. #[must_use] pub fn default_config_store_name() -> StoreName { - StoreName::from(EnvConfig::from_env().store_name("config", DEFAULT_CONFIG_STORE_ID)) + default_config_store_name_from_env(&EnvConfig::from_env()) +} + +fn default_config_store_name_from_env(env_config: &EnvConfig) -> StoreName { + StoreName::from(env_config.store_name("config", CONFIG_BLOB_KEY)) } /// Returns the default config-store key containing the app-config blob. #[must_use] pub fn default_config_key() -> String { - EnvConfig::from_env().store_key("config", DEFAULT_CONFIG_STORE_ID) + default_config_key_from_env(&EnvConfig::from_env()) +} + +fn default_config_key_from_env(env_config: &EnvConfig) -> String { + env_config.store_key("config", CONFIG_BLOB_KEY) } /// Loads [`Settings`] from a platform config store and key. @@ -219,6 +226,58 @@ mod tests { serde_json::to_string(&envelope).expect("should serialize envelope") } + #[test] + fn config_defaults_match_edgezero_manifest() { + let manifest = edgezero_core::manifest::ManifestLoader::try_load_from_str(include_str!( + "../../../edgezero.toml" + )) + .expect("should load the repository EdgeZero manifest"); + let manifest_default = manifest + .manifest() + .stores + .config + .as_ref() + .expect("should declare [stores.config]") + .default_id(); + + assert_eq!( + CONFIG_BLOB_KEY, manifest_default, + "compiled default should match edgezero.toml" + ); + assert_eq!( + manifest_default, "trusted_server_config", + "Trusted Server should retain its expected default config store" + ); + } + + #[test] + fn config_defaults_honor_edgezero_store_name_and_key_overrides() { + let name_variable = format!( + "EDGEZERO__STORES__CONFIG__{}__NAME", + CONFIG_BLOB_KEY.to_ascii_uppercase() + ); + let name_config = EnvConfig::from_vars([(name_variable, "example-config-store")]); + let store_name = default_config_store_name_from_env(&name_config); + + assert_eq!( + store_name.as_ref(), + "example-config-store", + "store name should use the override for the manifest default id" + ); + + let key_variable = format!( + "EDGEZERO__STORES__CONFIG__{}__KEY", + CONFIG_BLOB_KEY.to_ascii_uppercase() + ); + let key_config = EnvConfig::from_vars([(key_variable, "example-config-key")]); + + assert_eq!( + default_config_key_from_env(&key_config), + "example-config-key", + "blob key should use the override for the manifest default id" + ); + } + #[test] fn loads_settings_from_config_blob_entry() { let settings = diff --git a/crates/trusted-server-integration-tests/fixtures/configs/viceroy-template.toml b/crates/trusted-server-integration-tests/fixtures/configs/viceroy-template.toml index 9f1443d20..e45e564c5 100644 --- a/crates/trusted-server-integration-tests/fixtures/configs/viceroy-template.toml +++ b/crates/trusted-server-integration-tests/fixtures/configs/viceroy-template.toml @@ -67,7 +67,7 @@ data = "test-api-key" [local_server.config_stores] - # Generated integration configs inject the trusted_server_config blob + # Generated integration configs inject the manifest-default app-config blob # into the store required by the Fastly entry point. # GENERATED_TRUSTED_SERVER_CONFIG_STORES diff --git a/crates/trusted-server-integration-tests/src/bin/generate-viceroy-config.rs b/crates/trusted-server-integration-tests/src/bin/generate-viceroy-config.rs index 85b1bcf0f..a5f6f21c6 100644 --- a/crates/trusted-server-integration-tests/src/bin/generate-viceroy-config.rs +++ b/crates/trusted-server-integration-tests/src/bin/generate-viceroy-config.rs @@ -4,7 +4,9 @@ use std::fs; use std::path::PathBuf; use edgezero_core::blob_envelope::BlobEnvelope; -use trusted_server_core::{config::validate_settings_for_deploy, settings::Settings}; +use trusted_server_core::{ + config::validate_settings_for_deploy, config_payload::CONFIG_BLOB_KEY, settings::Settings, +}; const GENERATED_AT: &str = "2026-06-23T00:00:00Z"; const GENERATED_STORES_MARKER: &str = " # GENERATED_TRUSTED_SERVER_CONFIG_STORES"; @@ -147,10 +149,10 @@ fn inject_generated_config_stores(template: &str, envelope_json: &str) -> Result fn generated_config_store_blocks(envelope_json: &str) -> String { format!( r#" # Generated by generate-viceroy-config. Do not edit generated output. - [local_server.config_stores.trusted_server_config] + [local_server.config_stores.{CONFIG_BLOB_KEY}] format = "inline-toml" - [local_server.config_stores.trusted_server_config.contents] - trusted_server_config = '''{envelope_json}'''"# + [local_server.config_stores.{CONFIG_BLOB_KEY}.contents] + {CONFIG_BLOB_KEY} = '''{envelope_json}'''"# ) } @@ -216,8 +218,8 @@ mod tests { .expect("should inject generated stores"); assert!( - generated.contains("[local_server.config_stores.trusted_server_config]"), - "should include app config store" + generated.contains(&format!("[local_server.config_stores.{CONFIG_BLOB_KEY}]")), + "should include manifest-default app config store" ); assert!( !generated.contains("edgezero_enabled"), @@ -241,11 +243,10 @@ mod tests { let parsed: toml::Value = toml::from_str(&generated).expect("should parse as TOML"); assert_eq!( - parsed["local_server"]["config_stores"]["trusted_server_config"]["contents"] - ["trusted_server_config"] + parsed["local_server"]["config_stores"][CONFIG_BLOB_KEY]["contents"][CONFIG_BLOB_KEY] .as_str(), Some(envelope.as_str()), - "trusted_server_config should contain the app-config blob" + "manifest-default config store should contain the app-config blob" ); } diff --git a/crates/trusted-server-integration-tests/tests/common/config.rs b/crates/trusted-server-integration-tests/tests/common/config.rs index 4dc971d0e..d89308155 100644 --- a/crates/trusted-server-integration-tests/tests/common/config.rs +++ b/crates/trusted-server-integration-tests/tests/common/config.rs @@ -1,6 +1,7 @@ use edgezero_core::blob_envelope::BlobEnvelope; use error_stack::Report; use trusted_server_core::config::validate_settings_for_deploy; +use trusted_server_core::config_payload::CONFIG_BLOB_KEY; use trusted_server_core::settings::Settings; use crate::common::runtime::{TestError, TestResult}; @@ -35,7 +36,11 @@ pub fn integration_app_config_envelope(origin_port: u16) -> TestResult { pub fn cloudflare_config_json(origin_port: u16) -> TestResult { let envelope = integration_app_config_envelope(origin_port)?; - serde_json::to_string(&serde_json::json!({ "app_config": envelope })).map_err(|error| { + let config = serde_json::Value::Object(serde_json::Map::from_iter([( + CONFIG_BLOB_KEY.to_string(), + serde_json::Value::String(envelope), + )])); + serde_json::to_string(&config).map_err(|error| { Report::new(TestError::ConfigGeneration).attach(format!( "failed to serialize Cloudflare config binding: {error}" )) diff --git a/crates/trusted-server-integration-tests/tests/environments/axum.rs b/crates/trusted-server-integration-tests/tests/environments/axum.rs index 235af413f..43719a6bd 100644 --- a/crates/trusted-server-integration-tests/tests/environments/axum.rs +++ b/crates/trusted-server-integration-tests/tests/environments/axum.rs @@ -6,6 +6,7 @@ use error_stack::ResultExt as _; use std::io::{BufRead as _, BufReader}; use std::path::Path; use std::process::{Child, Command, Stdio}; +use trusted_server_core::config_payload::CONFIG_BLOB_KEY; /// Default port the Axum dev server binds to when no `PORT` env var is supplied. const AXUM_DEFAULT_PORT: u16 = 8787; @@ -33,13 +34,14 @@ impl RuntimeEnvironment for AxumDevServer { let port = super::find_available_port().unwrap_or(AXUM_DEFAULT_PORT); let app_config = integration_app_config_envelope(origin_port())?; + let config_segment = CONFIG_BLOB_KEY + .to_ascii_uppercase() + .replace(['-', '.', ' '], "_"); + let config_variable = format!("TRUSTED_SERVER_CONFIG_{config_segment}_{config_segment}"); let mut child = Command::new(&binary) .env("PORT", port.to_string()) - .env( - "TRUSTED_SERVER_CONFIG_TRUSTED_SERVER_CONFIG_TRUSTED_SERVER_CONFIG", - app_config, - ) + .env(config_variable, app_config) .stdout(Stdio::null()) .stderr(Stdio::piped()) .spawn() diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index b975590c6..8a5236831 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -1235,25 +1235,28 @@ After the EdgeZero cutover, the Fastly adapter always dispatches through the EdgeZero entry point. The former `edgezero_enabled` and `edgezero_rollout_pct` canary keys are no longer read. -The Fastly service must still provide a `trusted_server_config` config store -because the entry point opens it before dispatch and passes the handle to -EdgeZero-backed platform services. The store may be empty unless another feature -adds keys to it. +The Fastly service must provide the logical config store selected by +`[stores.config].default` in `edgezero.toml`. By default, the logical store ID +is also the platform store name and the app-config blob key. Deployments can +override those independently with +`EDGEZERO__STORES__CONFIG____NAME` and +`EDGEZERO__STORES__CONFIG____KEY`. -**Local development** (`fastly.toml`): +The resolved store and key must contain a valid Trusted Server app-config blob +envelope. An absent or empty entry makes application startup fail closed. Use +the Trusted Server CLI to provision the store and publish the validated config. -```toml -[local_server.config_stores] - [local_server.config_stores.trusted_server_config] - format = "inline-toml" - [local_server.config_stores.trusted_server_config.contents] +**Local development** (writes the entry used by Viceroy in `fastly.toml`): + +```bash +ts config push --adapter fastly --local ``` -**Production setup** (Fastly CLI): +**Production setup**: ```bash -# Create the store once and attach it to the service. -fastly config-store create --name trusted_server_config +ts provision --adapter fastly +ts config push --adapter fastly ``` Rollback to the legacy entry point is no longer controlled by runtime config diff --git a/edgezero.toml b/edgezero.toml index 2120ca5c9..1695e8023 100644 --- a/edgezero.toml +++ b/edgezero.toml @@ -16,7 +16,7 @@ version = "0.1.0" # -- Stores ------------------------------------------------------------------ # Logical store ids only. These are the portable Trusted Server names; the # physical store each adapter binds is overridable out of band (Fastly binds -# `ec_identity_store`/`app_config`/secret stores in `fastly.toml`, Spin via its +# `ec_identity_store`/`trusted_server_config`/secret stores in `fastly.toml`, Spin via its # runtime config, Cloudflare via bindings), so the ids here do not have to match # any one platform's names. `default` is the primary logical id.