Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ of the new YAML fields below until the version that ships them.
([crates/sbproxy-observe/src/alerting/burn_rate.rs],
[e2e/tests/slo_burn_rate.rs])

- **Vault-style quote-token seed references.** `ai_crawl_control.quote_token.secret_ref`
now accepts `secret:` references resolved through `sbproxy-vault`
with the existing environment fallback, in addition to the older
`secret_ref.env` and inline `seed_hex` paths.
([crates/sbproxy-modules/src/policy/ai_crawl.rs])

- **Operator first-24-hours quickstart.** Added a concise
`docs/quickstart-operator.md` covering deploy, `/readyz`, metrics,
Grafana, logs, and rollback, linked from the README and Kubernetes
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/sbproxy-modules/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ tokio-util = { workspace = true }
# XREAD with BLOCK; the AsyncKVStore facade in sbproxy-platform only
# exposes GET/SET-shaped operations.
redis = { workspace = true }
sbproxy-vault = { version = "0.1.0", path = "../sbproxy-vault" }

[dev-dependencies]
smallvec.workspace = true
Expand Down
67 changes: 52 additions & 15 deletions crates/sbproxy-modules/src/policy/ai_crawl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,26 @@ pub struct LedgerYamlConfig {
#[derive(Debug, Clone, Deserialize)]
pub struct LedgerSecretRef {
/// Environment variable name that holds the hex-encoded HMAC key.
pub env: String,
#[serde(default)]
pub env: Option<String>,
/// Logical `secret:<name>` reference resolved through sbproxy-vault.
#[serde(default)]
pub secret: Option<String>,
}

fn resolve_secret_ref(sref: &LedgerSecretRef, context: &str) -> anyhow::Result<String> {
if let Some(env) = sref.env.as_deref() {
return std::env::var(env)
.map_err(|_| anyhow::anyhow!("{context}.secret_ref.env: env var '{env}' not set"));
}
if let Some(secret) = sref.secret.as_deref() {
let resolver = sbproxy_vault::SecretResolver::new(None, std::collections::HashMap::new())
.with_fallback(sbproxy_vault::ResolveFallback::Env);
return resolver
.resolve(&format!("secret:{secret}"))
.map_err(|e| anyhow::anyhow!("{context}.secret_ref.secret: {e}"));
}
anyhow::bail!("{context}.secret_ref requires either env or secret")
}

/// Retry-policy override for the HTTP ledger client.
Expand Down Expand Up @@ -1879,17 +1898,12 @@ fn build_multi_rail_plan(

// --- Quote-token signer ---
let seed_hex = if let Some(sref) = &qt_yaml.secret_ref {
std::env::var(&sref.env).map_err(|_| {
anyhow::anyhow!(
"ai_crawl_control.quote_token.secret_ref.env: env var '{}' not set",
sref.env
)
})?
resolve_secret_ref(sref, "ai_crawl_control.quote_token")?
} else if let Some(inline) = &qt_yaml.seed_hex {
inline.clone()
} else {
anyhow::bail!(
"ai_crawl_control.quote_token requires either secret_ref.env or seed_hex (32-byte ed25519 seed, hex-encoded)"
"ai_crawl_control.quote_token requires either secret_ref.env, secret_ref.secret, or seed_hex (32-byte ed25519 seed, hex-encoded)"
);
};
let seed_bytes = hex::decode(seed_hex.trim())
Expand Down Expand Up @@ -1949,17 +1963,12 @@ fn build_http_ledger(yaml: LedgerYamlConfig) -> anyhow::Result<HttpLedger> {
use std::time::Duration;

let key_hex = if let Some(ref sref) = yaml.secret_ref {
std::env::var(&sref.env).map_err(|_| {
anyhow::anyhow!(
"ai_crawl_control.ledger.secret_ref.env: env var '{}' not set",
sref.env
)
})?
resolve_secret_ref(sref, "ai_crawl_control.ledger")?
} else if let Some(ref inline) = yaml.key_hex {
inline.clone()
} else {
anyhow::bail!(
"ai_crawl_control.ledger requires either secret_ref.env or key_hex (hex-encoded HMAC key)"
"ai_crawl_control.ledger requires either secret_ref.env, secret_ref.secret, or key_hex (hex-encoded HMAC key)"
);
};
let key = hex::decode(key_hex.trim())
Expand Down Expand Up @@ -3435,6 +3444,34 @@ mod tests {
);
}

#[test]
fn quote_token_yaml_resolves_secret_ref_secret_via_env_fallback() {
std::env::set_var(
"SBPROXY_TEST_QUOTE_SEED",
"0001020304050607080910111213141516171819202122232425262728293031",
);
let policy = AiCrawlControlPolicy::from_config(serde_json::json!({
"price": 0.001,
"rails": {
"x402": {
"chain": "base",
"facilitator": "https://facilitator-base.x402.org",
"asset": "USDC",
"pay_to": "0xabc"
}
},
"quote_token": {
"key_id": "quote-kid",
"secret_ref": { "secret": "SBPROXY_TEST_QUOTE_SEED" }
}
}))
.expect("policy compiles with secret_ref.secret");

let jwks = policy.quote_token_jwks().expect("jwks");
assert_eq!(jwks["keys"][0]["kid"], "quote-kid");
std::env::remove_var("SBPROXY_TEST_QUOTE_SEED");
}

#[test]
fn quote_token_yaml_without_rails_is_a_config_error() {
let err = AiCrawlControlPolicy::from_config(serde_json::json!({
Expand Down
Loading