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
677 changes: 227 additions & 450 deletions Cargo.lock

Large diffs are not rendered by default.

28 changes: 12 additions & 16 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ repository = "https://github.com/contextforge-org/contextforge-data-plane"
# Keep dependencies here only when at least two workspace members inherit them.
contextforge-data-plane-cpex = { path = "./crates/contextforge-data-plane-cpex" }
contextforge-data-plane-apis = { path = "./crates/contextforge-data-plane-apis"}
rmcp = { version = "3.1.2", features = [
rmcp = { version = "3.2.0", default-features = false, features = [
"server",
"client",
"auth",
"transport-streamable-http-server",
"reqwest",
"transport-streamable-http-client-reqwest",
Expand All @@ -37,29 +36,26 @@ serde = {version= "1.0"}
serde_json = "1.0"
tracing = "0.1"
tracing-opentelemetry = "0.33.0"
opentelemetry = "0.32"
opentelemetry_sdk = { version = "0.32", features = ["rt-tokio", "metrics"] }
opentelemetry = { version = "0.32", default-features = false, features = ["trace", "metrics", "internal-logs"] }
opentelemetry_sdk = { version = "0.32", default-features = false, features = ["rt-tokio", "trace", "metrics", "internal-logs"] }
tokio = { version = "1.53.1", features = ["macros", "net", "rt-multi-thread", "signal", "sync", "time"] }
http = "1.4.0"
redis = { version = "1.2.1", features = [
"default",
http = "1.5.0"
redis = { version = "1.7.0", default-features = false, features = [
"tokio-rustls-comp",
"tls-rustls",
"connection-manager"
] }
clap = { version = "4.5.60", features = ["derive", "env"] }
thiserror = "2.0.18"
clap = { version = "4.6.6", features = ["derive", "env"] }
thiserror = "2.0.20"
rmp-serde = "1.3.1"
async-trait = "0.1.89"
base64 = "0.22.1"
async-trait = "0.1.92"
base64 = "0.23.1"
reqwest = "0.13"
jsonwebtoken = { version = "11.0.0", features = ["aws_lc_rs"] }
rustls = { version = "0.23", features = ["ring"] }
rustls = "0.23"
cpex = "=0.2.2"
uuid = { version = "1.23.1", features = ["v4"] }
uuid = { version = "1.26.0", features = ["v4"] }
url = { version = "2.5", features = ["serde"] }
axum = "0.8"
openport = { version = "0.4.0", features = ["rand"] }
axum = { version = "0.8", default-features = false, features = ["http1", "matched-path", "tokio", "tower-log", "tracing"] }
cpex-secrets-detection = { path = "./crates/plugins/cpex-secrets-detection" }

[profile.release]
Expand Down
4 changes: 4 additions & 0 deletions _context/wiki/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ external-dataplane contract.

## Transport Security

Rustls uses the AWS-LC crypto provider for downstream TLS, upstream HTTPS,
Redis TLS, and OTLP TLS. JWT verification also uses AWS-LC. Keep the workspace
on this single provider to avoid compiling a second crypto implementation.

| Leg | Current posture |
| --- | --- |
| Downstream | TLS optional (`--tls-address`, no client auth — identity is the bearer token). Plain HTTP is acceptable only behind a trusted front door on a private network. |
Expand Down
2 changes: 1 addition & 1 deletion crates/contextforge-data-plane-apis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ cpex.workspace = true
serde= {workspace = true, features=["derive"]}
serde_json.workspace = true
url = { workspace = true }
schemars = { version = "1.2.1", features = ["url2", "preserve_order"] }
schemars = { version = "1.2.2", features = ["url2", "preserve_order"] }
rmcp.workspace = true

[lints]
Expand Down
3 changes: 2 additions & 1 deletion crates/contextforge-data-plane-cpex/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ repository.workspace = true
doctest = false

[dependencies]
arc-swap = "1.7"
arc-swap = "1.9"
async-trait.workspace = true
base64.workspace = true
contextforge-data-plane-apis.workspace = true
cpex.workspace = true
redis.workspace = true
rmcp.workspace = true
rmp-serde.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
tokio.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/contextforge-data-plane-cpex/src/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use cpex::cpex_core::cmf::{ContentPart, MessagePayload, Role, ToolCall, ToolResu
use rmcp::{
ErrorData,
model::{CallToolRequestParams, CallToolResult, ContentBlock},
serde::{Serialize, de::DeserializeOwned},
};
use serde::{Serialize, de::DeserializeOwned};
use serde_json::{Map, Value};
use tokio::sync::Mutex;

Expand Down
18 changes: 10 additions & 8 deletions crates/contextforge-data-plane-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ tokio.workspace = true
tokio-util = "0.7"
axum.workspace = true
axum-otel-metrics = "0.14"
tower-http = { version = "0.7.0", features = ["full"] }
tower-http = { version = "0.7.1", features = ["cors", "trace"] }
tower = "0.5.3"
http.workspace = true
futures = { version = "0.3", features = ["std", "alloc"] }
Expand All @@ -38,27 +38,29 @@ base64.workspace = true
reqwest.workspace = true
uuid.workspace = true
lru_time_cache = "0.11.11"
hyper-util = "0.1.20"
hyper = "1.4.0"
hyper-util = { version = "0.1.20", features = ["server-auto", "tokio"] }
hyper = "1.11.1"
rustls.workspace = true
rustls-pki-types = { version = "1.14.1", features = ["std", "alloc"] }
tokio-rustls = "0.26.4"
rustls-pki-types = { version = "1.15.1", features = ["std", "alloc"] }
tokio-rustls = "0.26.5"
typed-builder = "0.23.2"
url = { workspace = true, features = ["serde"] }
cel = "0.14.4"
cel = "0.14.5"



[features]
default = []
with_tools = []
with_tools = ["axum/json", "axum/query"]



[dev-dependencies]
axum = { workspace = true, features = ["json"] }
opentelemetry_sdk.workspace = true
cpex.workspace = true
test-log = "0.2.20"
rmcp = { workspace = true, features = ["macros"] }
test-log = "0.2.21"
axum-server = { version = "0.8.0", features = ["tls-rustls"] }


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ mod test {
#[test_log::test]
async fn claim_test_valid_hmac() {
CRYPTO.call_once(|| {
_ = rustls::crypto::ring::default_provider().install_default();
_ = rustls::crypto::aws_lc_rs::default_provider().install_default();
});

async fn handle(_: HeaderMap) -> Response {
Expand Down Expand Up @@ -274,7 +274,7 @@ mod test {
#[allow(clippy::items_after_statements)]
async fn claim_test_missing_scopes_is_allowed() {
CRYPTO.call_once(|| {
_ = rustls::crypto::ring::default_provider().install_default();
_ = rustls::crypto::aws_lc_rs::default_provider().install_default();
});

async fn handle(_: HeaderMap) -> Response {
Expand Down Expand Up @@ -311,7 +311,7 @@ mod test {
#[allow(clippy::items_after_statements)]
async fn claim_test_missing_token_use_and_full_name_is_allowed() {
CRYPTO.call_once(|| {
_ = rustls::crypto::ring::default_provider().install_default();
_ = rustls::crypto::aws_lc_rs::default_provider().install_default();
});
let user_id = "11111111-1111-1111-1111-111111111111".to_owned();

Expand Down Expand Up @@ -361,7 +361,7 @@ mod test {
#[allow(clippy::items_after_statements)]
async fn claim_test_expired_token() {
CRYPTO.call_once(|| {
_ = rustls::crypto::ring::default_provider().install_default();
_ = rustls::crypto::aws_lc_rs::default_provider().install_default();
});

async fn handle(_: HeaderMap) -> Response {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use rmcp::{
CallToolRequestParams, CallToolResult, ClientRequest, InitializeRequestParams, ProgressNotificationParam,
ProgressToken, Request, ServerResult,
},
serde::{Serialize, de::DeserializeOwned},
service::{NotificationContext, PeerRequestOptions, RequestHandle, ServiceError},
};
use serde::{Serialize, de::DeserializeOwned};
use tokio::sync::RwLock;
use tokio_util::sync::CancellationToken;
use tracing::{debug, warn};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async fn plaintext_lists_prefixed_backend_tools() -> Result<()> {
#[test_log::test]
#[ignore = "blocked on control-plane TLS publication and federated list-tools support"]
async fn tls_lists_prefixed_backend_tools() -> Result<()> {
let provider = crypto::ring::default_provider();
let provider = crypto::aws_lc_rs::default_provider();
_ = provider.install_default();
let fixture = start_tls_counter_gateway(TEST_USER_ID).await?;

Expand Down
17 changes: 16 additions & 1 deletion crates/contextforge-data-plane-lib/tests/gateway/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use contextforge_data_plane_lib::Result;
use rmcp::model::{CallToolRequestParams, ErrorCode};

use crate::harness::{
TEST_USER_ID, connect_modern_client, create_client, error_parts, modern_client_info, start_counter_gateway,
TEST_USER_ID, connect_modern_client, create_client, create_tls_client, error_parts, modern_client_info,
start_counter_gateway, start_tls_counter_gateway,
};

const DECREMENT_TOOL: &str = "00000000-0000-0000-0000-000000000001-decrement";
Expand All @@ -18,6 +19,20 @@ async fn plaintext_call_prefixed_backend_tools_modern_modern() -> Result<()> {
fixture.shutdown().await
}

#[tokio::test]
async fn tls_call_prefixed_backend_tools_modern_modern() -> Result<()> {
let fixture = start_tls_counter_gateway(TEST_USER_ID).await?;
let trust_bundle = std::fs::read("../../assets/contextforgeCA/contextforge.intermediate.ca-chain.cert.pem")?;
let certificates = reqwest::Certificate::from_pem_bundle(&trust_bundle)?;
let client = create_tls_client(TEST_USER_ID, certificates);
let service = connect_modern_client(&fixture.gateway_url, client, modern_client_info()).await;
let result = service.call_tool(CallToolRequestParams::new(DECREMENT_TOOL)).await?;
let text = result.content.first().and_then(|content| content.as_text()).expect("text tool result");
assert_eq!("-1", text.text);
drop(service);
fixture.shutdown().await
}

#[tokio::test]
async fn plaintext_call_invalid_backend_tools() -> Result<()> {
let fixture = start_counter_gateway(TEST_USER_ID).await?;
Expand Down
9 changes: 4 additions & 5 deletions crates/contextforge-data-plane/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ cpex-secrets-detection = { workspace = true, optional = true }
clap.workspace = true
tracing.workspace = true
tracing-opentelemetry.workspace = true
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "tracing-log"] }
tokio.workspace = true
opentelemetry.workspace = true
opentelemetry-otlp = { version = "0.32", features = ["grpc-tonic", "http-proto", "reqwest-blocking-client", "metrics","tls-aws-lc"] }
opentelemetry-otlp = { version = "0.32", default-features = false, features = ["grpc-tonic", "http-proto", "reqwest-blocking-client", "trace", "metrics", "internal-logs", "tls-aws-lc"] }
opentelemetry_sdk.workspace = true
tonic = "0.14"
tonic = { version = "0.14", default-features = false }
rmcp.workspace = true
tikv-jemallocator = "0.7.0"
rustls.workspace = true
Expand All @@ -38,11 +38,10 @@ test-plugins = ["dep:cpex-payload-marker", "dep:cpex-text-prefixer", "dep:cpex-t
with_tools = ["contextforge-data-plane-lib/with_tools"]

[dev-dependencies]
axum.workspace = true
axum = { workspace = true, features = ["json"] }
contextforge-data-plane-apis.workspace = true
http.workspace = true
jsonwebtoken.workspace = true
openport.workspace = true
redis.workspace = true
reqwest.workspace = true
rmp-serde.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/contextforge-data-plane/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use tracing::{debug, error, info};
static GLOBAL: Jemalloc = Jemalloc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let provider = crypto::ring::default_provider();
let provider = crypto::aws_lc_rs::default_provider();
_ = provider.install_default();

let config = Config::parse();
Expand Down
10 changes: 7 additions & 3 deletions crates/contextforge-data-plane/tests/secrets_detection_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use std::{
collections::HashMap,
fs,
net::TcpStream as StdTcpStream,
net::{TcpListener as StdTcpListener, TcpStream as StdTcpStream},
path::PathBuf,
process::{Child, Command, Stdio},
sync::{Arc, Mutex},
Expand Down Expand Up @@ -241,7 +241,7 @@ async fn start_environment(backend: RunningBackend, plugin_config: Value) -> E2e
write_redis_config(redis.port(), &backend).await;
write_runtime_plugin_config(redis.port(), plugin_config).await;

let gateway_port = openport::pick_random_unused_port().expect("gateway port");
let gateway_port = unused_local_port();
let mut gateway = start_gateway_process(gateway_port, redis.port());
wait_for_port(gateway_port, &mut gateway).await;

Expand All @@ -253,8 +253,12 @@ async fn start_environment(backend: RunningBackend, plugin_config: Value) -> E2e
}
}

fn unused_local_port() -> u16 {
StdTcpListener::bind(("127.0.0.1", 0)).expect("bind temporary listener").local_addr().expect("local address").port()
}

async fn start_redis() -> ChildProcess {
let port = openport::pick_random_unused_port().expect("redis port");
let port = unused_local_port();
let temp_dir = std::env::temp_dir().join(format!("contextforge-data-plane-redis-{}-{port}", std::process::id()));
fs::create_dir_all(&temp_dir).expect("redis temp dir is created");

Expand Down
2 changes: 1 addition & 1 deletion crates/plugins/cpex-secrets-detection/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ doctest = false

[dependencies]
cpex.workspace = true
regex = "1.12.3"
regex = "1.13.1"
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true

Expand Down
8 changes: 1 addition & 7 deletions deny.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
## This doesn't work at the moment. see https://github.com/EmbarkStudios/cargo-deny/issues/859
[[licenses.exceptions]]
allow = ["CDLA-Permissive-2.0"]
name = "webpki-root-certs"


[licenses]
unused-allowed-license = "allow"
confidence-threshold = 0.95
Expand Down Expand Up @@ -53,6 +47,7 @@ allow = [
"CC0-1.0",
"CDDL-1.0",
"CDDL-1.1",
"CDLA-Permissive-2.0",
"ClArtistic",
"CPL-1.0",
"curl",
Expand Down Expand Up @@ -189,7 +184,6 @@ feature-depth = 1
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
#ignore = [
# {id="RUSTSEC-2023-0071", , reason = "1. For time being not in production. 2. Workaround would be to force customers to use EC keys instead of RSA" }
#"RUSTSEC-0000-0000",
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
Expand Down