Skip to content

Commit b09f97c

Browse files
authored
inbound: Use policy protocol configurations (#1203)
Per-port server policies include protocol hinting that is not currently honored. This change modifies the inbound detect stacks to only perform HTTP detection when the server policy indicates that detection should be pefromed. It also honors the per-port detect timeout configuration. When a port is documented as HTTP/1, HTTP/2, or gRPC, protocol detection is skipped and the configuration is used instead. When the port's protocol is documented to be application TLS, we check that authorization policies apply but we do not explicitly enforce that application TLS was present, since we may _additionally_ apply mesh TLS in these situations.
1 parent a7373cf commit b09f97c

File tree

1 file changed

+97
-20
lines changed

1 file changed

+97
-20
lines changed

linkerd/app/inbound/src/detect.rs

Lines changed: 97 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ use linkerd_app_core::{
1111
addrs::{ClientAddr, OrigDstAddr, Remote},
1212
ServerAddr,
1313
},
14-
Error,
14+
Error, Infallible,
1515
};
16-
use std::fmt::Debug;
16+
use linkerd_server_policy::Protocol;
17+
use std::{fmt::Debug, time};
1718

1819
#[derive(Clone, Debug, PartialEq, Eq)]
1920
pub struct Tls {
@@ -22,6 +23,15 @@ pub struct Tls {
2223
permit: Permitted,
2324
}
2425

26+
#[derive(Clone, Debug)]
27+
struct Detect {
28+
timeout: time::Duration,
29+
tls: Tls,
30+
}
31+
32+
#[derive(Copy, Clone, Debug)]
33+
struct ConfigureHttpDetect;
34+
2535
#[derive(Clone, Debug, PartialEq, Eq)]
2636
pub struct Http {
2737
tls: Tls,
@@ -63,22 +73,34 @@ impl<N> Inbound<N> {
6373
const TLS_PORT_SKIPPED: tls::ConditionalServerTls =
6474
tls::ConditionalServerTls::None(tls::NoServerTls::PortSkipped);
6575

66-
self.map_stack(|cfg, rt, tls| {
76+
self.map_stack(|cfg, rt, detect| {
6777
let detect_timeout = cfg.proxy.detect_protocol_timeout;
68-
tls.check_new_service::<Tls, tls::server::Io<I>>()
69-
.push_request_filter(
70-
|(tls, t): (tls::ConditionalServerTls, T)| -> Result<Tls, DeniedUnauthorized> {
78+
detect
79+
.push_switch(
80+
// Ensure that the connection is authorized before proceeding with protocol
81+
// detection.
82+
|(tls, t): (tls::ConditionalServerTls, T)| -> Result<_, Error> {
7183
let policy: AllowPolicy = t.param();
7284
let permit = policy.check_authorized(tls)?;
73-
Ok(Tls::from_params(&t, permit))
85+
86+
// If the port is configured to support application TLS, it may have also
87+
// been wrapped in mesh identity. In any case, we don't actually validate
88+
// whether app TLS was employed, but we use this as a signal that we should
89+
// not perform additional protocol detection.
90+
if let Protocol::Tls = permit.protocol {
91+
return Ok(svc::Either::B(Tls::from_params(&t, permit)));
92+
}
93+
94+
Ok(svc::Either::A(Tls::from_params(&t, permit)))
7495
},
96+
svc::stack(forward.clone())
97+
.push_on_response(svc::MapTargetLayer::new(io::BoxedIo::new))
98+
.into_inner(),
7599
)
76-
.check_new_service::<(tls::ConditionalServerTls, T), tls::server::Io<I>>()
77100
.push(tls::NewDetectTls::layer(TlsParams {
78101
timeout: tls::server::Timeout(detect_timeout),
79102
identity: rt.identity.clone(),
80103
}))
81-
.check_new_service::<T, I>()
82104
.push_switch(
83105
// If this port's policy indicates that authentication is not required and
84106
// detection should be skipped, use the TCP stack directly.
@@ -94,7 +116,6 @@ impl<N> Inbound<N> {
94116
.push_on_response(svc::MapTargetLayer::new(io::BoxedIo::new))
95117
.into_inner(),
96118
)
97-
.check_new_service::<T, I>()
98119
.push_on_response(svc::BoxService::layer())
99120
.push(svc::BoxNewService::layer())
100121
})
@@ -122,17 +143,36 @@ impl<N> Inbound<N> {
122143
FSvc::Error: Into<Error>,
123144
FSvc::Future: Send,
124145
{
125-
self.map_stack(|cfg, rt, http| {
126-
http.push_map_target(|(http, tls)| Http { http, tls })
127-
.push(svc::UnwrapOr::layer(
128-
// When HTTP detection fails, forward the connection to the application as
129-
// an opaque TCP stream.
130-
forward.clone(),
131-
))
146+
self.map_stack(|_, rt, http| {
147+
http.clone()
132148
.push_on_response(svc::MapTargetLayer::new(io::BoxedIo::new))
133-
.push(svc::BoxNewService::layer())
134-
.push_map_target(detect::allow_timeout)
135-
.push(detect::NewDetectService::layer(cfg.proxy.detect_http()))
149+
.push_switch(
150+
// If we have a protocol hint, skip detection and just used the hinted HTTP
151+
// version.
152+
|tls: Tls| -> Result<_, Infallible> {
153+
let http = match tls.permit.protocol {
154+
Protocol::Detect { timeout } => {
155+
return Ok(svc::Either::B(Detect { timeout, tls }));
156+
}
157+
Protocol::Http1 => http::Version::Http1,
158+
Protocol::Http2 | Protocol::Grpc => http::Version::H2,
159+
_ => unreachable!("opaque protocols must not hit the HTTP stack"),
160+
};
161+
Ok(svc::Either::A(Http { http, tls }))
162+
},
163+
http.push_map_target(|(http, Detect { tls, .. })| Http { http, tls })
164+
.push(svc::UnwrapOr::layer(
165+
// When HTTP detection fails, forward the connection to the application as
166+
// an opaque TCP stream.
167+
svc::stack(forward.clone())
168+
.push_map_target(|Detect { tls, .. }| tls)
169+
.into_inner(),
170+
))
171+
.push_on_response(svc::MapTargetLayer::new(io::BoxedIo::new))
172+
.push(svc::BoxNewService::layer())
173+
.push_map_target(detect::allow_timeout)
174+
.push(detect::NewDetectService::layer(ConfigureHttpDetect)),
175+
)
136176
.push(rt.metrics.transport.layer_accept())
137177
.push_on_response(svc::BoxService::layer())
138178
.push(svc::BoxNewService::layer())
@@ -171,6 +211,14 @@ impl svc::Param<transport::labels::Key> for Tls {
171211
}
172212
}
173213

214+
// === impl ConfigureHttpDetect ===
215+
216+
impl svc::ExtractParam<detect::Config<http::DetectHttp>, Detect> for ConfigureHttpDetect {
217+
fn extract_param(&self, detect: &Detect) -> detect::Config<http::DetectHttp> {
218+
detect::Config::from_timeout(detect.timeout)
219+
}
220+
}
221+
174222
// === impl Http ===
175223

176224
impl svc::Param<http::Version> for Http {
@@ -355,6 +403,35 @@ mod tests {
355403
.expect("should succeed");
356404
}
357405

406+
#[tokio::test(flavor = "current_thread")]
407+
async fn hinted_http() {
408+
let _trace = trace::test::trace_init();
409+
410+
let target = Tls {
411+
client_addr: client_addr(),
412+
orig_dst_addr: orig_dst_addr(),
413+
permit: Permitted {
414+
protocol: Protocol::Http1,
415+
labels: None.into_iter().collect(),
416+
tls: tls::ConditionalServerTls::Some(tls::ServerTls::Established {
417+
client_id: Some(client_id()),
418+
negotiated_protocol: None,
419+
}),
420+
},
421+
};
422+
423+
let (ior, _) = io::duplex(100);
424+
425+
inbound()
426+
.with_stack(new_ok())
427+
.push_detect_http(new_panic("tcp stack must not be used"))
428+
.into_inner()
429+
.new_service(target)
430+
.oneshot(ior)
431+
.await
432+
.expect("should succeed");
433+
}
434+
358435
fn client_id() -> tls::ClientId {
359436
"testsa.testns.serviceaccount.identity.linkerd.cluster.local"
360437
.parse()

0 commit comments

Comments
 (0)