Skip to content
Closed
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
1 change: 1 addition & 0 deletions src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ pub struct TraceParent {

pub const BAGGAGE_HEADER: &str = "baggage";
pub const TRACEPARENT_HEADER: &str = "traceparent";
pub const X_ORIGIN_SOURCE_HEADER: &str = "x-istio-origin-network";

impl TraceParent {
pub fn header(&self) -> hyper::header::HeaderValue {
Expand Down
131 changes: 125 additions & 6 deletions src/proxy/outbound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ use tokio::sync::watch;
use tracing::{Instrument, debug, error, info, info_span, trace_span};

use crate::identity::Identity;
use crate::strng::Strng;

use crate::proxy::metrics::Reporter;
use crate::proxy::{
BAGGAGE_HEADER, Error, HboneAddress, ProxyInputs, TRACEPARENT_HEADER, TraceParent, util,
BAGGAGE_HEADER, Error, HboneAddress, ProxyInputs, TRACEPARENT_HEADER, TraceParent,
X_ORIGIN_SOURCE_HEADER, util,
};
use crate::proxy::{ConnectionOpen, ConnectionResult, DerivedWorkload, metrics};

Expand Down Expand Up @@ -266,7 +268,8 @@ impl OutboundConnection {
let mut sender =
super::h2::client::spawn_connection(self.pi.cfg.clone(), tls_stream, drain_rx, wl_key)
.await?;
let http_request = self.create_hbone_request(remote_addr, req);
let origin_network = &self.pi.cfg.network;
let http_request = self.create_hbone_request(remote_addr, req, Some(origin_network));
let inner_upgraded = sender.send_request(http_request).await?;

// Proxy
Expand Down Expand Up @@ -294,11 +297,12 @@ impl OutboundConnection {
}

fn create_hbone_request(
&mut self,
&self,
remote_addr: SocketAddr,
req: &Request,
origin_network: Option<&Strng>,
) -> http::Request<()> {
http::Request::builder()
let mut builder = http::Request::builder()
.uri(
req.hbone_target_destination
.as_ref()
Expand All @@ -312,7 +316,14 @@ impl OutboundConnection {
FORWARDED,
build_forwarded(remote_addr, &req.intended_destination_service),
)
.header(TRACEPARENT_HEADER, self.id.header())
.header(TRACEPARENT_HEADER, self.id.header());

// Add x-istio-origin-network header for inner CONNECT requests in double HBONE
if let Some(network) = origin_network {
builder = builder.header(X_ORIGIN_SOURCE_HEADER, network.as_str());
}

builder
.body(())
.expect("builder with known status code should not fail")
}
Expand All @@ -322,7 +333,11 @@ impl OutboundConnection {
remote_addr: SocketAddr,
req: &Request,
) -> Result<H2Stream, Error> {
let request = self.create_hbone_request(remote_addr, req);
// This is the single cluster/single-HBONE codepath (and also the outer tunnel
// for double HBONE). We don't need the x-istio-origin-network header here because:
// - For single HBONE: both source and destination are in the same network
// - For double HBONE outer: the gateway doesn't need origin network info
let request = self.create_hbone_request(remote_addr, req, None);
let pool_key = Box::new(WorkloadKey {
src_id: req.source.identity(),
// Clone here shouldn't be needed ideally, we could just take ownership of Request.
Expand Down Expand Up @@ -1876,6 +1891,110 @@ mod tests {
);
}

#[tokio::test]
async fn test_x_origin_source_header() {
initialize_telemetry();

// Create a test config with a specific network
let cfg = Arc::new(Config {
network: "test-network".into(),
local_node: Some("local-node".to_string()),
..crate::config::parse_config().unwrap()
});

// Create a source workload and add it to state
let source = XdsWorkload {
uid: "cluster1//v1/Pod/ns/source-workload".to_string(),
name: "source-workload".to_string(),
namespace: "ns".to_string(),
addresses: vec![Bytes::copy_from_slice(&[127, 0, 0, 1])],
node: "local-node".to_string(),
..Default::default()
};

let state = new_proxy_state(&[source], &[], &[]);
let sock_fact = Arc::new(crate::proxy::DefaultSocketFactory::default());

let wi = WorkloadInfo {
name: "source-workload".to_string(),
namespace: "ns".to_string(),
service_account: "default".to_string(),
};
let local_workload_information = Arc::new(LocalWorkloadInformation::new(
Arc::new(wi.clone()),
state.clone(),
identity::mock::new_secret_manager(Duration::from_secs(10)),
));

let outbound = OutboundConnection {
pi: Arc::new(ProxyInputs {
state: state.clone(),
cfg: cfg.clone(),
metrics: test_proxy_metrics(),
socket_factory: sock_fact.clone(),
local_workload_information: local_workload_information.clone(),
connection_manager: ConnectionManager::default(),
resolver: None,
disable_inbound_freebind: false,
crl_manager: None,
}),
id: TraceParent::new(),
pool: WorkloadHBONEPool::new(
cfg.clone(),
sock_fact,
local_workload_information.clone(),
),
hbone_port: cfg.inbound_addr.port(),
};

// Get the source workload from state
let source_workload = outbound
.pi
.local_workload_information
.get_workload()
.await
.unwrap();

// Create a minimal test request with required fields
let req = Request {
protocol: OutboundProtocol::HBONE,
source: source_workload,
hbone_target_destination: Some(HboneAddress::SocketAddr(
"10.0.0.1:8080".parse().unwrap(),
)),
actual_destination_workload: None,
intended_destination_service: None,
actual_destination: "10.0.0.1:8080".parse().unwrap(),
upstream_sans: vec![],
final_sans: vec![],
};

let remote_addr = "127.0.0.1:12345".parse().unwrap();

// Test the single HBONE case - header should NOT be added when origin_network is None
let http_request_no_header = outbound.create_hbone_request(remote_addr, &req, None);
assert!(
http_request_no_header
.headers()
.get(X_ORIGIN_SOURCE_HEADER)
.is_none(),
"x-istio-origin-network header should not be present when origin_network is None (single HBONE)"
);

// Test the double HBONE inner request case - header should be added when network is specified
let network = crate::strng::Strng::from("test-network");
let http_request_with_header =
outbound.create_hbone_request(remote_addr, &req, Some(&network));
assert_eq!(
http_request_with_header
.headers()
.get(X_ORIGIN_SOURCE_HEADER)
.unwrap(),
"test-network",
"x-istio-origin-network header should contain the network name for double HBONE inner request"
);
}

#[derive(PartialEq, Debug)]
struct ExpectedRequest<'a> {
protocol: OutboundProtocol,
Expand Down