Skip to content

Commit 7e5207e

Browse files
authored
Expose policy labels on inbound transport metrics (#1215)
The inbound server only permits connections that are allowed by a policy, but this decision is not observable at runtime. This change modifies the server transport labels to include server and authorization labels--prefixed by `srv_` and `saz_`, respectively (matching the shortnames of the `Server` and `ServerAuthorization` resources). Furthermore, this change updates the `inbound_tcp_acept_errors_total` metric to include an `unauthorized` error variant to expose a counter of connections that were not permitted.
1 parent e9eaedc commit 7e5207e

File tree

18 files changed

+284
-130
lines changed

18 files changed

+284
-130
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ dependencies = [
690690
"linkerd-proxy-transport",
691691
"linkerd-reconnect",
692692
"linkerd-retry",
693+
"linkerd-server-policy",
693694
"linkerd-service-profiles",
694695
"linkerd-stack",
695696
"linkerd-stack-metrics",

linkerd/app/admin/src/stack.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,13 @@ impl Config {
157157

158158
impl Param<transport::labels::Key> for Tcp {
159159
fn param(&self) -> transport::labels::Key {
160-
transport::labels::Key::Accept {
161-
direction: transport::labels::Direction::In,
162-
tls: self.tls.clone(),
163-
target_addr: self.addr.into(),
164-
}
160+
transport::labels::Key::inbound_server(
161+
self.tls.clone(),
162+
self.addr.into(),
163+
// TODO(ver) enforce policies on the proxy's admin port.
164+
Default::default(),
165+
Default::default(),
166+
)
165167
}
166168
}
167169

linkerd/app/core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ linkerd-proxy-tcp = { path = "../../proxy/tcp" }
5050
linkerd-proxy-transport = { path = "../../proxy/transport" }
5151
linkerd-reconnect = { path = "../../reconnect" }
5252
linkerd-retry = { path = "../../retry" }
53+
linkerd-server-policy = { path = "../../server-policy" }
5354
linkerd-timeout = { path = "../../timeout" }
5455
linkerd-tracing = { path = "../../tracing" }
5556
linkerd-transport-header = { path = "../../transport-header" }

linkerd/app/core/src/metrics/tcp_accept_errors.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
metrics::{self, Counter, FmtMetrics},
33
svc,
4-
transport::{labels, OrigDstAddr},
4+
transport::{labels, DeniedUnauthorized, DeniedUnknownPort, OrigDstAddr},
55
};
66
use linkerd_error::Error;
77
use linkerd_error_metrics::{FmtLabels, LabelError, RecordError};
@@ -36,6 +36,7 @@ pub struct LabelAcceptErrors(());
3636
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
3737
pub enum AcceptErrors {
3838
TlsDetectTimeout,
39+
Unauthorized,
3940
Io,
4041
Other,
4142
}
@@ -94,6 +95,10 @@ impl LabelError<Error> for LabelAcceptErrors {
9495
} else if err.is::<std::io::Error>() {
9596
// We ignore the error code because we want all labels to be consistent.
9697
return AcceptErrors::Io;
98+
} else if err.is::<DeniedUnknownPort>() || err.is::<DeniedUnauthorized>() {
99+
// If the port is unknown, the default policy is `deny`; so handle it as
100+
// unauthorized.
101+
return AcceptErrors::Unauthorized;
97102
}
98103
curr = err.source();
99104
}
@@ -110,6 +115,7 @@ impl FmtLabels for AcceptErrors {
110115
Self::TlsDetectTimeout => fmt::Display::fmt("error=\"tls_detect_timeout\"", f),
111116
Self::Io => fmt::Display::fmt("error=\"io\"", f),
112117
Self::Other => fmt::Display::fmt("error=\"other\"", f),
118+
Self::Unauthorized => fmt::Display::fmt("error=\"unauthorized\"", f),
113119
}
114120
}
115121
}

linkerd/app/core/src/transport/labels.rs

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub use crate::metrics::{Direction, OutboundEndpointLabels};
22
use linkerd_conditional::Conditional;
33
use linkerd_metrics::FmtLabels;
4+
use linkerd_server_policy as policy;
45
use linkerd_tls as tls;
56
use std::{fmt, net::SocketAddr};
67

@@ -11,13 +12,17 @@ use std::{fmt, net::SocketAddr};
1112
/// Implements `FmtLabels`.
1213
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
1314
pub enum Key {
14-
Accept {
15-
direction: Direction,
16-
tls: tls::ConditionalServerTls,
17-
target_addr: SocketAddr,
18-
},
19-
OutboundConnect(OutboundEndpointLabels),
20-
InboundConnect,
15+
Server(ServerLabels),
16+
OutboundClient(OutboundEndpointLabels),
17+
InboundClient,
18+
}
19+
20+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
21+
pub struct ServerLabels {
22+
direction: Direction,
23+
tls: tls::ConditionalServerTls,
24+
target_addr: SocketAddr,
25+
policy: Option<PolicyLabels>,
2126
}
2227

2328
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
@@ -29,40 +34,45 @@ pub(crate) struct TlsConnect<'t>(&'t tls::ConditionalClientTls);
2934
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
3035
pub(crate) struct TargetAddr(pub(crate) SocketAddr);
3136

37+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
38+
pub(crate) struct PolicyLabels {
39+
server: policy::Labels,
40+
authz: policy::Labels,
41+
}
42+
3243
// === impl Key ===
3344

3445
impl Key {
35-
pub fn accept(
36-
direction: Direction,
46+
pub fn inbound_server(
3747
tls: tls::ConditionalServerTls,
3848
target_addr: SocketAddr,
49+
server: policy::Labels,
50+
authz: policy::Labels,
3951
) -> Self {
40-
Self::Accept {
41-
direction,
52+
Self::Server(ServerLabels::inbound(
4253
tls,
4354
target_addr,
44-
}
55+
PolicyLabels { server, authz },
56+
))
57+
}
58+
59+
pub fn outbound_server(target_addr: SocketAddr) -> Self {
60+
Self::Server(ServerLabels::outbound(target_addr))
4561
}
4662
}
4763

4864
impl FmtLabels for Key {
4965
fn fmt_labels(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5066
match self {
51-
Self::Accept {
52-
direction,
53-
tls,
54-
target_addr,
55-
} => {
56-
direction.fmt_labels(f)?;
57-
f.write_str(",peer=\"src\",")?;
58-
(TargetAddr(*target_addr), TlsAccept::from(tls)).fmt_labels(f)
59-
}
60-
Self::OutboundConnect(endpoint) => {
67+
Self::Server(l) => l.fmt_labels(f),
68+
69+
Self::OutboundClient(endpoint) => {
6170
Direction::Out.fmt_labels(f)?;
6271
write!(f, ",peer=\"dst\",")?;
6372
endpoint.fmt_labels(f)
6473
}
65-
Self::InboundConnect => {
74+
75+
Self::InboundClient => {
6676
const NO_TLS: tls::client::ConditionalClientTls =
6777
Conditional::None(tls::NoClientTls::Loopback);
6878

@@ -74,6 +84,49 @@ impl FmtLabels for Key {
7484
}
7585
}
7686

87+
impl ServerLabels {
88+
fn inbound(
89+
tls: tls::ConditionalServerTls,
90+
target_addr: SocketAddr,
91+
policy: PolicyLabels,
92+
) -> Self {
93+
ServerLabels {
94+
direction: Direction::In,
95+
tls,
96+
target_addr,
97+
policy: Some(policy),
98+
}
99+
}
100+
101+
fn outbound(target_addr: SocketAddr) -> Self {
102+
ServerLabels {
103+
direction: Direction::Out,
104+
tls: tls::ConditionalServerTls::None(tls::NoServerTls::Loopback),
105+
target_addr,
106+
policy: None,
107+
}
108+
}
109+
}
110+
111+
impl FmtLabels for ServerLabels {
112+
fn fmt_labels(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113+
self.direction.fmt_labels(f)?;
114+
f.write_str(",peer=\"src\",")?;
115+
(TargetAddr(self.target_addr), TlsAccept(&self.tls)).fmt_labels(f)?;
116+
117+
if let Some(policy) = self.policy.as_ref() {
118+
for (k, v) in policy.server.iter() {
119+
write!(f, ",srv_{}=\"{}\"", k, v)?;
120+
}
121+
for (k, v) in policy.authz.iter() {
122+
write!(f, ",saz_{}=\"{}\"", k, v)?;
123+
}
124+
}
125+
126+
Ok(())
127+
}
128+
}
129+
77130
// === impl TlsAccept ===
78131

79132
impl<'t> From<&'t tls::ConditionalServerTls> for TlsAccept<'t> {
@@ -133,3 +186,38 @@ impl FmtLabels for TargetAddr {
133186
write!(f, "target_addr=\"{}\"", self.0)
134187
}
135188
}
189+
190+
#[cfg(test)]
191+
mod tests {
192+
pub use super::*;
193+
194+
impl std::fmt::Display for ServerLabels {
195+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196+
self.fmt_labels(f)
197+
}
198+
}
199+
200+
#[test]
201+
fn server_labels() {
202+
let labels = ServerLabels::inbound(
203+
tls::ConditionalServerTls::Some(tls::ServerTls::Established {
204+
client_id: Some("foo.id.example.com".parse().unwrap()),
205+
negotiated_protocol: None,
206+
}),
207+
([192, 0, 2, 4], 40000).into(),
208+
PolicyLabels {
209+
server: vec![("name".to_string(), "testserver".to_string())]
210+
.into_iter()
211+
.collect(),
212+
authz: vec![("name".to_string(), "testauthz".to_string())]
213+
.into_iter()
214+
.collect(),
215+
},
216+
);
217+
assert_eq!(
218+
labels.to_string(),
219+
"direction=\"inbound\",peer=\"src\",target_addr=\"192.0.2.4:40000\",tls=\"true\",\
220+
client_id=\"foo.id.example.com\",srv_name=\"testserver\",saz_name=\"testauthz\""
221+
);
222+
}
223+
}

linkerd/app/core/src/transport/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,25 @@ pub use linkerd_proxy_transport::*;
22
use linkerd_stack::{ExtractParam, Param};
33
pub use linkerd_transport_metrics as metrics;
44
use std::sync::Arc;
5+
use thiserror::Error;
56

67
pub mod labels;
78

89
#[derive(Clone, Debug)]
910
pub struct Metrics(metrics::Registry<labels::Key>);
1011

12+
#[derive(Clone, Debug, Error)]
13+
#[error("connection denied on unknown port {0}")]
14+
pub struct DeniedUnknownPort(pub u16);
15+
16+
#[derive(Debug, Error)]
17+
#[error("unauthorized connection from {client_addr} with identity {tls:?} to {dst_addr}")]
18+
pub struct DeniedUnauthorized {
19+
pub client_addr: Remote<ClientAddr>,
20+
pub dst_addr: OrigDstAddr,
21+
pub tls: linkerd_tls::ConditionalServerTls,
22+
}
23+
1124
impl From<metrics::Registry<labels::Key>> for Metrics {
1225
fn from(reg: metrics::Registry<labels::Key>) -> Self {
1326
Self(reg)

linkerd/app/gateway/src/lib.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use linkerd_app_core::{
2121
Error, Infallible, NameAddr, NameMatch,
2222
};
2323
use linkerd_app_inbound::{
24-
direct::{ClientInfo, GatewayConnection, GatewayTransportHeader},
24+
direct::{ClientInfo, GatewayConnection, GatewayTransportHeader, Legacy},
2525
Inbound,
2626
};
2727
use linkerd_app_outbound::{self as outbound, Outbound};
@@ -252,6 +252,7 @@ where
252252
target,
253253
protocol,
254254
client,
255+
..
255256
}| match protocol {
256257
Some(proto) => Ok(svc::Either::A(HttpTransportHeader {
257258
target,
@@ -307,14 +308,17 @@ impl Param<tls::ClientId> for HttpTransportHeader {
307308

308309
// === impl HttpLegacy ===
309310

310-
impl<E: Into<Error>> TryFrom<(Result<Option<http::Version>, E>, ClientInfo)> for HttpLegacy {
311+
impl<E: Into<Error>> TryFrom<(Result<Option<http::Version>, E>, Legacy)> for HttpLegacy {
311312
type Error = Error;
312313

313314
fn try_from(
314-
(version, client): (Result<Option<http::Version>, E>, ClientInfo),
315+
(version, gateway): (Result<Option<http::Version>, E>, Legacy),
315316
) -> Result<Self, Self::Error> {
316317
match version {
317-
Ok(Some(version)) => Ok(Self { version, client }),
318+
Ok(Some(version)) => Ok(Self {
319+
version,
320+
client: gateway.client,
321+
}),
318322
Ok(None) => Err(RefusedNoTarget(()).into()),
319323
Err(e) => Err(e.into()),
320324
}

0 commit comments

Comments
 (0)