feat(gateway): Add server-side TLS support (#15052)
This commit is contained in:
@@ -14,6 +14,8 @@ pub struct RouterConfigBuilder {
|
||||
client_cert_path: Option<String>,
|
||||
client_key_path: Option<String>,
|
||||
ca_cert_paths: Vec<String>,
|
||||
server_cert_path: Option<String>,
|
||||
server_key_path: Option<String>,
|
||||
mcp_config_path: Option<String>,
|
||||
}
|
||||
|
||||
@@ -29,6 +31,8 @@ impl RouterConfigBuilder {
|
||||
client_cert_path: None,
|
||||
client_key_path: None,
|
||||
ca_cert_paths: Vec::new(),
|
||||
server_cert_path: None,
|
||||
server_key_path: None,
|
||||
mcp_config_path: None,
|
||||
}
|
||||
}
|
||||
@@ -582,6 +586,30 @@ impl RouterConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
// ==================== Server TLS ====================
|
||||
|
||||
/// Both paths must be provided together. Files read during build()
|
||||
pub fn server_cert_and_key<S1: Into<String>, S2: Into<String>>(
|
||||
mut self,
|
||||
cert_path: S1,
|
||||
key_path: S2,
|
||||
) -> Self {
|
||||
self.server_cert_path = Some(cert_path.into());
|
||||
self.server_key_path = Some(key_path.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Files read during build()
|
||||
pub fn maybe_server_cert_and_key(
|
||||
mut self,
|
||||
cert_path: Option<impl Into<String>>,
|
||||
key_path: Option<impl Into<String>>,
|
||||
) -> Self {
|
||||
self.server_cert_path = cert_path.map(|p| p.into());
|
||||
self.server_key_path = key_path.map(|p| p.into());
|
||||
self
|
||||
}
|
||||
|
||||
// ==================== MCP ====================
|
||||
|
||||
/// Config file loaded during build()
|
||||
@@ -610,6 +638,9 @@ impl RouterConfigBuilder {
|
||||
// Read mTLS certificates from paths if provided
|
||||
self = self.read_mtls_certificates()?;
|
||||
|
||||
// Read Server TLS certificates from paths if provided
|
||||
self = self.read_server_certificates()?;
|
||||
|
||||
// Read MCP config from path if provided
|
||||
self = self.read_mcp_config()?;
|
||||
|
||||
@@ -672,6 +703,33 @@ impl RouterConfigBuilder {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Internal method to read Server TLS certificates from paths
|
||||
fn read_server_certificates(mut self) -> ConfigResult<Self> {
|
||||
match (&self.server_cert_path, &self.server_key_path) {
|
||||
(Some(cert_path), Some(key_path)) => {
|
||||
let cert = std::fs::read(cert_path).map_err(|e| ConfigError::ValidationFailed {
|
||||
reason: format!(
|
||||
"Failed to read server certificate from {}: {}",
|
||||
cert_path, e
|
||||
),
|
||||
})?;
|
||||
let key = std::fs::read(key_path).map_err(|e| ConfigError::ValidationFailed {
|
||||
reason: format!("Failed to read server key from {}: {}", key_path, e),
|
||||
})?;
|
||||
self.config.server_cert = Some(cert);
|
||||
self.config.server_key = Some(key);
|
||||
}
|
||||
(None, None) => {}
|
||||
_ => {
|
||||
return Err(ConfigError::ValidationFailed {
|
||||
reason: "Both --tls-cert-path and --tls-key-path must be specified together"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Internal method to read MCP config from path
|
||||
fn read_mcp_config(mut self) -> ConfigResult<Self> {
|
||||
if let Some(mcp_config_path) = &self.mcp_config_path {
|
||||
|
||||
@@ -64,6 +64,12 @@ pub struct RouterConfig {
|
||||
pub tool_call_parser: Option<String>,
|
||||
#[serde(default)]
|
||||
pub tokenizer_cache: TokenizerCacheConfig,
|
||||
/// Server TLS certificate (PEM)
|
||||
#[serde(skip)]
|
||||
pub server_cert: Option<Vec<u8>>,
|
||||
/// Server TLS private key (PEM)
|
||||
#[serde(skip)]
|
||||
pub server_key: Option<Vec<u8>>,
|
||||
/// Combined certificate + key in PEM format, loaded from client_cert_path and client_key_path during config creation
|
||||
#[serde(skip)]
|
||||
pub client_identity: Option<Vec<u8>>,
|
||||
@@ -523,6 +529,8 @@ impl Default for RouterConfig {
|
||||
ca_certificates: vec![],
|
||||
mcp_config: None,
|
||||
enable_wasm: false,
|
||||
server_cert: None,
|
||||
server_key: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,6 +360,12 @@ struct CliArgs {
|
||||
|
||||
#[arg(long, default_value = "localhost:4317")]
|
||||
otlp_traces_endpoint: String,
|
||||
|
||||
#[arg(long)]
|
||||
tls_cert_path: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
tls_key_path: Option<String>,
|
||||
}
|
||||
|
||||
enum OracleConnectSource {
|
||||
@@ -659,7 +665,8 @@ impl CliArgs {
|
||||
.retries(!self.disable_retries)
|
||||
.circuit_breaker(!self.disable_circuit_breaker)
|
||||
.enable_wasm(self.enable_wasm)
|
||||
.igw(self.enable_igw);
|
||||
.igw(self.enable_igw)
|
||||
.maybe_server_cert_and_key(self.tls_cert_path.as_ref(), self.tls_key_path.as_ref());
|
||||
|
||||
builder.build()
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use axum::{
|
||||
routing::{delete, get, post},
|
||||
serve, Json, Router,
|
||||
};
|
||||
use rustls::crypto::ring;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
use tokio::{net::TcpListener, signal, spawn};
|
||||
@@ -983,13 +984,45 @@ pub async fn startup(config: ServerConfig) -> Result<(), Box<dyn std::error::Err
|
||||
// TcpListener::bind accepts &str and handles IPv4/IPv6 via ToSocketAddrs
|
||||
let bind_addr = format!("{}:{}", config.host, config.port);
|
||||
info!("Starting server on {}", bind_addr);
|
||||
let listener = TcpListener::bind(&bind_addr)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to bind to {}: {}", bind_addr, e))?;
|
||||
serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
||||
|
||||
if let (Some(cert), Some(key)) = (
|
||||
&config.router_config.server_cert,
|
||||
&config.router_config.server_key,
|
||||
) {
|
||||
info!("TLS enabled");
|
||||
ring::default_provider()
|
||||
.install_default()
|
||||
.map_err(|e| format!("Failed to install rustls ring provider: {e:?}"))?;
|
||||
|
||||
let tls_config = axum_server::tls_rustls::RustlsConfig::from_pem(cert.clone(), key.clone())
|
||||
.await
|
||||
.map_err(|e| format!("Failed to create TLS config: {}", e))?;
|
||||
|
||||
let addr: std::net::SocketAddr = bind_addr
|
||||
.parse()
|
||||
.map_err(|e| format!("Invalid address: {}", e))?;
|
||||
|
||||
let handle = axum_server::Handle::new();
|
||||
let handle_clone = handle.clone();
|
||||
spawn(async move {
|
||||
shutdown_signal().await;
|
||||
handle_clone.graceful_shutdown(None);
|
||||
});
|
||||
|
||||
axum_server::bind_rustls(addr, tls_config)
|
||||
.handle(handle)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
||||
} else {
|
||||
let listener = TcpListener::bind(&bind_addr)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to bind to {}: {}", bind_addr, e))?;
|
||||
serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user