mirror of https://github.com/linkerd/linkerd2.git
policy: Test identity-parsing logic (#8103)
The identity-string parsing logic is currently implemented directly in the ServerAuthorization indexer. In preparation of this being needed in additional modules, this change extracts identity parsing and adds some basic tests. Signed-off-by: Oliver Gould <ver@buoyant.io>
This commit is contained in:
parent
0860e1c685
commit
e962bf857c
|
|
@ -1,10 +1,10 @@
|
|||
use std::fmt;
|
||||
use std::{convert::Infallible, fmt, str::FromStr};
|
||||
|
||||
/// Matches a client's mesh identity.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum IdentityMatch {
|
||||
/// An exact match.
|
||||
Name(String),
|
||||
Exact(String),
|
||||
|
||||
/// A suffix match.
|
||||
Suffix(Vec<String>),
|
||||
|
|
@ -12,11 +12,29 @@ pub enum IdentityMatch {
|
|||
|
||||
// === impl IdentityMatch ===
|
||||
|
||||
impl FromStr for IdentityMatch {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Infallible> {
|
||||
if s == "*" {
|
||||
return Ok(IdentityMatch::Suffix(vec![]));
|
||||
}
|
||||
|
||||
if s.starts_with("*.") {
|
||||
return Ok(IdentityMatch::Suffix(
|
||||
s.split('.').skip(1).map(|s| s.to_string()).collect(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(IdentityMatch::Exact(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IdentityMatch {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use std::fmt::Write;
|
||||
match self {
|
||||
Self::Name(name) => name.fmt(f),
|
||||
Self::Exact(name) => name.fmt(f),
|
||||
Self::Suffix(suffix) => {
|
||||
f.write_char('*')?;
|
||||
for part in suffix {
|
||||
|
|
@ -27,3 +45,43 @@ impl fmt::Display for IdentityMatch {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_star() {
|
||||
assert_eq!("*".parse(), Ok(IdentityMatch::Suffix(vec![])));
|
||||
|
||||
assert_eq!(
|
||||
"*.example.com".parse(),
|
||||
Ok(IdentityMatch::Suffix(vec![
|
||||
"example".to_string(),
|
||||
"com".to_string()
|
||||
]))
|
||||
);
|
||||
assert_eq!(
|
||||
"*.*.example.com".parse(),
|
||||
Ok(IdentityMatch::Suffix(vec![
|
||||
"*".to_string(),
|
||||
"example".to_string(),
|
||||
"com".to_string()
|
||||
]))
|
||||
);
|
||||
assert_eq!(
|
||||
"x.example.com".parse(),
|
||||
Ok(IdentityMatch::Exact("x.example.com".to_string()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"**.example.com".parse(),
|
||||
Ok(IdentityMatch::Exact("**.example.com".to_string()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"foo.*.example.com".parse(),
|
||||
Ok(IdentityMatch::Exact("foo.*.example.com".to_string()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ fn to_authz(
|
|||
let identities = identities
|
||||
.iter()
|
||||
.filter_map(|i| match i {
|
||||
IdentityMatch::Name(n) => Some(proto::Identity {
|
||||
IdentityMatch::Exact(n) => Some(proto::Identity {
|
||||
name: n.to_string(),
|
||||
}),
|
||||
_ => None,
|
||||
|
|
|
|||
|
|
@ -124,3 +124,12 @@ impl Index {
|
|||
self.namespaces.index.get(ns)
|
||||
}
|
||||
}
|
||||
|
||||
impl ClusterInfo {
|
||||
fn service_account_identity(&self, ns: &str, sa: &str) -> String {
|
||||
format!(
|
||||
"{}.{}.serviceaccount.identity.{}.{}",
|
||||
sa, ns, self.control_plane_ns, self.identity_domain
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,39 +235,27 @@ fn mk_mtls_authn(
|
|||
return Ok(ClientAuthentication::TlsUnauthenticated);
|
||||
}
|
||||
|
||||
let mut identities = Vec::new();
|
||||
let ids = mtls
|
||||
.identities
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|id| match id.parse() {
|
||||
Ok(id) => id,
|
||||
Err(e) => match e {},
|
||||
});
|
||||
|
||||
for id in mtls.identities.into_iter().flatten() {
|
||||
if id == "*" {
|
||||
debug!(suffix = %id, "Authenticated");
|
||||
identities.push(IdentityMatch::Suffix(vec![]));
|
||||
} else if id.starts_with("*.") {
|
||||
debug!(suffix = %id, "Authenticated");
|
||||
let mut parts = id.split('.');
|
||||
let star = parts.next();
|
||||
debug_assert_eq!(star, Some("*"));
|
||||
identities.push(IdentityMatch::Suffix(
|
||||
parts.map(|p| p.to_string()).collect::<Vec<_>>(),
|
||||
));
|
||||
} else {
|
||||
debug!(%id, "Authenticated");
|
||||
identities.push(IdentityMatch::Name(id));
|
||||
}
|
||||
}
|
||||
|
||||
for sa in mtls.service_accounts.into_iter().flatten() {
|
||||
let name = sa.name;
|
||||
let ns = sa
|
||||
.namespace
|
||||
.unwrap_or_else(|| metadata.namespace.clone().unwrap());
|
||||
debug!(ns = %ns, serviceaccount = %name, "Authenticated");
|
||||
let n = format!(
|
||||
"{}.{}.serviceaccount.identity.{}.{}",
|
||||
name, ns, cluster.control_plane_ns, cluster.identity_domain
|
||||
);
|
||||
identities.push(IdentityMatch::Name(n));
|
||||
}
|
||||
let sas = mtls
|
||||
.service_accounts
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|sa| {
|
||||
let ns = sa.namespace.as_deref().or(metadata.namespace.as_deref())?;
|
||||
Some(IdentityMatch::Exact(
|
||||
cluster.service_account_identity(ns, &sa.name),
|
||||
))
|
||||
});
|
||||
|
||||
let identities = ids.chain(sas).collect::<Vec<_>>();
|
||||
if identities.is_empty() {
|
||||
bail!("authorization authorizes no clients");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue