Skip to content

Commit b8e1f63

Browse files
authored
Merge pull request #109 from awgn/enable-rbac-policy-name
Add policy IDs to RBAC internal structures.
2 parents 8f42acc + 1fcb5f9 commit b8e1f63

File tree

6 files changed

+129
-60
lines changed

6 files changed

+129
-60
lines changed

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

Lines changed: 58 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,19 @@ 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!(
90+
"HttpRbac: rule is enforced by {enforced_policy:?} with action: {:?} -> permitted {permitted}",
91+
self.action
92+
);
93+
(permitted, enforced_policy)
8894
}
8995
}
9096

@@ -115,17 +121,23 @@ mod rbac_tests {
115121
let permission = Permission::Any;
116122
let principal = Principal::Any;
117123
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")));
124+
let rbac_rule =
125+
HttpRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
126+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request("blah.com"));
127+
assert!(permitted);
128+
assert_eq!(rule, Some("my-id".into()));
120129
}
121130
#[test]
122131
fn rule_test_allow_host_permission_any_principal() {
123132
let host = "blah.com";
124133
let permission = Permission::Header(create_host_matcher(host));
125134
let principal = Principal::Any;
126135
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)));
136+
let rbac_rule =
137+
HttpRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
138+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
139+
assert!(permitted);
140+
assert_eq!(rule, Some("my-id".into()));
129141
}
130142

131143
#[test]
@@ -135,8 +147,11 @@ mod rbac_tests {
135147
let permission2 = Permission::Any;
136148
let principal = Principal::Any;
137149
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)));
150+
let rbac_rule =
151+
HttpRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
152+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
153+
assert!(permitted);
154+
assert_eq!(rule, Some("my-id".into()));
140155
}
141156

142157
#[test]
@@ -145,26 +160,37 @@ mod rbac_tests {
145160
let permission = Permission::Header(create_host_matcher(host));
146161
let principal = Principal::Header(create_host_matcher(host));
147162
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)));
163+
let rbac_rule =
164+
HttpRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
165+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
166+
assert!(permitted);
167+
assert_eq!(rule, Some("my-id".into()));
150168
}
151169

152170
#[test]
153171
fn rule_test_deny_any() {
172+
let host = "blah.com";
154173
let permission = Permission::Any;
155174
let principal = Principal::Any;
156175
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")));
176+
let rbac_rule =
177+
HttpRbac { action: Action::Deny, policies: vec![("my-id".into(), policy)].into_iter().collect() };
178+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
179+
assert!(!permitted);
180+
assert_eq!(rule, Some("my-id".into()));
159181
}
182+
160183
#[test]
161184
fn rule_test_deny_host_permission_any_principal() {
162185
let host = "blah.com";
163186
let permission = Permission::Header(create_host_matcher(host));
164187
let principal = Principal::Any;
165188
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)));
189+
let rbac_rule =
190+
HttpRbac { action: Action::Deny, policies: vec![("my-id".into(), policy)].into_iter().collect() };
191+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
192+
assert!(!permitted);
193+
assert_eq!(rule, Some("my-id".into()));
168194
}
169195

170196
#[test]
@@ -173,8 +199,11 @@ mod rbac_tests {
173199
let permission = Permission::Header(create_host_matcher(host));
174200
let principal = Principal::Header(create_host_matcher(host));
175201
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)));
202+
let rbac_rule =
203+
HttpRbac { action: Action::Deny, policies: vec![("my-id".into(), policy)].into_iter().collect() };
204+
let (permitted, rule) = rbac_rule.is_permitted(&create_host_request(host));
205+
assert!(!permitted);
206+
assert_eq!(rule, Some("my-id".into()));
178207
}
179208
}
180209

@@ -223,7 +252,10 @@ mod envoy_conversions {
223252
let EnvoyHttpRbac { action, policies, audit_logging_options } = envoy;
224253
unsupported_field!(audit_logging_options)?;
225254
let action = Action::try_from(action).with_node("action")?;
226-
let policies = required!(policies)?.into_values().map(Policy::try_from).collect::<Result<_, _>>()?;
255+
let policies = required!(policies)?
256+
.into_iter()
257+
.map(|(id, pol)| Policy::try_from(pol).map(|value| (id.into(), value)))
258+
.collect::<Result<_, _>>()?;
227259
Ok(HttpRbac { action, policies })
228260
}
229261
}

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

Lines changed: 52 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,18 @@ 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!(
116+
"NetworkRbac: rule is enforced by {enforced_policy:?} with action: {:?} -> permitted {permitted}",
117+
self.action
118+
);
119+
(permitted, enforced_policy)
117120
}
118121
}
119122

@@ -141,16 +144,24 @@ mod tests {
141144
let permission = Permission::Any;
142145
let principal = Principal::Any;
143146
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)));
147+
let rbac_rule =
148+
NetworkRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
149+
let (permitted, rule) =
150+
rbac_rule.is_permitted(&create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None));
151+
assert!(permitted);
152+
assert_eq!(rule, Some("my-id".into()));
146153
}
147154
#[test]
148155
fn rule_test_allow_dest_ip_permission_any_principal() {
149156
let permission = Permission::DestinationIp("127.0.0.0/24".parse().unwrap());
150157
let principal = Principal::Any;
151158
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)));
159+
let rbac_rule =
160+
NetworkRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
161+
let (permitted, rule) =
162+
rbac_rule.is_permitted(&create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None));
163+
assert!(permitted);
164+
assert_eq!(rule, Some("my-id".into()));
154165
}
155166

156167
#[test]
@@ -159,8 +170,12 @@ mod tests {
159170
let permission1 = Permission::Any;
160171
let principal = Principal::Any;
161172
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)));
173+
let rbac_rule =
174+
NetworkRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
175+
let (permitted, rule) =
176+
rbac_rule.is_permitted(&create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None));
177+
assert!(permitted);
178+
assert_eq!(rule, Some("my-id".into()));
164179
}
165180

166181
#[test]
@@ -169,17 +184,25 @@ mod tests {
169184
let permission1 = Permission::Any;
170185
let principal = Principal::Any;
171186
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)));
187+
let rbac_rule =
188+
NetworkRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
189+
let (permitted, rule) =
190+
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()));
174193
}
175194

176195
#[test]
177196
fn rule_test_allow_dest_ip_permission_src_ip_principal() {
178197
let permission = Permission::DestinationIp("127.0.0.0/24".parse().unwrap());
179198
let principal = Principal::DownstreamRemoteIp("127.0.0.0/24".parse().unwrap());
180199
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)));
200+
let rbac_rule =
201+
NetworkRbac { action: Action::Allow, policies: vec![("my-id".into(), policy)].into_iter().collect() };
202+
let (permitted, rule) =
203+
rbac_rule.is_permitted(&create_network_context("127.0.0.1", 8000, "127.0.0.1", 9000, None));
204+
assert!(permitted);
205+
assert_eq!(rule, Some("my-id".into()));
183206
}
184207
}
185208

@@ -205,7 +228,10 @@ mod envoy_conversions {
205228
let EnvoyRbac { action, policies, audit_logging_options } = envoy;
206229
unsupported_field!(audit_logging_options)?;
207230
let action = Action::try_from(action).with_node("action")?;
208-
let policies = required!(policies)?.into_values().map(Policy::try_from).collect::<Result<_, _>>()?;
231+
let policies = required!(policies)?
232+
.into_iter()
233+
.map(|(id, pol)| Policy::try_from(pol).map(|value| (id.into(), value)))
234+
.collect::<Result<_, _>>()?;
209235
Ok(NetworkRbac { action, policies })
210236
}
211237
}

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: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
//
1616
//
1717

18+
use compact_str::CompactString;
1819
use http::Response;
1920
use orion_format::types::ResponseFlags as FmtResponseFlags;
21+
use orion_interner::StringInterner;
2022
use std::error::Error as ErrorTrait;
2123
use std::io;
2224
use tokio::time::error::Elapsed;
@@ -54,7 +56,7 @@ pub enum EventKind {
5456
NoHealthyUpstream,
5557
RouteNotFound,
5658
UpgradeFailed,
57-
RbacAccessDenied,
59+
RbacAccessDenied(CompactString),
5860
RateLimited,
5961
ViaUpstream,
6062
}
@@ -79,7 +81,9 @@ 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) => {
85+
Some(ResponseCodeDetails(format!("rbac_access_denied[{id}]").to_static_str()))
86+
},
8387
EventKind::RateLimited => Some(ResponseCodeDetails("rate_limited")),
8488
EventKind::ViaUpstream => Some(ResponseCodeDetails("via_upstream")),
8589
}
@@ -93,7 +97,9 @@ impl EventKind {
9397
EventError::RouteTimeout => Some(ConnectionTerminationDetails("route timeout was reached")),
9498
_ => None,
9599
},
96-
EventKind::RbacAccessDenied => Some(ConnectionTerminationDetails("rbac_access_denied_matched_policy")),
100+
EventKind::RbacAccessDenied(id) => {
101+
Some(ConnectionTerminationDetails(format!("rbac_access_denied_matched_policy[{id}]").to_static_str()))
102+
},
97103
_ => None,
98104
}
99105
}

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
}

0 commit comments

Comments
 (0)