policy: Cleanup policy response labels (#6722)

Policy controller API responses include a set of labels. These labels
are to be used in proxy m$etrics to indicate why traffic is permitted to
a pod. This permits metrics to be associated with `Server` and
ServerAuthorization` resources (i.e. for `stat`).

This change updates the response API to include a `name` label
referencing the server's name. When the policy is derived from a default
configuration (and not a `Server` instance), the name takes the form
'default:<policy>'.

This change also updates authorization labels. Defaults are encoded as
servers are, otherwise the authorization's name is set as a label. The
`tls` and `authn` labels have been removed, as they're redundant with
other labels that are already present.
This commit is contained in:
Oliver Gould 2021-08-23 14:56:19 -07:00 committed by GitHub
parent 154ad9a228
commit 49f4af6e6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 135 deletions

View File

@ -23,6 +23,7 @@ pub type InboundServerStream = Pin<Box<dyn Stream<Item = InboundServer> + Send +
/// Inbound server configuration. /// Inbound server configuration.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct InboundServer { pub struct InboundServer {
pub name: String,
pub protocol: ProxyProtocol, pub protocol: ProxyProtocol,
pub authorizations: HashMap<String, ClientAuthorization>, pub authorizations: HashMap<String, ClientAuthorization>,
} }

View File

@ -10,7 +10,7 @@ use linkerd_policy_controller_core::{
ClientAuthentication, ClientAuthorization, DiscoverInboundServer, IdentityMatch, InboundServer, ClientAuthentication, ClientAuthorization, DiscoverInboundServer, IdentityMatch, InboundServer,
InboundServerStream, IpNet, NetworkMatch, ProxyProtocol, InboundServerStream, IpNet, NetworkMatch, ProxyProtocol,
}; };
use std::{collections::HashMap, sync::Arc}; use std::sync::Arc;
use tracing::trace; use tracing::trace;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -20,12 +20,6 @@ pub struct Server<T> {
cluster_networks: Arc<[IpNet]>, cluster_networks: Arc<[IpNet]>,
} }
struct Labels {
authn: bool,
tls: bool,
name: String,
}
// === impl Server === // === impl Server ===
impl<T> Server<T> impl<T> Server<T>
@ -200,9 +194,15 @@ fn to_server(srv: &InboundServer, cluster_networks: &[IpNet]) -> proto::Server {
.collect(); .collect();
trace!(?authorizations); trace!(?authorizations);
let labels = vec![("name".to_string(), srv.name.to_string())]
.into_iter()
.collect();
trace!(?labels);
proto::Server { proto::Server {
protocol: Some(protocol), protocol: Some(protocol),
authorizations, authorizations,
labels,
..Default::default() ..Default::default()
} }
} }
@ -233,104 +233,62 @@ fn to_authz(
.collect() .collect()
}; };
match authentication { let labels = vec![("name".to_string(), name.to_string())]
ClientAuthentication::Unauthenticated => { .into_iter()
let labels = Labels { .collect();
authn: false,
tls: false,
name: name.to_string(),
};
proto::Authz {
networks,
labels: labels.into(),
authentication: Some(proto::Authn {
permit: Some(proto::authn::Permit::Unauthenticated(
proto::authn::PermitUnauthenticated {},
)),
}),
}
}
ClientAuthentication::TlsUnauthenticated => { let authn = match authentication {
let labels = Labels { ClientAuthentication::Unauthenticated => proto::Authn {
authn: false, permit: Some(proto::authn::Permit::Unauthenticated(
tls: true, proto::authn::PermitUnauthenticated {},
name: name.to_string(), )),
}; },
proto::Authz {
networks, ClientAuthentication::TlsUnauthenticated => proto::Authn {
labels: labels.into(), permit: Some(proto::authn::Permit::MeshTls(proto::authn::PermitMeshTls {
authentication: Some(proto::Authn { clients: Some(proto::authn::permit_mesh_tls::Clients::Unauthenticated(
permit: Some(proto::authn::Permit::MeshTls(proto::authn::PermitMeshTls { proto::authn::PermitUnauthenticated {},
clients: Some(proto::authn::permit_mesh_tls::Clients::Unauthenticated( )),
proto::authn::PermitUnauthenticated {}, })),
)), },
})),
}),
}
}
// Authenticated connections must have TLS and apply to all // Authenticated connections must have TLS and apply to all
// networks. // networks.
ClientAuthentication::TlsAuthenticated(identities) => { ClientAuthentication::TlsAuthenticated(identities) => {
let labels = Labels { let suffixes = identities
authn: true, .iter()
tls: true, .filter_map(|i| match i {
name: name.to_string(), IdentityMatch::Suffix(s) => Some(proto::IdentitySuffix { parts: s.to_vec() }),
}; _ => None,
})
.collect();
let authn = { let identities = identities
let suffixes = identities .iter()
.iter() .filter_map(|i| match i {
.filter_map(|i| match i { IdentityMatch::Name(n) => Some(proto::Identity {
IdentityMatch::Suffix(s) => { name: n.to_string(),
Some(proto::IdentitySuffix { parts: s.to_vec() }) }),
} _ => None,
_ => None, })
}) .collect();
.collect();
let identities = identities proto::Authn {
.iter() permit: Some(proto::authn::Permit::MeshTls(proto::authn::PermitMeshTls {
.filter_map(|i| match i { clients: Some(proto::authn::permit_mesh_tls::Clients::Identities(
IdentityMatch::Name(n) => Some(proto::Identity { proto::authn::permit_mesh_tls::PermitClientIdentities {
name: n.to_string(), identities,
}), suffixes,
_ => None, },
}) )),
.collect(); })),
proto::Authn {
permit: Some(proto::authn::Permit::MeshTls(proto::authn::PermitMeshTls {
clients: Some(proto::authn::permit_mesh_tls::Clients::Identities(
proto::authn::permit_mesh_tls::PermitClientIdentities {
identities,
suffixes,
},
)),
})),
}
};
proto::Authz {
networks,
labels: labels.into(),
authentication: Some(authn),
} }
} }
} };
}
proto::Authz {
// === impl Labels === networks,
labels,
impl From<Labels> for HashMap<String, String> { authentication: Some(authn),
fn from(labels: Labels) -> HashMap<String, String> {
vec![
("authn".to_string(), labels.authn.to_string()),
("tls".to_string(), labels.tls.to_string()),
("name".to_string(), labels.name),
]
.into_iter()
.collect()
} }
} }

View File

@ -171,6 +171,7 @@ impl DefaultPolicyWatches {
} }
DefaultPolicy::Deny => InboundServer { DefaultPolicy::Deny => InboundServer {
name: "default:deny".to_string(),
protocol, protocol,
authorizations: Default::default(), authorizations: Default::default(),
}, },
@ -218,6 +219,7 @@ impl DefaultPolicyWatches {
}; };
InboundServer { InboundServer {
name: name.clone(),
protocol, protocol,
authorizations: Some((name, authz)).into_iter().collect(), authorizations: Some((name, authz)).into_iter().collect(),
} }

View File

@ -191,6 +191,7 @@ impl SrvIndex {
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
debug!(authzs = ?authzs.keys()); debug!(authzs = ?authzs.keys());
let (tx, rx) = watch::channel(InboundServer { let (tx, rx) = watch::channel(InboundServer {
name: entry.key().clone(),
protocol: protocol.clone(), protocol: protocol.clone(),
authorizations: authzs.clone(), authorizations: authzs.clone(),
}); });

View File

@ -40,15 +40,13 @@ async fn incrementally_configure_server() {
); );
idx.apply_pod(pod.clone()).unwrap(); idx.apply_pod(pod.clone()).unwrap();
let default = DefaultPolicy::Allow {
authenticated_only: false,
cluster_only: true,
};
let default_config = InboundServer { let default_config = InboundServer {
authorizations: mk_default_policy( name: format!("default:{}", default),
DefaultPolicy::Allow { authorizations: mk_default_policy(default, cluster_net, kubelet_ip),
authenticated_only: false,
cluster_only: true,
},
cluster_net,
kubelet_ip,
),
protocol: ProxyProtocol::Detect { protocol: ProxyProtocol::Detect {
timeout: detect_timeout, timeout: detect_timeout,
}, },
@ -77,6 +75,7 @@ async fn incrementally_configure_server() {
// Check that the watch has been updated to reflect the above change and that this change _only_ // Check that the watch has been updated to reflect the above change and that this change _only_
// applies to the correct port. // applies to the correct port.
let basic_config = InboundServer { let basic_config = InboundServer {
name: "srv-0".into(),
protocol: ProxyProtocol::Http1, protocol: ProxyProtocol::Http1,
authorizations: vec![healthcheck_authz(kubelet_ip)].into_iter().collect(), authorizations: vec![healthcheck_authz(kubelet_ip)].into_iter().collect(),
}; };
@ -103,6 +102,7 @@ async fn incrementally_configure_server() {
assert_eq!( assert_eq!(
time::timeout(time::Duration::from_secs(1), rx.next()).await, time::timeout(time::Duration::from_secs(1), rx.next()).await,
Ok(Some(InboundServer { Ok(Some(InboundServer {
name: "srv-0".into(),
protocol: ProxyProtocol::Http1, protocol: ProxyProtocol::Http1,
authorizations: vec![ authorizations: vec![
( (
@ -150,13 +150,14 @@ fn server_update_deselects_pod() {
(ips.next().unwrap(), ips.next().unwrap()) (ips.next().unwrap(), ips.next().unwrap())
}; };
let detect_timeout = time::Duration::from_secs(1); let detect_timeout = time::Duration::from_secs(1);
let default = DefaultPolicy::Allow {
authenticated_only: false,
cluster_only: true,
};
let (lookup_rx, mut idx) = Index::new( let (lookup_rx, mut idx) = Index::new(
vec![cluster_net], vec![cluster_net],
"cluster.example.com".into(), "cluster.example.com".into(),
DefaultPolicy::Allow { default,
authenticated_only: false,
cluster_only: true,
},
detect_timeout, detect_timeout,
); );
@ -182,6 +183,7 @@ fn server_update_deselects_pod() {
assert_eq!( assert_eq!(
port2222.get(), port2222.get(),
InboundServer { InboundServer {
name: "srv-0".into(),
protocol: ProxyProtocol::Http2, protocol: ProxyProtocol::Http2,
authorizations: vec![healthcheck_authz(kubelet_ip)].into_iter().collect(), authorizations: vec![healthcheck_authz(kubelet_ip)].into_iter().collect(),
} }
@ -195,14 +197,8 @@ fn server_update_deselects_pod() {
assert_eq!( assert_eq!(
port2222.get(), port2222.get(),
InboundServer { InboundServer {
authorizations: mk_default_policy( name: format!("default:{}", default),
DefaultPolicy::Allow { authorizations: mk_default_policy(default, cluster_net, kubelet_ip),
authenticated_only: false,
cluster_only: true,
},
cluster_net,
kubelet_ip
),
protocol: ProxyProtocol::Detect { protocol: ProxyProtocol::Detect {
timeout: detect_timeout, timeout: detect_timeout,
}, },
@ -243,6 +239,7 @@ fn default_policy_global() {
idx.reset_pods(vec![p]).unwrap(); idx.reset_pods(vec![p]).unwrap();
let config = InboundServer { let config = InboundServer {
name: format!("default:{}", default),
authorizations: mk_default_policy(*default, cluster_net, kubelet_ip), authorizations: mk_default_policy(*default, cluster_net, kubelet_ip),
protocol: ProxyProtocol::Detect { protocol: ProxyProtocol::Detect {
timeout: detect_timeout, timeout: detect_timeout,
@ -300,6 +297,7 @@ fn default_policy_annotated() {
idx.reset_pods(vec![p]).unwrap(); idx.reset_pods(vec![p]).unwrap();
let config = InboundServer { let config = InboundServer {
name: format!("default:{}", default),
authorizations: mk_default_policy(*default, cluster_net, kubelet_ip), authorizations: mk_default_policy(*default, cluster_net, kubelet_ip),
protocol: ProxyProtocol::Detect { protocol: ProxyProtocol::Detect {
timeout: detect_timeout, timeout: detect_timeout,
@ -323,13 +321,14 @@ fn default_policy_annotated_invalid() {
}; };
let detect_timeout = time::Duration::from_secs(1); let detect_timeout = time::Duration::from_secs(1);
let default = DefaultPolicy::Allow {
authenticated_only: false,
cluster_only: false,
};
let (lookup_rx, mut idx) = Index::new( let (lookup_rx, mut idx) = Index::new(
vec![cluster_net], vec![cluster_net],
"cluster.example.com".into(), "cluster.example.com".into(),
DefaultPolicy::Allow { default,
authenticated_only: false,
cluster_only: false,
},
detect_timeout, detect_timeout,
); );
@ -353,6 +352,7 @@ fn default_policy_annotated_invalid() {
assert_eq!( assert_eq!(
port2222.get(), port2222.get(),
InboundServer { InboundServer {
name: format!("default:{}", default),
authorizations: mk_default_policy( authorizations: mk_default_policy(
DefaultPolicy::Allow { DefaultPolicy::Allow {
authenticated_only: false, authenticated_only: false,
@ -400,6 +400,7 @@ fn opaque_annotated() {
idx.reset_pods(vec![p]).unwrap(); idx.reset_pods(vec![p]).unwrap();
let config = InboundServer { let config = InboundServer {
name: format!("default:{}", default),
authorizations: mk_default_policy(*default, cluster_net, kubelet_ip), authorizations: mk_default_policy(*default, cluster_net, kubelet_ip),
protocol: ProxyProtocol::Opaque, protocol: ProxyProtocol::Opaque,
}; };
@ -444,21 +445,21 @@ fn authenticated_annotated() {
); );
idx.reset_pods(vec![p]).unwrap(); idx.reset_pods(vec![p]).unwrap();
let config = InboundServer { let config = {
authorizations: mk_default_policy( let policy = match *default {
match *default { DefaultPolicy::Allow { cluster_only, .. } => DefaultPolicy::Allow {
DefaultPolicy::Allow { cluster_only, .. } => DefaultPolicy::Allow { cluster_only,
cluster_only, authenticated_only: true,
authenticated_only: true,
},
DefaultPolicy::Deny => DefaultPolicy::Deny,
}, },
cluster_net, DefaultPolicy::Deny => DefaultPolicy::Deny,
kubelet_ip, };
), InboundServer {
protocol: ProxyProtocol::Detect { name: format!("default:{}", policy),
timeout: detect_timeout, authorizations: mk_default_policy(policy, cluster_net, kubelet_ip),
}, protocol: ProxyProtocol::Detect {
timeout: detect_timeout,
},
}
}; };
let port2222 = lookup_rx let port2222 = lookup_rx