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
9 changes: 8 additions & 1 deletion crates/agentgateway/src/control/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use tower::Service;

use crate::client::{ApplicationTransport, Transport};
use crate::http::HeaderValue;
use crate::http::backendtls::{BackendTLS, PerAlpnConfig, SYSTEM_TRUST};
use crate::http::backendtls::{BackendTLS, BackendTLSInfo, PerAlpnConfig, SYSTEM_TRUST};
use crate::types::agent::Target;
use crate::*;

Expand All @@ -32,15 +32,21 @@ pub enum RootCert {

impl RootCert {
pub async fn to_client_config(&self) -> anyhow::Result<BackendTLS> {
let mut metadata = BackendTLSInfo {
alpn: Some(vec!["h2".to_string()]),
..Default::default()
};
let roots = match self {
RootCert::File(f) => {
let certfile = tokio::fs::read(f).await?;
metadata.root = Some(String::from_utf8_lossy(&certfile).into());
let certs = CertificateDer::pem_slice_iter(&certfile).collect::<Result<Vec<_>, _>>()?;
Comment on lines 41 to 43
let mut roots = rustls::RootCertStore::empty();
roots.add_parsable_certificates(certs);
roots
},
RootCert::Static(b) => {
metadata.root = Some(String::from_utf8_lossy(b).into());
let certs = CertificateDer::pem_slice_iter(b).collect::<Result<Vec<_>, _>>()?;
Comment on lines 48 to 50
let mut roots = rustls::RootCertStore::empty();
roots.add_parsable_certificates(certs);
Expand All @@ -57,6 +63,7 @@ impl RootCert {
Ok(BackendTLS {
hostname_override: None,
config: PerAlpnConfig::new(Arc::new(ccb), false),
metadata,
})
}
}
Expand Down
62 changes: 58 additions & 4 deletions crates/agentgateway/src/http/backendtls.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use std::path::PathBuf;
use std::sync::Arc;

use agent_core::strng;
use agent_core::strng::Strng;
use once_cell::sync::Lazy;
use rustls::ClientConfig;
use rustls_pki_types::pem::PemObject;
use rustls_pki_types::{CertificateDer, ServerName};
use serde::Serializer;
use tracing::trace;

use crate::transport;
use crate::serdes::schema_ser;
use crate::transport::tls;
use crate::types::agent::{parse_cert, parse_key};
use crate::{apply, transport};

pub static SYSTEM_TRUST: Lazy<BackendTLS> =
Lazy::new(|| LocalBackendTLS::default().try_into().unwrap());
Expand Down Expand Up @@ -75,6 +79,7 @@ impl PerAlpnConfig {
pub struct BackendTLS {
pub hostname_override: Option<ServerName<'static>>,
pub config: PerAlpnConfig,
pub metadata: BackendTLSInfo,
}

impl BackendTLS {
Expand Down Expand Up @@ -117,10 +122,56 @@ impl serde::Serialize for BackendTLS {
where
S: Serializer,
{
// TODO: store raw pem so we can send it back
serializer.serialize_none()
serde::Serialize::serialize(&self.metadata, serializer)
}
}

#[apply(schema_ser!)]
#[derive(Default)]
pub struct BackendTLSInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub cert: Option<Strng>,
#[serde(skip_serializing_if = "Option::is_none")]
pub root: Option<Strng>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hostname: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub insecure: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub insecure_host: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub system_roots: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub alpn: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject_alt_names: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub key_exchange_groups: Option<Vec<tls::KeyExchangeGroup>>,
}

impl BackendTLSInfo {
pub fn from_resolved(tls: &ResolvedBackendTLS) -> Self {
Self {
cert: tls.cert.as_ref().map(pem_to_string),
root: tls.root.as_ref().map(pem_to_string),
hostname: tls.hostname.clone(),
insecure: tls.insecure,
insecure_host: tls.insecure_host,
system_roots: tls.root.is_none(),
alpn: tls.alpn.clone(),
subject_alt_names: tls.subject_alt_names.clone(),
key_exchange_groups: tls.key_exchange_groups.clone(),
}
}
}

fn pem_to_string(pem: impl AsRef<[u8]>) -> Strng {
strng::new(String::from_utf8_lossy(pem.as_ref()))
}

fn is_false(value: &bool) -> bool {
!*value
}
static SYSTEM_ROOT: Lazy<rustls_native_certs::CertificateResult> =
Lazy::new(rustls_native_certs::load_native_certs);

Expand Down Expand Up @@ -162,10 +213,12 @@ pub struct ResolvedBackendTLS {

impl ResolvedBackendTLS {
pub fn try_into(self) -> anyhow::Result<BackendTLS> {
let metadata = BackendTLSInfo::from_resolved(&self);
let mut roots = rustls::RootCertStore::empty();
if let Some(root) = self.root {
let certs = CertificateDer::pem_slice_iter(&root).collect::<Result<Vec<_>, _>>()?;
roots.add_parsable_certificates(certs);
let (valid, invalid) = roots.add_parsable_certificates(certs);
trace!(valid, invalid, "added root certificates")
} else {
Comment on lines +220 to 222
// TODO: we probably should do this once globally!
for cert in &crate::http::backendtls::SYSTEM_ROOT.certs {
Expand Down Expand Up @@ -219,6 +272,7 @@ impl ResolvedBackendTLS {
Ok(BackendTLS {
hostname_override: self.hostname.map(|s| s.try_into()).transpose()?,
config: PerAlpnConfig::new(Arc::new(cc), allow_custom_alpn),
metadata,
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ backends:
pathPrefix: ~
tokenize: false
inlinePolicies:
- backendTLS: ~
- backendTLS:
systemRoots: true
- backendAuth:
key:
value: "<redacted>"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ backends:
capacity: 1
rejected: {}
inlinePolicies:
- backendTLS: ~
- backendTLS:
systemRoots: true
- requestHeaderModifier:
remove:
- x-gateway-model-name
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: crates/agentgateway/src/types/local_tests.rs
assertion_line: 137
description: "Config normalization test for mcp: YAML -> LocalConfig -> NormalizedLocalConfig -> YAML"
---
binds:
Expand Down Expand Up @@ -53,7 +52,8 @@ backends:
namespace: ""
target: "example.com:443"
inlinePolicies:
- backendTLS: ~
- backendTLS:
systemRoots: true
- backend:
mcp:
name: ns/name/bind/3000/listener0/default/route0/backend0
Expand Down
Loading