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.
 | /// Matches a client's mesh identity.
 | ||||||
| #[derive(Clone, Debug, PartialEq, Eq, Hash)] | #[derive(Clone, Debug, PartialEq, Eq, Hash)] | ||||||
| pub enum IdentityMatch { | pub enum IdentityMatch { | ||||||
|     /// An exact match.
 |     /// An exact match.
 | ||||||
|     Name(String), |     Exact(String), | ||||||
| 
 | 
 | ||||||
|     /// A suffix match.
 |     /// A suffix match.
 | ||||||
|     Suffix(Vec<String>), |     Suffix(Vec<String>), | ||||||
|  | @ -12,11 +12,29 @@ pub enum IdentityMatch { | ||||||
| 
 | 
 | ||||||
| // === impl 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 { | impl fmt::Display for IdentityMatch { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|         use std::fmt::Write; |         use std::fmt::Write; | ||||||
|         match self { |         match self { | ||||||
|             Self::Name(name) => name.fmt(f), |             Self::Exact(name) => name.fmt(f), | ||||||
|             Self::Suffix(suffix) => { |             Self::Suffix(suffix) => { | ||||||
|                 f.write_char('*')?; |                 f.write_char('*')?; | ||||||
|                 for part in suffix { |                 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 |             let identities = identities | ||||||
|                 .iter() |                 .iter() | ||||||
|                 .filter_map(|i| match i { |                 .filter_map(|i| match i { | ||||||
|                     IdentityMatch::Name(n) => Some(proto::Identity { |                     IdentityMatch::Exact(n) => Some(proto::Identity { | ||||||
|                         name: n.to_string(), |                         name: n.to_string(), | ||||||
|                     }), |                     }), | ||||||
|                     _ => None, |                     _ => None, | ||||||
|  |  | ||||||
|  | @ -124,3 +124,12 @@ impl Index { | ||||||
|         self.namespaces.index.get(ns) |         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); |         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() { |     let sas = mtls | ||||||
|         if id == "*" { |         .service_accounts | ||||||
|             debug!(suffix = %id, "Authenticated"); |         .into_iter() | ||||||
|             identities.push(IdentityMatch::Suffix(vec![])); |         .flatten() | ||||||
|         } else if id.starts_with("*.") { |         .filter_map(|sa| { | ||||||
|             debug!(suffix = %id, "Authenticated"); |             let ns = sa.namespace.as_deref().or(metadata.namespace.as_deref())?; | ||||||
|             let mut parts = id.split('.'); |             Some(IdentityMatch::Exact( | ||||||
|             let star = parts.next(); |                 cluster.service_account_identity(ns, &sa.name), | ||||||
|             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 identities = ids.chain(sas).collect::<Vec<_>>(); | ||||||
|     if identities.is_empty() { |     if identities.is_empty() { | ||||||
|         bail!("authorization authorizes no clients"); |         bail!("authorization authorizes no clients"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue