-
Notifications
You must be signed in to change notification settings - Fork 12
Add native secret-store config resolution #1036
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
00de4e9
0fdfcbe
2783e00
3dc7370
0db2e11
3e2b3d2
b47ced8
58730d7
1b17ce0
76f6f13
44e9eaf
3f6e29d
d71c660
6c35ce9
43c7577
edfbcb0
22176da
3b2c64c
c7c382a
55f0a23
e45ff8e
9831ee4
3c48d39
71df1aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,27 @@ | ||
| # Trusted Server Environment Variables | ||
| # Copy this file to .env.dev, .env.staging, or .env.production and fill in values | ||
| # See docs/guide/configuration.md for details | ||
| # Trusted Server development environment variables | ||
| # Copy this file to .env.dev, .env.staging, or .env.production and fill in | ||
| # non-secret values. App-config secrets are key names in the pushed blob and | ||
| # their values belong in the platform secret store; see the configuration guide. | ||
| # For Axum runtime loading, export the config blob as: | ||
| # TRUSTED_SERVER_CONFIG_TRUSTED_SERVER_CONFIG_TRUSTED_SERVER_CONFIG=<blob-envelope-json> | ||
| # and export one secret per key name as: | ||
| # TRUSTED_SERVER_SECRET_TRUSTED_SERVER_SECRETS_<KEY_NAME>=<secret-value> | ||
| # The commented examples below are CLI overlays for ordinary fields only. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⛏ nitpick — This ordering reads backwards. The line says "The commented examples below are CLI overlays for ordinary fields only", and |
||
| # Fastly example: map logical app-config secrets to physical `ts_secrets`. | ||
| # EDGEZERO__STORES__SECRETS__TRUSTED_SERVER_SECRETS__NAME=ts_secrets | ||
|
|
||
| # ============================================================================= | ||
| # Publisher Settings | ||
| # ============================================================================= | ||
| TRUSTED_SERVER__PUBLISHER__DOMAIN=publisher.com | ||
| TRUSTED_SERVER__PUBLISHER__COOKIE_DOMAIN=.publisher.com | ||
| TRUSTED_SERVER__PUBLISHER__ORIGIN_URL=https://origin.publisher.com | ||
| TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=<your-proxy-secret> | ||
|
|
||
| # ============================================================================= | ||
| # Synthetic ID Settings | ||
| # ============================================================================= | ||
| TRUSTED_SERVER__SYNTHETIC__COUNTER_STORE=counter_store | ||
| TRUSTED_SERVER__SYNTHETIC__OPID_STORE=opid_store | ||
| TRUSTED_SERVER__SYNTHETIC__SECRET_KEY=<your-synthetic-secret> | ||
| # Template variables: client_ip, user_agent, first_party_id, auth_user_id, publisher_domain, accept_language | ||
| TRUSTED_SERVER__SYNTHETIC__TEMPLATE={{ client_ip }}:{{ user_agent }}:{{ first_party_id }} | ||
|
|
||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,7 +38,7 @@ use trusted_server_core::settings_data::{ | |
| use trusted_server_core::platform::RuntimeServices; | ||
|
|
||
| use crate::middleware::{AuthMiddleware, FinalizeResponseMiddleware, SanitizeRequestMiddleware}; | ||
| use crate::platform::{AxumPlatformConfigStore, build_runtime_services}; | ||
| use crate::platform::{AxumPlatformConfigStore, AxumPlatformSecretStore, build_runtime_services}; | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // AppState | ||
|
|
@@ -60,8 +60,13 @@ pub struct AppState { | |
| fn build_state() -> Result<Arc<AppState>, Report<TrustedServerError>> { | ||
| let store_name = default_config_store_name(); | ||
| let config_key = default_config_key(); | ||
| let settings = | ||
| get_settings_from_config_store(&AxumPlatformConfigStore, &store_name, &config_key)?; | ||
| let settings = get_settings_from_config_store( | ||
| &AxumPlatformConfigStore, | ||
| &AxumPlatformSecretStore, | ||
| &store_name, | ||
| &config_key, | ||
| &trusted_server_core::settings_data::default_secret_store_name(), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⛏ nitpick — This reaches for a fully-qualified path while its two siblings at Apply manually — the import and the call site are in two different hunks, and the import alone would be an unused import that fails
use trusted_server_core::settings_data::{
default_config_key, default_config_store_name, default_secret_store_name,
get_settings_from_config_store,
};and this line becomes: &default_secret_store_name(), |
||
| )?; | ||
| build_state_with_settings(settings) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,7 +12,7 @@ use trusted_server_core::auction::endpoints::handle_auction; | |||||||||
| use trusted_server_core::auction::{AuctionOrchestrator, build_orchestrator}; | ||||||||||
| use trusted_server_core::cache_policy::EdgeCacheHeader; | ||||||||||
| #[cfg(target_arch = "wasm32")] | ||||||||||
| use trusted_server_core::config_payload::settings_from_config_blob; | ||||||||||
| use trusted_server_core::config_payload::{DEFAULT_SECRET_STORE_ID, settings_from_config_blob}; | ||||||||||
| use trusted_server_core::ec::EcContext; | ||||||||||
| use trusted_server_core::ec::admin::{ | ||||||||||
| admin_ec_lookup_not_supported as core_admin_ec_lookup_not_supported, | ||||||||||
|
|
@@ -22,6 +22,8 @@ use trusted_server_core::ec::registry::PartnerRegistry; | |||||||||
| use trusted_server_core::error::{IntoHttpResponse as _, TrustedServerError}; | ||||||||||
| use trusted_server_core::integrations::{IntegrationRegistry, ProxyDispatchInput}; | ||||||||||
| use trusted_server_core::platform::RuntimeServices; | ||||||||||
| #[cfg(target_arch = "wasm32")] | ||||||||||
| use trusted_server_core::platform::StoreName; | ||||||||||
| use trusted_server_core::proxy::{ | ||||||||||
| handle_first_party_click, handle_first_party_proxy, handle_first_party_proxy_rebuild, | ||||||||||
| handle_first_party_proxy_sign, | ||||||||||
|
|
@@ -44,11 +46,23 @@ use crate::platform::build_runtime_services; | |||||||||
| // --------------------------------------------------------------------------- | ||||||||||
|
|
||||||||||
| #[cfg(target_arch = "wasm32")] | ||||||||||
| static CLOUDFLARE_CONFIG_JSON: std::sync::OnceLock<String> = std::sync::OnceLock::new(); | ||||||||||
| thread_local! { | ||||||||||
| static CLOUDFLARE_CONFIG_JSON: std::cell::OnceCell<String> = const { std::cell::OnceCell::new() }; | ||||||||||
| static CLOUDFLARE_ENV: std::cell::OnceCell<worker::Env> = const { std::cell::OnceCell::new() }; | ||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 thinking — Cloudflare resolves every secret on every request, against request #1's pinned In the pinned edgezero rev, I checked and this is not a correctness bug: Aside: |
||||||||||
| } | ||||||||||
|
|
||||||||||
| #[cfg(target_arch = "wasm32")] | ||||||||||
| pub fn set_cloudflare_config_json(value: String) { | ||||||||||
| let _ = CLOUDFLARE_CONFIG_JSON.set(value); | ||||||||||
| CLOUDFLARE_CONFIG_JSON.with(|slot| { | ||||||||||
| let _ = slot.set(value); | ||||||||||
| }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| #[cfg(target_arch = "wasm32")] | ||||||||||
| pub fn set_cloudflare_env(env: worker::Env) { | ||||||||||
| CLOUDFLARE_ENV.with(|slot| { | ||||||||||
| let _ = slot.set(env); | ||||||||||
| }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// Application state built once at startup and shared across all requests. | ||||||||||
|
|
@@ -76,18 +90,22 @@ fn load_startup_settings() -> Result<Settings, Report<TrustedServerError>> { | |||||||||
|
|
||||||||||
| #[cfg(not(target_arch = "wasm32"))] | ||||||||||
| fn load_startup_settings() -> Result<Settings, Report<TrustedServerError>> { | ||||||||||
| Settings::from_toml(include_str!("../../../trusted-server.example.toml")) | ||||||||||
| Err(Report::new(TrustedServerError::Configuration { | ||||||||||
| message: "Cloudflare startup settings require a Worker config binding".to_string(), | ||||||||||
| }) | ||||||||||
| .attach("use TrustedServerApp::routes_with_settings for host tests")) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| #[cfg(target_arch = "wasm32")] | ||||||||||
| fn settings_from_cloudflare_config_json() -> Result<Settings, Report<TrustedServerError>> { | ||||||||||
| let raw_config = CLOUDFLARE_CONFIG_JSON.get().ok_or_else(|| { | ||||||||||
| let raw_config = CLOUDFLARE_CONFIG_JSON.with(|slot| slot.get().cloned()); | ||||||||||
| let raw_config = raw_config.ok_or_else(|| { | ||||||||||
| Report::new(TrustedServerError::Configuration { | ||||||||||
| message: "Cloudflare TRUSTED_SERVER_CONFIG is required".to_string(), | ||||||||||
| }) | ||||||||||
| .attach("set TRUSTED_SERVER_CONFIG to JSON containing the app_config blob envelope") | ||||||||||
| })?; | ||||||||||
| let value: serde_json::Value = serde_json::from_str(raw_config).map_err(|error| { | ||||||||||
| let value: serde_json::Value = serde_json::from_str(&raw_config).map_err(|error| { | ||||||||||
| Report::new(TrustedServerError::Configuration { | ||||||||||
| message: "invalid Cloudflare TRUSTED_SERVER_CONFIG JSON".to_string(), | ||||||||||
| }) | ||||||||||
|
|
@@ -101,7 +119,16 @@ fn settings_from_cloudflare_config_json() -> Result<Settings, Report<TrustedServ | |||||||||
| message: "Cloudflare TRUSTED_SERVER_CONFIG missing app_config".to_string(), | ||||||||||
| }) | ||||||||||
| })?; | ||||||||||
| settings_from_config_blob(envelope) | ||||||||||
| let env = CLOUDFLARE_ENV | ||||||||||
| .with(|slot| slot.get().cloned()) | ||||||||||
| .ok_or_else(|| { | ||||||||||
| Report::new(TrustedServerError::Configuration { | ||||||||||
| message: "Cloudflare Worker environment is unavailable during startup".to_string(), | ||||||||||
| }) | ||||||||||
| })?; | ||||||||||
| let secret_store = crate::platform::CloudflareSecretStoreAdapter { env }; | ||||||||||
| let default_secret_store = StoreName::from(DEFAULT_SECRET_STORE_ID); | ||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 thinking — Cloudflare is the only adapter with no logical→physical store mapping, and nothing here says whether that is deliberate. It is functionally correct:
Suggested change
|
||||||||||
| settings_from_config_blob(envelope, &secret_store, &default_secret_store) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// Build the application state from explicit settings. | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 wrench — The Axum quick-start cannot complete, because the starter config's publisher domains are reserved placeholders.
trusted-server.example.toml:57-59shipsdomain = "example.com"andcookie_domain = ".example.com", and both are in the reject lists atsettings.rs:131-132(PLACEHOLDER_DOMAINS/PLACEHOLDER_COOKIE_DOMAINS).validate_non_secret_deploy_placeholdersraisesTrustedServerError::InsecureDefault(config.rs:320-325) throughvalidate_settings_for_deploy→TrustedServerAppConfig::validate()→validate_excluding_secrets, whichrun_config_push_typedcalls. The error is keyed at the root fieldtrusted_server(config.rs:110-115), andprune_secret_leafonly descends secret paths — so it survives pruning and bothts config validateandts config pushfail.reject_placeholder_secretsre-checks the same three fields at runtime (settings.rs:3071-3079), so even a hand-forced blob would not start..env.devcurrently overrides onlyORIGIN_URL.It fails loudly rather than silently, but a new user has no way to know
example.comis reserved, and the block atgetting-started.md:73-79is presented as a complete copy-paste quick-start. This PR's own harness confirms the diagnosis atscripts/template-cache-local-test.sh:184-186, with the comment "The example publisher domains are reserved placeholders that validation rejects."localhost/localhostis the pairfixtures/configs/trusted-server.integration.toml:6-7already uses, andvalidate_cookie_domain(settings.rs:3345) rejects only;,\nand\r, so the dotless form is fine. The env overlay replaces existing scalar leaves only, and both keys exist in the template, so the override lands.