From 492afce08df12a2f27bcfd075a943947efea4469 Mon Sep 17 00:00:00 2001 From: John Howard Date: Wed, 18 Dec 2024 16:21:28 -0800 Subject: [PATCH] authorization: implement new serviceAccounts field --- benches/throughput.rs | 2 ++ proto/authorization.proto | 8 ++++++ src/admin.rs | 9 ++++++ src/rbac.rs | 58 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+) diff --git a/benches/throughput.rs b/benches/throughput.rs index 3d35f6ddc..d2ab7470f 100644 --- a/benches/throughput.rs +++ b/benches/throughput.rs @@ -70,6 +70,8 @@ fn create_test_policies() -> Vec { StringMatch::Exact("random-exac-2bc13".into()), ], not_namespaces: vec![], + service_accounts: vec![], + not_service_accounts: vec![], principals: vec![ StringMatch::Prefix("random-prefix-2b123".into()), StringMatch::Suffix("random-postix-2b723".into()), diff --git a/proto/authorization.proto b/proto/authorization.proto index ea6a399ed..94e9767f5 100644 --- a/proto/authorization.proto +++ b/proto/authorization.proto @@ -57,6 +57,9 @@ message Match { repeated StringMatch namespaces = 1; repeated StringMatch not_namespaces = 2; + repeated ServiceAccountMatch service_accounts = 11; + repeated ServiceAccountMatch not_service_accounts = 12; + repeated StringMatch principals = 3; repeated StringMatch not_principals = 4; @@ -75,6 +78,11 @@ message Address { uint32 length = 2; } +message ServiceAccountMatch { + string namespace = 1; + string serviceAccount = 2; +} + message StringMatch { oneof match_type { // exact string match diff --git a/src/admin.rs b/src/admin.rs index bb8bab212..69fe5c658 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -457,6 +457,7 @@ mod tests { use crate::xds::istio::security::Clause as XdsClause; use crate::xds::istio::security::Match as XdsMatch; use crate::xds::istio::security::Rule as XdsRule; + use crate::xds::istio::security::ServiceAccountMatch as XdsServiceAccountMatch; use crate::xds::istio::security::StringMatch as XdsStringMatch; use crate::xds::istio::workload::gateway_address::Destination as XdsDestination; use crate::xds::istio::workload::GatewayAddress as XdsGatewayAddress; @@ -714,6 +715,14 @@ mod tests { not_namespaces: vec![XdsStringMatch { match_type: Some(XdsMatchType::Exact("not-ns".to_string())), }], + service_accounts: vec![XdsServiceAccountMatch { + namespace: "ns".into(), + service_account: "sa".into(), + }], + not_service_accounts: vec![XdsServiceAccountMatch { + namespace: "ns".into(), + service_account: "sa".into(), + }], principals: vec![XdsStringMatch { match_type: Some(XdsMatchType::Exact( "spiffe://cluster.local/ns/ns/sa/sa".to_string(), diff --git a/src/rbac.rs b/src/rbac.rs index 26ca3f882..539cb1530 100644 --- a/src/rbac.rs +++ b/src/rbac.rs @@ -22,6 +22,7 @@ use xds::istio::security::string_match::MatchType; use xds::istio::security::Address as XdsAddress; use xds::istio::security::Authorization as XdsRbac; use xds::istio::security::Match; +use xds::istio::security::ServiceAccountMatch as XdsServiceAccountMatch; use xds::istio::security::StringMatch as XdsStringMatch; use crate::identity::Identity; @@ -82,6 +83,7 @@ impl Authorization { #[instrument(level = "trace", skip_all, fields(policy=self.to_key().as_str()))] pub fn matches(&self, conn: &Connection) -> bool { + let full_identity = conn.src_identity.as_ref(); let id = conn .src_identity .as_ref() @@ -133,6 +135,12 @@ impl Authorization { &mg.not_destination_ports, |p| *p == conn.dst.port(), ); + m &= Self::matches_internal( + "service_accounts", + &mg.service_accounts, + &mg.not_service_accounts, + |p| p.matches(&full_identity), + ); m &= Self::matches_internal( "principals", &mg.principals, @@ -207,6 +215,10 @@ pub struct RbacMatch { #[serde(skip_serializing_if = "Vec::is_empty", default)] pub not_namespaces: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub service_accounts: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub not_service_accounts: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] pub principals: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub not_principals: Vec, @@ -228,6 +240,8 @@ impl RbacMatch { fn is_empty(&self) -> bool { self.namespaces.is_empty() && self.not_namespaces.is_empty() + && self.service_accounts.is_empty() + && self.not_service_accounts.is_empty() && self.principals.is_empty() && self.not_principals.is_empty() && self.source_ips.is_empty() @@ -268,6 +282,26 @@ impl StringMatch { } } +#[derive(Debug, Hash, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] +pub struct ServiceAccountMatch { + namespace: Strng, + service_account: Strng, +} + +impl ServiceAccountMatch { + pub fn matches(&self, check: &Option<&Identity>) -> bool { + match check { + Some(Identity::Spiffe { + trust_domain: _, + namespace, + service_account, + }) => namespace == &self.namespace && service_account == &self.service_account, + // No identity at all, this does not match + None => false, + } + } +} + #[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum RbacScope { Global, @@ -340,6 +374,12 @@ impl TryFrom<&Match> for RbacMatch { .iter() .filter_map(From::from) .collect(), + service_accounts: resource.service_accounts.iter().map(From::from).collect(), + not_service_accounts: resource + .not_service_accounts + .iter() + .map(From::from) + .collect(), principals: resource.principals.iter().filter_map(From::from).collect(), not_principals: resource .not_principals @@ -401,6 +441,15 @@ impl From<&XdsStringMatch> for Option { } } +impl From<&XdsServiceAccountMatch> for ServiceAccountMatch { + fn from(resource: &XdsServiceAccountMatch) -> Self { + Self { + namespace: resource.namespace.as_str().into(), + service_account: resource.service_account.as_str().into(), + } + } +} + #[cfg(test)] mod tests { use test_case::test_case; @@ -620,6 +669,15 @@ mod tests { &tls_conn() => false, &tls_conn_alt() => true); + rbac_test!(service_accounts, vec![ServiceAccountMatch {namespace: "namespace".into(), service_account: "account".into() }], + &plaintext_conn() => false, + &tls_conn() => true, + &tls_conn_alt() => false); + rbac_test!(not_service_accounts, vec![ServiceAccountMatch {namespace: "namespace".into(), service_account: "account".into() }], + &plaintext_conn() => true, + &tls_conn() => false, + &tls_conn_alt() => true); + rbac_test!(principals, vec![StringMatch::Exact("td/ns/namespace/sa/account".into())], &plaintext_conn() => false, &tls_conn() => true,