mirror of https://github.com/linkerd/linkerd2.git
policy: Provide a default route for servers with no HTTPRoutes (#9036)
Currently, if no `HTTPRoute` resources reference a `Server`, the policy controller returns an empty list of routes. This results in the proxy 404ing HTTP traffic. Instead, we should return a default empty route in this case. This branch changes the policy controller to return a default route for `Server`s which are not referenced by any `HTTPRoute`s. The default route defines a single `HttpRouteRule` with no matches and no filters, so it will match any HTTP request. The default route does *not* define any authorizations, as they would potentially clobber authorizations defined by other resources that define authorization policies targeting that `Server` --- if it is not targeted by any other resource defining authorization policies, the `Server` itself will still get the default authz policy. In addition, this branch changes the various `${Resource}Ref` enums so that their `Default` variants take a `&'static str` rather than a `String`, to minimize string copying, since the default names are always pre-defined. It also adds an `InboundHttpRouteRef` type which is used as a key in the maps of `HTTPRoute`s (instead of `String`s), since we now define default routes as well. Signed-off-by: Eliza Weisman <eliza@buoyant.io> Co-authored-by: Oliver Gould <ver@buoyant.io>
This commit is contained in:
parent
18716ca206
commit
75bbbb9146
|
@ -18,7 +18,7 @@ pub struct InboundHttpRoute {
|
|||
|
||||
/// This is required for ordering returned `HttpRoute`s by their creation
|
||||
/// timestamp.
|
||||
pub creation_timestamp: DateTime<Utc>,
|
||||
pub creation_timestamp: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -102,6 +102,32 @@ pub enum QueryParamMatch {
|
|||
Regex(String, Regex),
|
||||
}
|
||||
|
||||
// === impl InboundHttpRoute ===
|
||||
|
||||
/// The default `InboundHttpRoute` used for any `InboundServer` that
|
||||
/// does not have routes.
|
||||
impl Default for InboundHttpRoute {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hostnames: vec![],
|
||||
rules: vec![InboundHttpRouteRule {
|
||||
matches: vec![HttpRouteMatch {
|
||||
path: Some(PathMatch::Prefix("/".to_string())),
|
||||
headers: vec![],
|
||||
query_params: vec![],
|
||||
method: None,
|
||||
}],
|
||||
filters: vec![],
|
||||
}],
|
||||
// Default routes do not have authorizations; the default policy's
|
||||
// authzs will be configured by the default `InboundServer`, not by
|
||||
// the route.
|
||||
authorizations: HashMap::new(),
|
||||
creation_timestamp: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === impl PathMatch ===
|
||||
|
||||
impl PartialEq for PathMatch {
|
||||
|
|
|
@ -31,22 +31,28 @@ pub struct InboundServer {
|
|||
|
||||
pub protocol: ProxyProtocol,
|
||||
pub authorizations: HashMap<AuthorizationRef, ClientAuthorization>,
|
||||
pub http_routes: HashMap<String, InboundHttpRoute>,
|
||||
pub http_routes: HashMap<InboundHttpRouteRef, InboundHttpRoute>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ServerRef {
|
||||
Default(String),
|
||||
Default(&'static str),
|
||||
Server(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum AuthorizationRef {
|
||||
Default(String),
|
||||
Default(&'static str),
|
||||
ServerAuthorization(String),
|
||||
AuthorizationPolicy(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum InboundHttpRouteRef {
|
||||
Default(&'static str),
|
||||
Linkerd(String),
|
||||
}
|
||||
|
||||
/// Describes how a proxy should handle inbound connections.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ProxyProtocol {
|
||||
|
@ -87,3 +93,24 @@ pub enum ClientAuthentication {
|
|||
/// Indicates that clients must use mutually-authenticated TLS.
|
||||
TlsAuthenticated(Vec<IdentityMatch>),
|
||||
}
|
||||
|
||||
// === impl InboundHttpRouteRef ===
|
||||
|
||||
impl Ord for InboundHttpRouteRef {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match (self, other) {
|
||||
(Self::Default(a), Self::Default(b)) => a.cmp(b),
|
||||
(Self::Linkerd(a), Self::Linkerd(b)) => a.cmp(b),
|
||||
// Route resources are always preferred over default resources, so they should sort
|
||||
// first in a list.
|
||||
(Self::Linkerd(_), Self::Default(_)) => std::cmp::Ordering::Less,
|
||||
(Self::Default(_), Self::Linkerd(_)) => std::cmp::Ordering::Greater,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for InboundHttpRouteRef {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ use linkerd2_proxy_api::{
|
|||
use linkerd_policy_controller_core::{
|
||||
http_route::{InboundFilter, InboundHttpRoute, InboundHttpRouteRule},
|
||||
AuthorizationRef, ClientAuthentication, ClientAuthorization, DiscoverInboundServer,
|
||||
IdentityMatch, InboundServer, InboundServerStream, IpNet, NetworkMatch, ProxyProtocol,
|
||||
ServerRef,
|
||||
IdentityMatch, InboundHttpRouteRef, InboundServer, InboundServerStream, IpNet, NetworkMatch,
|
||||
ProxyProtocol, ServerRef,
|
||||
};
|
||||
use maplit::*;
|
||||
use std::{num::NonZeroU16, sync::Arc};
|
||||
|
@ -211,7 +211,7 @@ fn to_server(srv: &InboundServer, cluster_networks: &[IpNet]) -> proto::Server {
|
|||
ServerRef::Default(name) => convert_args!(hashmap!(
|
||||
"group" => "",
|
||||
"kind" => "default",
|
||||
"name" => name,
|
||||
"name" => *name,
|
||||
)),
|
||||
ServerRef::Server(name) => convert_args!(hashmap!(
|
||||
"group" => "policy.linkerd.io",
|
||||
|
@ -239,12 +239,12 @@ fn to_authz(
|
|||
) -> proto::Authz {
|
||||
let meta = Metadata {
|
||||
kind: Some(match reference {
|
||||
AuthorizationRef::Default(name) => metadata::Kind::Default(name.clone()),
|
||||
AuthorizationRef::Default(name) => metadata::Kind::Default(name.to_string()),
|
||||
AuthorizationRef::AuthorizationPolicy(name) => {
|
||||
metadata::Kind::Resource(api::meta::Resource {
|
||||
group: "policy.linkerd.io".to_string(),
|
||||
kind: "authorizationpolicy".to_string(),
|
||||
name: name.clone(),
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
AuthorizationRef::ServerAuthorization(name) => {
|
||||
|
@ -263,7 +263,7 @@ fn to_authz(
|
|||
AuthorizationRef::Default(name) => convert_args!(hashmap!(
|
||||
"group" => "",
|
||||
"kind" => "default",
|
||||
"name" => name,
|
||||
"name" => *name,
|
||||
)),
|
||||
AuthorizationRef::ServerAuthorization(name) => convert_args!(hashmap!(
|
||||
"group" => "policy.linkerd.io",
|
||||
|
@ -353,7 +353,7 @@ fn to_authz(
|
|||
}
|
||||
|
||||
fn to_http_route_list<'r>(
|
||||
routes: impl IntoIterator<Item = (&'r String, &'r InboundHttpRoute)>,
|
||||
routes: impl IntoIterator<Item = (&'r InboundHttpRouteRef, &'r InboundHttpRoute)>,
|
||||
cluster_networks: &[IpNet],
|
||||
) -> Vec<proto::HttpRoute> {
|
||||
// Per the Gateway API spec:
|
||||
|
@ -369,20 +369,25 @@ fn to_http_route_list<'r>(
|
|||
// comparison, because all these routes will exist in the same
|
||||
// namespace.
|
||||
let mut route_list = routes.into_iter().collect::<Vec<_>>();
|
||||
route_list.sort_by(|(a_name, a), (b_name, b)| {
|
||||
a.creation_timestamp
|
||||
.cmp(&b.creation_timestamp)
|
||||
.then_with(|| a_name.cmp(b_name))
|
||||
route_list.sort_by(|(a_ref, a), (b_ref, b)| {
|
||||
let by_ts = match (&a.creation_timestamp, &b.creation_timestamp) {
|
||||
(Some(a_ts), Some(b_ts)) => a_ts.cmp(b_ts),
|
||||
(None, None) => std::cmp::Ordering::Equal,
|
||||
// Routes with timestamps are preferred over routes without.
|
||||
(Some(_), None) => return std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => return std::cmp::Ordering::Greater,
|
||||
};
|
||||
by_ts.then_with(|| a_ref.cmp(b_ref))
|
||||
});
|
||||
|
||||
route_list
|
||||
.into_iter()
|
||||
.map(|(name, route)| to_http_route(name, route.clone(), cluster_networks))
|
||||
.map(|(route_ref, route)| to_http_route(route_ref, route.clone(), cluster_networks))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn to_http_route(
|
||||
name: impl ToString,
|
||||
name: &InboundHttpRouteRef,
|
||||
InboundHttpRoute {
|
||||
hostnames,
|
||||
rules,
|
||||
|
@ -392,11 +397,14 @@ fn to_http_route(
|
|||
cluster_networks: &[IpNet],
|
||||
) -> proto::HttpRoute {
|
||||
let metadata = Metadata {
|
||||
kind: Some(metadata::Kind::Resource(api::meta::Resource {
|
||||
group: "policy.linkerd.io".to_string(),
|
||||
kind: "HTTPRoute".to_string(),
|
||||
name: name.to_string(),
|
||||
})),
|
||||
kind: Some(match name {
|
||||
InboundHttpRouteRef::Default(name) => metadata::Kind::Default(name.to_string()),
|
||||
InboundHttpRouteRef::Linkerd(name) => metadata::Kind::Resource(api::meta::Resource {
|
||||
group: "policy.linkerd.io".to_string(),
|
||||
kind: "HTTPRoute".to_string(),
|
||||
name: name.to_string(),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
let hosts = hostnames
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
use ahash::AHashMap as HashMap;
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use linkerd_policy_controller_core::{
|
||||
AuthorizationRef, ClientAuthentication, ClientAuthorization, IdentityMatch, IpNet,
|
||||
};
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::ClusterInfo;
|
||||
|
||||
/// Indicates the default behavior to apply when no Server is found for a port.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum DefaultPolicy {
|
||||
|
@ -45,28 +51,68 @@ impl std::str::FromStr for DefaultPolicy {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DefaultPolicy {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl DefaultPolicy {
|
||||
pub(crate) fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Allow {
|
||||
authenticated_only: true,
|
||||
cluster_only: false,
|
||||
} => "all-authenticated".fmt(f),
|
||||
} => "all-authenticated",
|
||||
Self::Allow {
|
||||
authenticated_only: false,
|
||||
cluster_only: false,
|
||||
} => "all-unauthenticated".fmt(f),
|
||||
} => "all-unauthenticated",
|
||||
Self::Allow {
|
||||
authenticated_only: true,
|
||||
cluster_only: true,
|
||||
} => "cluster-authenticated".fmt(f),
|
||||
} => "cluster-authenticated",
|
||||
Self::Allow {
|
||||
authenticated_only: false,
|
||||
cluster_only: true,
|
||||
} => "cluster-unauthenticated".fmt(f),
|
||||
Self::Deny => "deny".fmt(f),
|
||||
} => "cluster-unauthenticated",
|
||||
Self::Deny => "deny",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_authzs(
|
||||
self,
|
||||
config: &ClusterInfo,
|
||||
) -> HashMap<AuthorizationRef, ClientAuthorization> {
|
||||
let mut authzs = HashMap::default();
|
||||
if let DefaultPolicy::Allow {
|
||||
authenticated_only,
|
||||
cluster_only,
|
||||
} = self
|
||||
{
|
||||
let authentication = if authenticated_only {
|
||||
ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Suffix(vec![])])
|
||||
} else {
|
||||
ClientAuthentication::Unauthenticated
|
||||
};
|
||||
let networks = if cluster_only {
|
||||
config.networks.iter().copied().map(Into::into).collect()
|
||||
} else {
|
||||
vec![
|
||||
"0.0.0.0/0".parse::<IpNet>().unwrap().into(),
|
||||
"::/0".parse::<IpNet>().unwrap().into(),
|
||||
]
|
||||
};
|
||||
authzs.insert(
|
||||
AuthorizationRef::Default(self.as_str()),
|
||||
ClientAuthorization {
|
||||
authentication,
|
||||
networks,
|
||||
},
|
||||
);
|
||||
};
|
||||
authzs
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DefaultPolicy {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.as_str().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -39,16 +39,7 @@ impl TryFrom<api::HttpRoute> for InboundRouteBinding {
|
|||
|
||||
fn try_from(route: api::HttpRoute) -> Result<Self, Self::Error> {
|
||||
let route_ns = route.metadata.namespace.as_deref();
|
||||
let creation_timestamp = route
|
||||
.metadata
|
||||
.creation_timestamp
|
||||
.map(|k8s::Time(t)| t)
|
||||
.unwrap_or_else(|| {
|
||||
tracing::warn!("HTTPRoute resource did not have a creation timestamp!");
|
||||
// If the resource is missing a creation timestamp, we'll use the current time so
|
||||
// that existing resources have precedence over newly added ones.
|
||||
std::time::SystemTime::now().into()
|
||||
});
|
||||
let creation_timestamp = route.metadata.creation_timestamp.map(|k8s::Time(t)| t);
|
||||
let parents = InboundParentRef::collect_from(route_ns, route.spec.inner.parent_refs)?;
|
||||
let hostnames = route
|
||||
.spec
|
||||
|
@ -89,16 +80,7 @@ impl TryFrom<policy::HttpRoute> for InboundRouteBinding {
|
|||
|
||||
fn try_from(route: policy::HttpRoute) -> Result<Self, Self::Error> {
|
||||
let route_ns = route.metadata.namespace.as_deref();
|
||||
let creation_timestamp = route
|
||||
.metadata
|
||||
.creation_timestamp
|
||||
.map(|k8s::Time(t)| t)
|
||||
.unwrap_or_else(|| {
|
||||
tracing::warn!("HTTPRoute resource did not have a creation timestamp!");
|
||||
// If the resource is missing a creation timestamp, we'll use the current time so
|
||||
// that existing resources have precedence over newly added ones.
|
||||
std::time::SystemTime::now().into()
|
||||
});
|
||||
let creation_timestamp = route.metadata.creation_timestamp.map(|k8s::Time(t)| t);
|
||||
let parents = InboundParentRef::collect_from(route_ns, route.spec.inner.parent_refs)?;
|
||||
let hostnames = route
|
||||
.spec
|
||||
|
|
|
@ -18,7 +18,7 @@ use ahash::{AHashMap as HashMap, AHashSet as HashSet};
|
|||
use anyhow::{anyhow, bail, Result};
|
||||
use linkerd_policy_controller_core::{
|
||||
AuthorizationRef, ClientAuthentication, ClientAuthorization, IdentityMatch, InboundHttpRoute,
|
||||
InboundServer, IpNet, Ipv4Net, Ipv6Net, NetworkMatch, ProxyProtocol, ServerRef,
|
||||
InboundHttpRouteRef, InboundServer, Ipv4Net, Ipv6Net, NetworkMatch, ProxyProtocol, ServerRef,
|
||||
};
|
||||
use linkerd_policy_controller_k8s_api::{self as k8s, policy::server::Port, ResourceExt};
|
||||
use parking_lot::RwLock;
|
||||
|
@ -1042,39 +1042,20 @@ impl Pod {
|
|||
}
|
||||
}
|
||||
|
||||
let mut authorizations = HashMap::default();
|
||||
if let DefaultPolicy::Allow {
|
||||
authenticated_only,
|
||||
cluster_only,
|
||||
} = policy
|
||||
{
|
||||
let authentication = if authenticated_only {
|
||||
ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Suffix(vec![])])
|
||||
} else {
|
||||
ClientAuthentication::Unauthenticated
|
||||
};
|
||||
let networks = if cluster_only {
|
||||
config.networks.iter().copied().map(Into::into).collect()
|
||||
} else {
|
||||
vec![
|
||||
"0.0.0.0/0".parse::<IpNet>().unwrap().into(),
|
||||
"::/0".parse::<IpNet>().unwrap().into(),
|
||||
]
|
||||
};
|
||||
authorizations.insert(
|
||||
AuthorizationRef::Default(policy.to_string()),
|
||||
ClientAuthorization {
|
||||
authentication,
|
||||
networks,
|
||||
},
|
||||
);
|
||||
};
|
||||
let authorizations = policy.default_authzs(config);
|
||||
|
||||
let http_routes = Some((
|
||||
InboundHttpRouteRef::Default("default"),
|
||||
InboundHttpRoute::default(),
|
||||
))
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
InboundServer {
|
||||
reference: ServerRef::Default(policy.to_string()),
|
||||
reference: ServerRef::Default(policy.as_str()),
|
||||
protocol,
|
||||
authorizations,
|
||||
http_routes: HashMap::default(),
|
||||
http_routes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1282,16 +1263,29 @@ impl PolicyIndex {
|
|||
&self,
|
||||
server_name: &str,
|
||||
authentications: &AuthenticationNsIndex,
|
||||
) -> HashMap<String, InboundHttpRoute> {
|
||||
self.http_routes
|
||||
) -> HashMap<InboundHttpRouteRef, InboundHttpRoute> {
|
||||
let mut routes = self
|
||||
.http_routes
|
||||
.iter()
|
||||
.filter(|(_, route)| route.selects_server(server_name))
|
||||
.map(|(name, route)| {
|
||||
let mut route = route.route.clone();
|
||||
route.authorizations = self.route_client_authzs(name, authentications);
|
||||
(name.clone(), route)
|
||||
(InboundHttpRouteRef::Linkerd(name.clone()), route)
|
||||
})
|
||||
.collect()
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
if routes.is_empty() {
|
||||
// If no routes are defined for the server, use a default route that
|
||||
// matches all requests. Default authorizations are instrumented on
|
||||
// the server.
|
||||
routes.insert(
|
||||
InboundHttpRouteRef::Default("default"),
|
||||
InboundHttpRoute::default(),
|
||||
);
|
||||
}
|
||||
|
||||
routes
|
||||
}
|
||||
|
||||
fn policy_client_authz(
|
||||
|
|
|
@ -7,8 +7,9 @@ use crate::{defaults::DefaultPolicy, index::*, server_authorization::ServerSelec
|
|||
use ahash::AHashMap as HashMap;
|
||||
use kubert::index::IndexNamespacedResource;
|
||||
use linkerd_policy_controller_core::{
|
||||
AuthorizationRef, ClientAuthentication, ClientAuthorization, IdentityMatch, InboundServer,
|
||||
IpNet, Ipv4Net, Ipv6Net, NetworkMatch, ProxyProtocol, ServerRef,
|
||||
AuthorizationRef, ClientAuthentication, ClientAuthorization, IdentityMatch, InboundHttpRoute,
|
||||
InboundHttpRouteRef, InboundServer, IpNet, Ipv4Net, Ipv6Net, NetworkMatch, ProxyProtocol,
|
||||
ServerRef,
|
||||
};
|
||||
use linkerd_policy_controller_k8s_api::{
|
||||
self as k8s,
|
||||
|
@ -126,7 +127,7 @@ fn mk_default_policy(
|
|||
authenticated_only: true,
|
||||
cluster_only: false,
|
||||
} => Some((
|
||||
AuthorizationRef::Default("all-authenticated".to_string()),
|
||||
AuthorizationRef::Default("all-authenticated"),
|
||||
ClientAuthorization {
|
||||
authentication: authed,
|
||||
networks: all_nets,
|
||||
|
@ -136,7 +137,7 @@ fn mk_default_policy(
|
|||
authenticated_only: false,
|
||||
cluster_only: false,
|
||||
} => Some((
|
||||
AuthorizationRef::Default("all-unauthenticated".to_string()),
|
||||
AuthorizationRef::Default("all-unauthenticated"),
|
||||
ClientAuthorization {
|
||||
authentication: ClientAuthentication::Unauthenticated,
|
||||
networks: all_nets,
|
||||
|
@ -146,7 +147,7 @@ fn mk_default_policy(
|
|||
authenticated_only: true,
|
||||
cluster_only: true,
|
||||
} => Some((
|
||||
AuthorizationRef::Default("cluster-authenticated".to_string()),
|
||||
AuthorizationRef::Default("cluster-authenticated"),
|
||||
ClientAuthorization {
|
||||
authentication: authed,
|
||||
networks: cluster_nets,
|
||||
|
@ -156,7 +157,7 @@ fn mk_default_policy(
|
|||
authenticated_only: false,
|
||||
cluster_only: true,
|
||||
} => Some((
|
||||
AuthorizationRef::Default("cluster-unauthenticated".to_string()),
|
||||
AuthorizationRef::Default("cluster-unauthenticated"),
|
||||
ClientAuthorization {
|
||||
authentication: ClientAuthentication::Unauthenticated,
|
||||
networks: cluster_nets,
|
||||
|
@ -167,6 +168,15 @@ fn mk_default_policy(
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn mk_default_routes() -> HashMap<InboundHttpRouteRef, InboundHttpRoute> {
|
||||
Some((
|
||||
InboundHttpRouteRef::Default("default"),
|
||||
InboundHttpRoute::default(),
|
||||
))
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl TestConfig {
|
||||
fn from_default_policy(default_policy: DefaultPolicy) -> Self {
|
||||
let _tracing = Self::init_tracing();
|
||||
|
@ -191,12 +201,12 @@ impl TestConfig {
|
|||
|
||||
fn default_server(&self) -> InboundServer {
|
||||
InboundServer {
|
||||
reference: ServerRef::Default(self.default_policy.to_string()),
|
||||
reference: ServerRef::Default(self.default_policy.as_str()),
|
||||
authorizations: mk_default_policy(self.default_policy, self.cluster.networks.clone()),
|
||||
protocol: ProxyProtocol::Detect {
|
||||
timeout: self.detect_timeout,
|
||||
},
|
||||
http_routes: HashMap::default(),
|
||||
http_routes: mk_default_routes(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ fn default_policy_annotated() {
|
|||
.expect("pod-0.ns-0 should exist");
|
||||
assert_eq!(
|
||||
rx.borrow_and_update().reference,
|
||||
ServerRef::Default(test.default_policy.to_string()),
|
||||
ServerRef::Default(test.default_policy.as_str()),
|
||||
);
|
||||
|
||||
// Update the annotation on the pod and check that the watch is updated
|
||||
|
@ -42,10 +42,7 @@ fn default_policy_annotated() {
|
|||
);
|
||||
test.index.write().apply(pod);
|
||||
assert!(rx.has_changed().unwrap());
|
||||
assert_eq!(
|
||||
rx.borrow().reference,
|
||||
ServerRef::Default(default.to_string())
|
||||
);
|
||||
assert_eq!(rx.borrow().reference, ServerRef::Default(default.as_str()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,12 +111,12 @@ fn authenticated_annotated() {
|
|||
DefaultPolicy::Deny => DefaultPolicy::Deny,
|
||||
};
|
||||
InboundServer {
|
||||
reference: ServerRef::Default(policy.to_string()),
|
||||
reference: ServerRef::Default(policy.as_str()),
|
||||
authorizations: mk_default_policy(policy, test.cluster.networks),
|
||||
protocol: ProxyProtocol::Detect {
|
||||
timeout: test.detect_timeout,
|
||||
},
|
||||
http_routes: HashMap::default(),
|
||||
http_routes: mk_default_routes(),
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ fn links_authorization_policy_with_mtls_name() {
|
|||
reference: ServerRef::Server("srv-8080".to_string()),
|
||||
authorizations: Default::default(),
|
||||
protocol: ProxyProtocol::Http1,
|
||||
http_routes: HashMap::default(),
|
||||
http_routes: mk_default_routes(),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -85,7 +85,7 @@ fn links_authorization_policy_with_mtls_name() {
|
|||
.into_iter()
|
||||
.collect(),
|
||||
protocol: ProxyProtocol::Http1,
|
||||
http_routes: HashMap::default(),
|
||||
http_routes: mk_default_routes(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ fn authorization_targets_namespace() {
|
|||
reference: ServerRef::Server("srv-8080".to_string()),
|
||||
authorizations: Default::default(),
|
||||
protocol: ProxyProtocol::Http1,
|
||||
http_routes: HashMap::default(),
|
||||
http_routes: mk_default_routes(),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -175,7 +175,7 @@ fn authorization_targets_namespace() {
|
|||
.into_iter()
|
||||
.collect(),
|
||||
protocol: ProxyProtocol::Http1,
|
||||
http_routes: HashMap::default(),
|
||||
http_routes: mk_default_routes(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ fn links_authorization_policy_with_service_account() {
|
|||
reference: ServerRef::Server("srv-8080".to_string()),
|
||||
authorizations: Default::default(),
|
||||
protocol: ProxyProtocol::Http1,
|
||||
http_routes: HashMap::default(),
|
||||
http_routes: mk_default_routes(),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -259,7 +259,7 @@ fn links_authorization_policy_with_service_account() {
|
|||
.into_iter()
|
||||
.collect(),
|
||||
protocol: ProxyProtocol::Http1,
|
||||
http_routes: HashMap::default(),
|
||||
http_routes: mk_default_routes(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use super::*;
|
||||
use linkerd_policy_controller_core::InboundHttpRouteRef;
|
||||
|
||||
const POLICY_API_GROUP: &str = "policy.linkerd.io";
|
||||
|
||||
|
@ -34,7 +35,7 @@ fn route_attaches_to_server() {
|
|||
reference: ServerRef::Server("srv-8080".to_string()),
|
||||
authorizations: Default::default(),
|
||||
protocol: ProxyProtocol::Http1,
|
||||
http_routes: HashMap::default(),
|
||||
http_routes: mk_default_routes(),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -47,7 +48,10 @@ fn route_attaches_to_server() {
|
|||
rx.borrow().reference,
|
||||
ServerRef::Server("srv-8080".to_string())
|
||||
);
|
||||
assert!(rx.borrow_and_update().http_routes.contains_key("route-foo"));
|
||||
assert!(rx
|
||||
.borrow_and_update()
|
||||
.http_routes
|
||||
.contains_key(&InboundHttpRouteRef::Linkerd("route-foo".to_string())));
|
||||
|
||||
// Create authz policy.
|
||||
test.index.write().apply(mk_authorization_policy(
|
||||
|
@ -63,11 +67,13 @@ fn route_attaches_to_server() {
|
|||
));
|
||||
|
||||
assert!(rx.has_changed().unwrap());
|
||||
assert!(rx.borrow().http_routes["route-foo"]
|
||||
.authorizations
|
||||
.contains_key(&AuthorizationRef::AuthorizationPolicy(
|
||||
"authz-foo".to_string()
|
||||
)));
|
||||
assert!(
|
||||
rx.borrow().http_routes[&InboundHttpRouteRef::Linkerd("route-foo".to_string())]
|
||||
.authorizations
|
||||
.contains_key(&AuthorizationRef::AuthorizationPolicy(
|
||||
"authz-foo".to_string()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
fn mk_route(
|
||||
|
|
|
@ -43,7 +43,7 @@ fn link_server_authz(selector: ServerSelector) {
|
|||
reference: ServerRef::Server("srv-8080".to_string()),
|
||||
authorizations: Default::default(),
|
||||
protocol: ProxyProtocol::Http1,
|
||||
http_routes: HashMap::default(),
|
||||
http_routes: mk_default_routes(),
|
||||
},
|
||||
);
|
||||
test.index.write().apply(mk_server_authz(
|
||||
|
|
|
@ -11,6 +11,14 @@ use tokio::io;
|
|||
|
||||
#[macro_export]
|
||||
macro_rules! assert_is_default_all_unauthenticated {
|
||||
($config:expr) => {
|
||||
assert_default_all_unauthenticated_labels!($config);
|
||||
assert_eq!($config.authorizations.len(), 1);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_default_all_unauthenticated_labels {
|
||||
($config:expr) => {
|
||||
assert_eq!(
|
||||
$config.labels,
|
||||
|
@ -22,7 +30,6 @@ macro_rules! assert_is_default_all_unauthenticated {
|
|||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
assert_eq!($config.authorizations.len(), 1);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -37,7 +44,7 @@ macro_rules! assert_protocol_detect {
|
|||
kind: Some(inbound::proxy_protocol::Kind::Detect(
|
||||
inbound::proxy_protocol::Detect {
|
||||
timeout: Some(time::Duration::from_secs(10).into()),
|
||||
http_routes: Default::default(),
|
||||
http_routes: vec![$crate::grpc::defaults::http_route()],
|
||||
}
|
||||
)),
|
||||
}),
|
||||
|
@ -173,3 +180,38 @@ impl hyper::service::Service<hyper::Request<tonic::body::BoxBody>> for GrpcHttp
|
|||
self.tx.call(hyper::Request::from_parts(parts, body))
|
||||
}
|
||||
}
|
||||
|
||||
pub mod defaults {
|
||||
use super::*;
|
||||
|
||||
pub fn proxy_protocol() -> inbound::ProxyProtocol {
|
||||
use inbound::proxy_protocol::{Http1, Kind};
|
||||
inbound::ProxyProtocol {
|
||||
kind: Some(Kind::Http1(Http1 {
|
||||
routes: vec![http_route()],
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn http_route() -> inbound::HttpRoute {
|
||||
use http_route::{path_match, HttpRouteMatch, PathMatch};
|
||||
use inbound::{http_route::Rule, HttpRoute};
|
||||
use meta::{metadata, Metadata};
|
||||
|
||||
HttpRoute {
|
||||
metadata: Some(Metadata {
|
||||
kind: Some(metadata::Kind::Default("default".to_owned())),
|
||||
}),
|
||||
rules: vec![Rule {
|
||||
matches: vec![HttpRouteMatch {
|
||||
path: Some(PathMatch {
|
||||
kind: Some(path_match::Kind::Prefix("/".to_owned())),
|
||||
}),
|
||||
..HttpRouteMatch::default()
|
||||
}],
|
||||
..Rule::default()
|
||||
}],
|
||||
..HttpRoute::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ use kube::ResourceExt;
|
|||
use linkerd_policy_controller_core::{Ipv4Net, Ipv6Net};
|
||||
use linkerd_policy_controller_k8s_api as k8s;
|
||||
use linkerd_policy_test::{
|
||||
assert_is_default_all_unauthenticated, assert_protocol_detect, create, create_ready_pod, grpc,
|
||||
with_temp_ns,
|
||||
assert_default_all_unauthenticated_labels, assert_is_default_all_unauthenticated,
|
||||
assert_protocol_detect, create, create_ready_pod, grpc, with_temp_ns,
|
||||
};
|
||||
use maplit::{btreemap, convert_args, hashmap};
|
||||
use tokio::time;
|
||||
|
@ -34,14 +34,7 @@ async fn server_with_server_authorization() {
|
|||
// that the update now uses this server, which has no authorizations
|
||||
let server = create(&client, mk_admin_server(&ns, "linkerd-admin")).await;
|
||||
let config = next_config(&mut rx).await;
|
||||
assert_eq!(
|
||||
config.protocol,
|
||||
Some(grpc::inbound::ProxyProtocol {
|
||||
kind: Some(grpc::inbound::proxy_protocol::Kind::Http1(
|
||||
grpc::inbound::proxy_protocol::Http1::default()
|
||||
)),
|
||||
}),
|
||||
);
|
||||
assert_eq!(config.protocol, Some(grpc::defaults::proxy_protocol()));
|
||||
assert_eq!(config.authorizations, vec![]);
|
||||
assert_eq!(
|
||||
config.labels,
|
||||
|
@ -82,14 +75,7 @@ async fn server_with_server_authorization() {
|
|||
.expect("watch must not fail")
|
||||
.expect("watch must return an updated config");
|
||||
tracing::trace!(?config);
|
||||
assert_eq!(
|
||||
config.protocol,
|
||||
Some(grpc::inbound::ProxyProtocol {
|
||||
kind: Some(grpc::inbound::proxy_protocol::Kind::Http1(
|
||||
grpc::inbound::proxy_protocol::Http1::default()
|
||||
)),
|
||||
}),
|
||||
);
|
||||
assert_eq!(config.protocol, Some(grpc::defaults::proxy_protocol()));
|
||||
assert_eq!(
|
||||
config.authorizations.first().unwrap().labels,
|
||||
convert_args!(hashmap!(
|
||||
|
@ -160,14 +146,7 @@ async fn server_with_authorization_policy() {
|
|||
// that the update now uses this server, which has no authorizations
|
||||
let server = create(&client, mk_admin_server(&ns, "linkerd-admin")).await;
|
||||
let config = next_config(&mut rx).await;
|
||||
assert_eq!(
|
||||
config.protocol,
|
||||
Some(grpc::inbound::ProxyProtocol {
|
||||
kind: Some(grpc::inbound::proxy_protocol::Kind::Http1(
|
||||
grpc::inbound::proxy_protocol::Http1::default()
|
||||
)),
|
||||
}),
|
||||
);
|
||||
assert_eq!(config.protocol, Some(grpc::defaults::proxy_protocol()));
|
||||
assert_eq!(config.authorizations, vec![]);
|
||||
assert_eq!(
|
||||
config.labels,
|
||||
|
@ -223,14 +202,8 @@ async fn server_with_authorization_policy() {
|
|||
let config = time::timeout(time::Duration::from_secs(10), next_config(&mut rx))
|
||||
.await
|
||||
.expect("watch must update within 10s");
|
||||
assert_eq!(
|
||||
config.protocol,
|
||||
Some(grpc::inbound::ProxyProtocol {
|
||||
kind: Some(grpc::inbound::proxy_protocol::Kind::Http1(
|
||||
grpc::inbound::proxy_protocol::Http1::default()
|
||||
)),
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(config.protocol, Some(grpc::defaults::proxy_protocol()));
|
||||
assert_eq!(config.authorizations.len(), 1);
|
||||
assert_eq!(
|
||||
config.authorizations.first().unwrap().labels,
|
||||
|
@ -288,14 +261,7 @@ async fn server_with_http_route() {
|
|||
// and no routes.
|
||||
let _server = create(&client, mk_admin_server(&ns, "linkerd-admin")).await;
|
||||
let config = next_config(&mut rx).await;
|
||||
assert_eq!(
|
||||
config.protocol,
|
||||
Some(grpc::inbound::ProxyProtocol {
|
||||
kind: Some(grpc::inbound::proxy_protocol::Kind::Http1(
|
||||
grpc::inbound::proxy_protocol::Http1::default()
|
||||
)),
|
||||
}),
|
||||
);
|
||||
assert_eq!(config.protocol, Some(grpc::defaults::proxy_protocol()));
|
||||
assert_eq!(config.authorizations, vec![]);
|
||||
assert_eq!(
|
||||
config.labels,
|
||||
|
@ -409,14 +375,7 @@ async fn server_with_http_route() {
|
|||
.await
|
||||
.expect("HttpRoute must be deleted");
|
||||
let config = next_config(&mut rx).await;
|
||||
assert_eq!(
|
||||
config.protocol,
|
||||
Some(grpc::inbound::ProxyProtocol {
|
||||
kind: Some(grpc::inbound::proxy_protocol::Kind::Http1(
|
||||
grpc::inbound::proxy_protocol::Http1::default()
|
||||
)),
|
||||
}),
|
||||
);
|
||||
assert_eq!(config.protocol, Some(grpc::defaults::proxy_protocol()));
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
@ -461,14 +420,7 @@ async fn http_routes_ordered_by_creation() {
|
|||
// and no routes.
|
||||
let _server = create(&client, mk_admin_server(&ns, "linkerd-admin")).await;
|
||||
let config = next_config(&mut rx).await;
|
||||
assert_eq!(
|
||||
config.protocol,
|
||||
Some(grpc::inbound::ProxyProtocol {
|
||||
kind: Some(grpc::inbound::proxy_protocol::Kind::Http1(
|
||||
grpc::inbound::proxy_protocol::Http1::default()
|
||||
)),
|
||||
}),
|
||||
);
|
||||
assert_eq!(config.protocol, Some(grpc::defaults::proxy_protocol()));
|
||||
assert_eq!(config.authorizations, vec![]);
|
||||
assert_eq!(
|
||||
config.labels,
|
||||
|
@ -540,6 +492,31 @@ async fn http_routes_ordered_by_creation() {
|
|||
.await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn default_http_routes() {
|
||||
with_temp_ns(|client, ns| async move {
|
||||
// Create a pod that does nothing. It's injected with a proxy, so we can
|
||||
// attach policies to its admin server.
|
||||
let pod = create_ready_pod(&client, mk_pause(&ns, "pause")).await;
|
||||
|
||||
let mut rx = retry_watch_server(&client, &ns, &pod.name_unchecked()).await;
|
||||
let config = rx
|
||||
.next()
|
||||
.await
|
||||
.expect("watch must not fail")
|
||||
.expect("watch must return an initial config");
|
||||
tracing::trace!(?config);
|
||||
assert_is_default_all_unauthenticated!(config);
|
||||
assert_protocol_detect!(config);
|
||||
|
||||
let routes = detect_routes(&config);
|
||||
assert_eq!(routes.len(), 1);
|
||||
let route_authzs = &routes[0].authorizations;
|
||||
assert_eq!(route_authzs.len(), 0);
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns an `HttpRoute` resource in the provdied namespace and with the
|
||||
/// provided name, which attaches to the `linkerd-admin` `Server` resource and
|
||||
/// matches `GET` requests with the path `/metrics`.
|
||||
|
@ -686,18 +663,34 @@ async fn next_config(rx: &mut tonic::Streaming<grpc::inbound::Server>) -> grpc::
|
|||
config
|
||||
}
|
||||
|
||||
fn http1_routes(config: &grpc::inbound::Server) -> &[grpc::inbound::HttpRoute] {
|
||||
let http1 = if let grpc::inbound::proxy_protocol::Kind::Http1(ref http1) = config
|
||||
fn detect_routes(config: &grpc::inbound::Server) -> &[grpc::inbound::HttpRoute] {
|
||||
let kind = config
|
||||
.protocol
|
||||
.as_ref()
|
||||
.expect("must have proxy protocol")
|
||||
.kind
|
||||
.as_ref()
|
||||
.expect("must have kind")
|
||||
{
|
||||
.expect("must have kind");
|
||||
let detect = if let grpc::inbound::proxy_protocol::Kind::Detect(ref detect) = kind {
|
||||
detect
|
||||
} else {
|
||||
panic!("proxy protocol must be Detect; actually got:\n{kind:#?}")
|
||||
};
|
||||
&detect.http_routes[..]
|
||||
}
|
||||
|
||||
fn http1_routes(config: &grpc::inbound::Server) -> &[grpc::inbound::HttpRoute] {
|
||||
let kind = config
|
||||
.protocol
|
||||
.as_ref()
|
||||
.expect("must have proxy protocol")
|
||||
.kind
|
||||
.as_ref()
|
||||
.expect("must have kind");
|
||||
let http1 = if let grpc::inbound::proxy_protocol::Kind::Http1(ref http1) = kind {
|
||||
http1
|
||||
} else {
|
||||
panic!("proxy protocol must be HTTP1")
|
||||
panic!("proxy protocol must be HTTP1; actually got:\n{kind:#?}")
|
||||
};
|
||||
&http1.routes[..]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue