Skip to content

Commit a5af520

Browse files
committed
Add policy IDs to RBAC and return matched policy name
- Replace Vec with BTreeMap for policies in HttpRbac and NetworkRbac - Update is_permitted to return (bool, Option<CompactString>) with matched policy - Update tests and conversions to use policy IDs - Include policy ID in EventKind::RbacAccessDenied and response code details Signed-off-by: Nicola Bonelli <nicola.bonelli@huawei-partners.com>
1 parent 8f42acc commit a5af520

File tree

6 files changed

+99
-60
lines changed

6 files changed

+99
-60
lines changed

orion-configuration/src/config/network_filters/http_connection_manager/http_filters/http_rbac.rs

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@
1515
//
1616
//
1717

18+
use std::collections::BTreeMap;
19+
1820
use crate::config::network_filters::{http_connection_manager::header_matcher::HeaderMatcher, network_rbac::Action};
21+
use compact_str::CompactString;
1922
use http::Request;
2023
use serde::{Deserialize, Serialize};
2124
use tracing::debug;
2225

2326
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
2427
pub struct HttpRbac {
2528
pub action: Action,
26-
//todo(hayley): replace vec with std::collections::BTreeMap
27-
// and include the policy name as Envoy says to apply them
28-
// in lexical order
29-
pub policies: Vec<Policy>,
29+
pub policies: BTreeMap<CompactString, Policy>,
3030
}
3131

3232
//since we support different permission and principals for http vs network rbac, this struct is different from the network rbac
@@ -78,13 +78,16 @@ impl Policy {
7878
}
7979

8080
impl HttpRbac {
81-
pub fn is_permitted<B>(&self, req: &Request<B>) -> bool {
82-
let is_enforced = self.policies.iter().any(|p| p.enforce(req));
83-
debug!("Rule is enforced {is_enforced}");
84-
match self.action {
85-
Action::Allow => is_enforced,
86-
Action::Deny => !is_enforced,
87-
}
81+
pub fn is_permitted<B>(&self, req: &Request<B>) -> (bool, Option<CompactString>) {
82+
// policies are applied in the lexicographical order of their keys
83+
let enforced_policy = self.policies.iter().find(|(_, p)| p.enforce(req)).map(|(name, _)| name.clone());
84+
let permitted = match self.action {
85+
Action::Allow => enforced_policy.is_some(),
86+
Action::Deny => enforced_policy.is_none(),
87+
};
88+
89+
debug!("HttpRbac: rule is enforced by {enforced_policy:?} with action: {:?} -> permitted {permitted}", self.action);
90+
(permitted, enforced_policy)
8891
}
8992
}
9093

@@ -115,17 +118,21 @@ mod rbac_tests {
115118
let permission = Permission::Any;
116119
let principal = Principal::Any;
117120
let policy = Policy { permissions: vec![permission], principals: vec![principal] };
118-
let rbac_rule = HttpRbac { action: Action::Allow, policies: vec![policy] };
119-
assert!(rbac_rule.is_permitted(&create_host_request("blah.com")));
121+
let rbac_rule = HttpRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
122+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request("blah.com"));
123+
assert!(permitted);
124+
assert_eq!(rule, Some("my-id".into()));
120125
}
121126
#[test]
122127
fn rule_test_allow_host_permission_any_principal() {
123128
let host = "blah.com";
124129
let permission = Permission::Header(create_host_matcher(host));
125130
let principal = Principal::Any;
126131
let policy = Policy { permissions: vec![permission], principals: vec![principal] };
127-
let rbac_rule = HttpRbac { action: Action::Allow, policies: vec![policy] };
128-
assert!(rbac_rule.is_permitted(&create_host_request(host)));
132+
let rbac_rule = HttpRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
133+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
134+
assert!(permitted);
135+
assert_eq!(rule, Some("my-id".into()));
129136
}
130137

131138
#[test]
@@ -135,8 +142,10 @@ mod rbac_tests {
135142
let permission2 = Permission::Any;
136143
let principal = Principal::Any;
137144
let policy = Policy { permissions: vec![permission1, permission2], principals: vec![principal] };
138-
let rbac_rule = HttpRbac { action: Action::Allow, policies: vec![policy] };
139-
assert!(rbac_rule.is_permitted(&create_host_request(host)));
145+
let rbac_rule = HttpRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
146+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
147+
assert!(permitted);
148+
assert_eq!(rule, Some("my-id".into()));
140149
}
141150

142151
#[test]
@@ -145,26 +154,34 @@ mod rbac_tests {
145154
let permission = Permission::Header(create_host_matcher(host));
146155
let principal = Principal::Header(create_host_matcher(host));
147156
let policy = Policy { permissions: vec![permission], principals: vec![principal] };
148-
let rbac_rule = HttpRbac { action: Action::Allow, policies: vec![policy] };
149-
assert!(rbac_rule.is_permitted(&create_host_request(host)));
157+
let rbac_rule = HttpRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
158+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
159+
assert!(permitted);
160+
assert_eq!(rule, Some("my-id".into()));
150161
}
151162

152163
#[test]
153164
fn rule_test_deny_any() {
165+
let host = "blah.com";
154166
let permission = Permission::Any;
155167
let principal = Principal::Any;
156168
let policy = Policy { permissions: vec![permission], principals: vec![principal] };
157-
let rbac_rule = HttpRbac { action: Action::Deny, policies: vec![policy] };
158-
assert!(!rbac_rule.is_permitted(&create_host_request("blah.com")));
169+
let rbac_rule = HttpRbac { action: Action::Deny, policies: vec![("my-id".into(), policy)].into_iter().collect() };
170+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
171+
assert!(!permitted);
172+
assert_eq!(rule, Some("my-id".into()));
159173
}
174+
160175
#[test]
161176
fn rule_test_deny_host_permission_any_principal() {
162177
let host = "blah.com";
163178
let permission = Permission::Header(create_host_matcher(host));
164179
let principal = Principal::Any;
165180
let policy = Policy { permissions: vec![permission], principals: vec![principal] };
166-
let rbac_rule = HttpRbac { action: Action::Deny, policies: vec![policy] };
167-
assert!(!rbac_rule.is_permitted(&create_host_request(host)));
181+
let rbac_rule = HttpRbac { action: Action::Deny, policies: vec![("my-id".into(), policy)].into_iter().collect() };
182+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
183+
assert!(!permitted);
184+
assert_eq!(rule, Some("my-id".into()));
168185
}
169186

170187
#[test]
@@ -173,8 +190,10 @@ mod rbac_tests {
173190
let permission = Permission::Header(create_host_matcher(host));
174191
let principal = Principal::Header(create_host_matcher(host));
175192
let policy = Policy { permissions: vec![permission], principals: vec![principal] };
176-
let rbac_rule = HttpRbac { action: Action::Deny, policies: vec![policy] };
177-
assert!(!rbac_rule.is_permitted(&create_host_request(host)));
193+
let rbac_rule = HttpRbac { action: Action::Deny, policies: vec![("my-id".into(), policy)].into_iter().collect() };
194+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
195+
assert!(!permitted);
196+
assert_eq!(rule, Some("my-id".into()));
178197
}
179198
}
180199

@@ -223,7 +242,10 @@ mod envoy_conversions {
223242
let EnvoyHttpRbac { action, policies, audit_logging_options } = envoy;
224243
unsupported_field!(audit_logging_options)?;
225244
let action = Action::try_from(action).with_node("action")?;
226-
let policies = required!(policies)?.into_values().map(Policy::try_from).collect::<Result<_, _>>()?;
245+
let policies = required!(policies)?
246+
.into_iter()
247+
.map(|(id, pol)| Policy::try_from(pol).map(|value| (id.into(), value)))
248+
.collect::<Result<_, _>>()?;
227249
Ok(HttpRbac { action, policies })
228250
}
229251
}

orion-configuration/src/config/network_filters/network_rbac.rs

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,16 @@
1616
//
1717

1818
use crate::config::core::StringMatcher;
19+
use compact_str::CompactString;
1920
use ipnet::IpNet;
2021
use serde::{Deserialize, Serialize};
21-
use std::net::SocketAddr;
22+
use std::{collections::BTreeMap, net::SocketAddr};
2223
use tracing::debug;
2324

2425
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
2526
pub struct NetworkRbac {
2627
pub action: Action,
27-
//fixme(hayley): replace vec with std::collections::BTreeMap
28-
// and include the policy name as Envoy says to apply them
29-
// in lexical order
30-
pub policies: Vec<Policy>,
28+
pub policies: BTreeMap<CompactString, Policy>,
3129
}
3230

3331
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
@@ -74,7 +72,7 @@ impl<'a> NetworkContext<'a> {
7472
}
7573

7674
impl Permission {
77-
fn is_applicable(&self, ctx: NetworkContext) -> bool {
75+
fn is_applicable(&self, ctx: &NetworkContext) -> bool {
7876
match self {
7977
Self::Any => true,
8078
Self::DestinationIp(ip) => ip.contains(&ctx.destination.ip()),
@@ -89,7 +87,7 @@ impl Permission {
8987
}
9088

9189
impl Principal {
92-
fn has_principal(&self, ctx: NetworkContext) -> bool {
90+
fn has_principal(&self, ctx: &NetworkContext) -> bool {
9391
match self {
9492
Principal::Any => true,
9593
Principal::DownstreamRemoteIp(ip) => ip.contains(&ctx.downstream.ip()),
@@ -98,7 +96,7 @@ impl Principal {
9896
}
9997

10098
impl Policy {
101-
fn enforce(&self, ctx: NetworkContext) -> bool {
99+
fn enforce(&self, ctx: &NetworkContext) -> bool {
102100
let has_permission = self.permissions.iter().any(|p| p.is_applicable(ctx));
103101
let has_principal = self.principals.iter().any(|p| p.has_principal(ctx));
104102
debug!("Enforcing policy permissions {has_permission} principals {has_principal}");
@@ -107,13 +105,15 @@ impl Policy {
107105
}
108106

109107
impl NetworkRbac {
110-
pub fn is_permitted(&self, ctx: NetworkContext) -> bool {
111-
let is_enforced = self.policies.iter().any(|p| p.enforce(ctx));
112-
debug!("Rule is enforced {is_enforced}");
113-
match self.action {
114-
Action::Allow => is_enforced,
115-
Action::Deny => !is_enforced,
116-
}
108+
pub fn is_permitted(&self, ctx: &NetworkContext) -> (bool, Option<CompactString>) {
109+
let enforced_policy = self.policies.iter().find(|(_, p)| p.enforce(ctx)).map(|(id, _)| id.clone());
110+
let permitted = match self.action {
111+
Action::Allow => enforced_policy.is_some(),
112+
Action::Deny => enforced_policy.is_none(),
113+
};
114+
115+
debug!("NetworkRbac: rule is enforced by {enforced_policy:?} with action: {:?} -> permitted {permitted}", self.action);
116+
(permitted, enforced_policy)
117117
}
118118
}
119119

@@ -141,16 +141,20 @@ mod tests {
141141
let permission = Permission::Any;
142142
let principal = Principal::Any;
143143
let policy = Policy { permissions: vec![permission], principals: vec![principal] };
144-
let rbac_rule = NetworkRbac { action: Action::Allow, policies: vec![policy] };
145-
assert!(rbac_rule.is_permitted(create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None)));
144+
let rbac_rule = NetworkRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
145+
let (permitted, rule) = rbac_rule.is_permitted(&create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None));
146+
assert!(permitted);
147+
assert_eq!(rule, Some("my-id".into()));
146148
}
147149
#[test]
148150
fn rule_test_allow_dest_ip_permission_any_principal() {
149151
let permission = Permission::DestinationIp("127.0.0.0/24".parse().unwrap());
150152
let principal = Principal::Any;
151153
let policy = Policy { permissions: vec![permission], principals: vec![principal] };
152-
let rbac_rule = NetworkRbac { action: Action::Allow, policies: vec![policy] };
153-
assert!(rbac_rule.is_permitted(create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None)));
154+
let rbac_rule = NetworkRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
155+
let (permitted, rule) = rbac_rule.is_permitted(&create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None));
156+
assert!(permitted);
157+
assert_eq!(rule, Some("my-id".into()));
154158
}
155159

156160
#[test]
@@ -159,8 +163,10 @@ mod tests {
159163
let permission1 = Permission::Any;
160164
let principal = Principal::Any;
161165
let policy = Policy { permissions: vec![permission1, permission2], principals: vec![principal] };
162-
let rbac_rule = NetworkRbac { action: Action::Allow, policies: vec![policy] };
163-
assert!(rbac_rule.is_permitted(create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None)));
166+
let rbac_rule = NetworkRbac { action: Action::Allow, policies: vec![("my-id".into(),policy)].into_iter().collect() };
167+
let (permitted, rule) = rbac_rule.is_permitted(&create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None));
168+
assert!(permitted);
169+
assert_eq!(rule, Some("my-id".into()));
164170
}
165171

166172
#[test]
@@ -169,17 +175,21 @@ mod tests {
169175
let permission1 = Permission::Any;
170176
let principal = Principal::Any;
171177
let policy = Policy { permissions: vec![permission1, permission2], principals: vec![principal] };
172-
let rbac_rule = NetworkRbac { action: Action::Allow, policies: vec![policy] };
173-
assert!(rbac_rule.is_permitted(create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None)));
178+
let rbac_rule = NetworkRbac { action: Action::Allow, policies: vec![("my-id".into(),policy)].into_iter().collect() };
179+
let (permitted, rule) = rbac_rule.is_permitted(&create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None));
180+
assert!(permitted);
181+
assert_eq!(rule, Some("my-id".into()));
174182
}
175183

176184
#[test]
177185
fn rule_test_allow_dest_ip_permission_src_ip_principal() {
178186
let permission = Permission::DestinationIp("127.0.0.0/24".parse().unwrap());
179187
let principal = Principal::DownstreamRemoteIp("127.0.0.0/24".parse().unwrap());
180188
let policy = Policy { permissions: vec![permission], principals: vec![principal] };
181-
let rbac_rule = NetworkRbac { action: Action::Allow, policies: vec![policy] };
182-
assert!(rbac_rule.is_permitted(create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None)));
189+
let rbac_rule = NetworkRbac { action: Action::Allow, policies: vec![("my-id".into(),policy)].into_iter().collect() };
190+
let (permitted, rule) = rbac_rule.is_permitted(&create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None));
191+
assert!(permitted);
192+
assert_eq!(rule, Some("my-id".into()));
183193
}
184194
}
185195

@@ -205,7 +215,10 @@ mod envoy_conversions {
205215
let EnvoyRbac { action, policies, audit_logging_options } = envoy;
206216
unsupported_field!(audit_logging_options)?;
207217
let action = Action::try_from(action).with_node("action")?;
208-
let policies = required!(policies)?.into_values().map(Policy::try_from).collect::<Result<_, _>>()?;
218+
let policies = required!(policies)?
219+
.into_iter()
220+
.map(|(id, pol)| Policy::try_from(pol).map(|value| (id.into(), value)))
221+
.collect::<Result<_, _>>()?;
209222
Ok(NetworkRbac { action, policies })
210223
}
211224
}

orion-error/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ impl Context for Error {
227227
}
228228
}
229229

230-
impl AsRef<(dyn ErrorTrait + Send + Sync + 'static)> for Error {
230+
impl AsRef<dyn ErrorTrait + Send + Sync + 'static> for Error {
231231
fn as_ref(&self) -> &(dyn ErrorTrait + Send + Sync + 'static) {
232232
&self.0
233233
}

orion-lib/src/event_error.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
use http::Response;
1919
use orion_format::types::ResponseFlags as FmtResponseFlags;
20+
use orion_interner::StringInterner;
2021
use std::error::Error as ErrorTrait;
22+
use compact_str::CompactString;
2123
use std::io;
2224
use tokio::time::error::Elapsed;
2325

@@ -54,14 +56,14 @@ pub enum EventKind {
5456
NoHealthyUpstream,
5557
RouteNotFound,
5658
UpgradeFailed,
57-
RbacAccessDenied,
59+
RbacAccessDenied(CompactString),
5860
RateLimited,
5961
ViaUpstream,
6062
}
6163

6264
impl EventKind {
6365
pub fn code_details(&self) -> Option<ResponseCodeDetails> {
64-
match self {
66+
match self {
6567
EventKind::Error(err) => match err {
6668
EventError::IoError(err) => Some(ResponseCodeDetails::from(err)),
6769
EventError::ConnectTimeout(_) => Some(ResponseCodeDetails("connect_timeout")),
@@ -79,7 +81,7 @@ impl EventKind {
7981
EventKind::NoHealthyUpstream => Some(ResponseCodeDetails("no_healthy_upstream")),
8082
EventKind::RouteNotFound => Some(ResponseCodeDetails("route_not_found")),
8183
EventKind::UpgradeFailed => Some(ResponseCodeDetails("upgrade_failed")),
82-
EventKind::RbacAccessDenied => Some(ResponseCodeDetails("rbac_access_denied")),
84+
EventKind::RbacAccessDenied(id) => Some(ResponseCodeDetails(format!("rbac_access_denied[{id}]").to_static_str())),
8385
EventKind::RateLimited => Some(ResponseCodeDetails("rate_limited")),
8486
EventKind::ViaUpstream => Some(ResponseCodeDetails("via_upstream")),
8587
}
@@ -93,7 +95,7 @@ impl EventKind {
9395
EventError::RouteTimeout => Some(ConnectionTerminationDetails("route timeout was reached")),
9496
_ => None,
9597
},
96-
EventKind::RbacAccessDenied => Some(ConnectionTerminationDetails("rbac_access_denied_matched_policy")),
98+
EventKind::RbacAccessDenied(id) => Some(ConnectionTerminationDetails(format!("rbac_access_denied_matched_policy[{id}]").to_static_str())),
9799
_ => None,
98100
}
99101
}

orion-lib/src/listeners/filterchain.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ impl FilterchainType {
161161
let network_context =
162162
NetworkContext::new(downstream_metadata.local_address(), downstream_metadata.peer_address(), server_name);
163163
for rbac in rbac_filters {
164-
if !rbac.is_permitted(network_context) {
164+
let (permitted, _) = rbac.is_permitted(&network_context);
165+
if !permitted {
165166
return None;
166167
}
167168
}

orion-lib/src/listeners/http_connection_manager.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,11 +1154,12 @@ fn eval_http_finish_context(
11541154

11551155
fn apply_authorization_rules<B>(rbac: &HttpRbac, req: &Request<B>) -> FilterDecision {
11561156
debug!("Applying authorization rules {rbac:?} {:?}", &req.headers());
1157-
if rbac.is_permitted(req) {
1157+
let (permitted, enforced_policy) = rbac.is_permitted(req);
1158+
if permitted {
11581159
FilterDecision::Continue
11591160
} else {
11601161
FilterDecision::DirectResponse(
1161-
SyntheticHttpResponse::forbidden(EventKind::RbacAccessDenied, "RBAC: access denied")
1162+
SyntheticHttpResponse::forbidden(EventKind::RbacAccessDenied(enforced_policy.unwrap_or(CompactString::new("unknown"))), "RBAC: access denied")
11621163
.into_response(req.version()),
11631164
)
11641165
}

0 commit comments

Comments
 (0)