1use std::error::Error as StdError;
2
3use lazy_static::lazy_static;
4use regex::Regex;
5use serde::Deserialize;
6
7use crate::{
8 core::{Identities, Operations, Resources},
9 matcher, Decision, DefaultSubstituter, DefaultValidator, Error, Policy, PolicyValidator,
10 ResourceMatcher, Result, Substituter,
11};
12
13pub struct PolicyBuilder<V, M, S> {
19 validator: V,
20 matcher: M,
21 substituter: S,
22 source: Source,
23 default_decision: Decision,
24}
25
26impl PolicyBuilder<DefaultValidator, matcher::Default, DefaultSubstituter> {
27 pub fn from_json(
33 json: impl Into<String>,
34 ) -> PolicyBuilder<DefaultValidator, matcher::Default, DefaultSubstituter> {
35 PolicyBuilder {
36 source: Source::Json(json.into()),
37 validator: DefaultValidator,
38 matcher: matcher::Default,
39 substituter: DefaultSubstituter,
40 default_decision: Decision::Denied,
41 }
42 }
43
44 pub fn from_definition(
50 definition: PolicyDefinition,
51 ) -> PolicyBuilder<DefaultValidator, matcher::Default, DefaultSubstituter> {
52 PolicyBuilder {
53 source: Source::Definition(definition),
54 validator: DefaultValidator,
55 matcher: matcher::Default,
56 substituter: DefaultSubstituter,
57 default_decision: Decision::Denied,
58 }
59 }
60}
61
62impl<V, M, S, E> PolicyBuilder<V, M, S>
63where
64 V: PolicyValidator<Error = E>,
65 M: ResourceMatcher,
66 S: Substituter,
67 E: StdError + Sync + Into<Box<dyn StdError>> + 'static,
68{
69 pub fn with_validator<V1>(self, validator: V1) -> PolicyBuilder<V1, M, S> {
71 PolicyBuilder {
72 source: self.source,
73 validator,
74 matcher: self.matcher,
75 substituter: self.substituter,
76 default_decision: self.default_decision,
77 }
78 }
79
80 pub fn with_matcher<M1>(self, matcher: M1) -> PolicyBuilder<V, M1, S> {
82 PolicyBuilder {
83 source: self.source,
84 validator: self.validator,
85 matcher,
86 substituter: self.substituter,
87 default_decision: self.default_decision,
88 }
89 }
90
91 pub fn with_substituter<S1>(self, substituter: S1) -> PolicyBuilder<V, M, S1> {
93 PolicyBuilder {
94 source: self.source,
95 validator: self.validator,
96 matcher: self.matcher,
97 substituter,
98 default_decision: self.default_decision,
99 }
100 }
101
102 pub fn with_default_decision(mut self, decision: Decision) -> Self {
105 self.default_decision = decision;
106 self
107 }
108
109 pub fn build(self) -> Result<Policy<M, S>> {
117 let PolicyBuilder {
118 validator,
119 matcher,
120 substituter,
121 source,
122 default_decision,
123 } = self;
124
125 let mut definition: PolicyDefinition = match source {
126 Source::Json(json) => PolicyDefinition::from_json(&json)?,
127 Source::Definition(definition) => definition,
128 };
129
130 for (order, mut statement) in definition.statements.iter_mut().enumerate() {
131 statement.order = order;
132 }
133
134 validator
135 .validate(&definition)
136 .map_err(|e| Error::Validation(e.into()))?;
137
138 let mut static_rules = Identities::new();
139 let mut variable_rules = Identities::new();
140
141 for statement in definition.statements {
142 process_statement(&statement, &mut static_rules, &mut variable_rules);
143 }
144
145 Ok(Policy {
146 default_decision,
147 resource_matcher: matcher,
148 substituter,
149 static_rules: static_rules.0,
150 variable_rules: variable_rules.0,
151 })
152 }
153}
154
155fn process_statement(
156 statement: &Statement,
157 static_rules: &mut Identities,
158 variable_rules: &mut Identities,
159) {
160 let (static_ids, variable_ids) = process_identities(statement);
161
162 static_rules.merge(static_ids);
163 variable_rules.merge(variable_ids);
164}
165
166fn process_identities(statement: &Statement) -> (Identities, Identities) {
167 let mut static_ids = Identities::new();
168 let mut variable_ids = Identities::new();
169 for identity in &statement.identities {
170 let (static_ops, variable_ops) = process_operations(&statement);
171
172 if is_variable_rule(identity) {
173 let mut all = static_ops.clone();
177 all.merge(variable_ops);
178 variable_ids.insert(identity, all);
179 } else {
180 static_ids.insert(identity, static_ops);
183 variable_ids.insert(identity, variable_ops);
184 }
185 }
186
187 (static_ids, variable_ids)
188}
189
190fn process_operations(statement: &Statement) -> (Operations, Operations) {
191 let mut static_ops = Operations::new();
192 let mut variable_ops = Operations::new();
193 for operation in &statement.operations {
194 let (static_res, variable_res) = process_resources(&statement);
195
196 if is_variable_rule(operation) {
197 let mut all = static_res.clone();
201 all.merge(variable_res);
202 variable_ops.insert(operation, all);
203 } else {
204 static_ops.insert(operation, static_res);
207 variable_ops.insert(operation, variable_res);
208 }
209 }
210
211 (static_ops, variable_ops)
212}
213
214fn process_resources(statement: &Statement) -> (Resources, Resources) {
215 let mut static_res = Resources::new();
216 let mut variable_res = Resources::new();
217 if statement.resources.is_empty() {
218 static_res.insert("", statement.into());
219 }
220
221 for resource in &statement.resources {
222 let map = if is_variable_rule(resource) {
224 &mut variable_res
225 } else {
226 &mut static_res
227 };
228
229 map.insert(resource, statement.into());
230 }
231
232 (static_res, variable_res)
233}
234
235fn is_variable_rule(value: &str) -> bool {
236 lazy_static! {
237 static ref VAR_PATTERN: Regex =
238 Regex::new(r#"\{\{[^\{\}]+\}\}"#).expect("failed to create a Regex from pattern");
239 }
240 VAR_PATTERN.is_match(value)
241}
242
243enum Source {
244 Json(String),
245 Definition(PolicyDefinition),
246}
247
248#[derive(Debug, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct PolicyDefinition {
252 pub(super) statements: Vec<Statement>,
253}
254
255impl PolicyDefinition {
256 pub fn from_json(json: &str) -> Result<Self> {
257 let definition: PolicyDefinition =
258 serde_json::from_str(json).map_err(Error::Deserializing)?;
259
260 Ok(definition)
261 }
262
263 pub fn statements(&self) -> &Vec<Statement> {
264 &self.statements
265 }
266}
267
268#[derive(Debug, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct Statement {
272 #[serde(default)]
273 pub(super) order: usize,
274 #[serde(default)]
275 pub(super) description: String,
276 pub(super) effect: Effect,
277 pub(super) identities: Vec<String>,
278 pub(super) operations: Vec<String>,
279 #[serde(default)]
280 pub(super) resources: Vec<String>,
281}
282
283impl Statement {
284 pub(crate) fn order(&self) -> usize {
285 self.order
286 }
287
288 pub fn description(&self) -> &str {
289 &self.description
290 }
291
292 pub fn effect(&self) -> Effect {
293 self.effect
294 }
295
296 pub fn identities(&self) -> &Vec<String> {
297 &self.identities
298 }
299
300 pub fn operations(&self) -> &Vec<String> {
301 &self.operations
302 }
303
304 pub fn resources(&self) -> &Vec<String> {
305 &self.resources
306 }
307}
308
309#[derive(Debug, Deserialize, Copy, Clone, PartialOrd, PartialEq)]
311#[serde(rename_all = "camelCase")]
312pub enum Effect {
313 Allow,
314 Deny,
315}
316
317#[cfg(test)]
318mod tests {
319 use std::result::Result as StdResult;
320
321 use assert_matches::assert_matches;
322
323 use crate::{
324 core::{tests::build_policy, Effect, EffectOrd},
325 validator::ValidatorError,
326 };
327
328 use super::*;
329
330 #[test]
331 fn test_basic_definition() {
332 let json = r#"{
333 "statements": [
334 {
335 "effect": "allow",
336 "identities": [
337 "actor_a"
338 ],
339 "operations": [
340 "read"
341 ],
342 "resources": [
343 "resource_group"
344 ]
345 },
346 {
347 "effect": "allow",
348 "identities": [
349 "actor_b"
350 ],
351 "operations": [
352 "write"
353 ],
354 "resources": [
355 "resource_1"
356 ]
357 },
358 {
359 "description": "Deny all other identities to read",
360 "effect": "deny",
361 "identities": [
362 "{{var_actor}}"
363 ],
364 "operations": [
365 "read"
366 ],
367 "resources": [
368 "resource_group"
369 ]
370 }
371 ]
372 }"#;
373
374 let policy = build_policy(json);
375
376 assert_eq!(1, policy.variable_rules.len());
377 assert_eq!(2, policy.static_rules.len());
378 }
379
380 #[test]
381 fn identity_merge_rules() {
382 let json = r#"{
383 "statements": [
384 {
385 "effect": "allow",
386 "identities": [
387 "actor_a"
388 ],
389 "operations": [
390 "write"
391 ],
392 "resources": [
393 "events/telemetry"
394 ]
395 },
396 {
397 "effect": "allow",
398 "identities": [
399 "actor_a"
400 ],
401 "operations": [
402 "read"
403 ],
404 "resources": [
405 "resource_1"
406 ]
407 },
408 {
409 "effect": "allow",
410 "identities": [
411 "actor_a"
412 ],
413 "operations": [
414 "read"
415 ],
416 "resources": [
417 "{{variable}}/#"
418 ]
419 },
420 {
421 "effect": "allow",
422 "identities": [
423 "actor_a"
424 ],
425 "operations": [
426 "write"
427 ],
428 "resources": [
429 "{{variable}}/#"
430 ]
431 }
432 ]
433 }"#;
434
435 let policy = build_policy(json);
436
437 assert_eq!(1, policy.static_rules.len());
439 assert_eq!(2, policy.static_rules["actor_a"].0.len());
440
441 assert_eq!(1, policy.variable_rules.len());
443 assert_eq!(2, policy.variable_rules["actor_a"].0.len());
444 }
445
446 #[test]
447 fn operation_merge_rules() {
448 let json = r#"{
449 "statements": [
450 {
451 "effect": "allow",
452 "identities": [
453 "actor_a"
454 ],
455 "operations": [
456 "write"
457 ],
458 "resources": [
459 "events/telemetry"
460 ]
461 },
462 {
463 "effect": "allow",
464 "identities": [
465 "actor_a"
466 ],
467 "operations": [
468 "write"
469 ],
470 "resources": [
471 "resource_1"
472 ]
473 },
474 {
475 "effect": "allow",
476 "identities": [
477 "actor_a"
478 ],
479 "operations": [
480 "read"
481 ],
482 "resources": [
483 "{{variable}}/#"
484 ]
485 },
486 {
487 "effect": "allow",
488 "identities": [
489 "actor_a"
490 ],
491 "operations": [
492 "read"
493 ],
494 "resources": [
495 "devices/{{variable}}/#"
496 ]
497 }
498 ]
499 }"#;
500
501 let policy = build_policy(json);
502
503 assert_eq!(1, policy.static_rules["actor_a"].0.len());
505 assert_eq!(2, policy.static_rules["actor_a"].0["write"].0.len());
506
507 assert_eq!(1, policy.variable_rules["actor_a"].0.len());
509 assert_eq!(2, policy.variable_rules["actor_a"].0["read"].0.len());
510 }
511
512 #[test]
513 fn resource_merge_rules_higher_priority_statement_wins() {
514 let json = r#"{
515 "statements": [
516 {
517 "effect": "allow",
518 "identities": [
519 "actor_a"
520 ],
521 "operations": [
522 "write"
523 ],
524 "resources": [
525 "events/telemetry"
526 ]
527 },
528 {
529 "effect": "deny",
530 "identities": [
531 "actor_a"
532 ],
533 "operations": [
534 "write"
535 ],
536 "resources": [
537 "events/telemetry"
538 ]
539 },
540 {
541 "effect": "allow",
542 "identities": [
543 "actor_a"
544 ],
545 "operations": [
546 "read"
547 ],
548 "resources": [
549 "{{variable}}/#"
550 ]
551 },
552 {
553 "effect": "deny",
554 "identities": [
555 "actor_a"
556 ],
557 "operations": [
558 "read"
559 ],
560 "resources": [
561 "{{variable}}/#"
562 ]
563 }
564 ]
565 }"#;
566
567 let policy = build_policy(json);
568
569 assert_eq!(
571 EffectOrd {
572 order: 0,
573 effect: Effect::Allow
574 },
575 policy.static_rules["actor_a"].0["write"].0["events/telemetry"]
576 );
577
578 assert_eq!(
580 EffectOrd {
581 order: 2,
582 effect: Effect::Allow
583 },
584 policy.variable_rules["actor_a"].0["read"].0["{{variable}}/#"]
585 );
586 }
587
588 #[test]
589 #[allow(clippy::too_many_lines)]
590 fn grouping_rules_with_variables_test() {
591 let json = r#"{
592 "statements": [
593 {
594 "effect": "allow",
595 "identities": [
596 "actor_a",
597 "actor_b",
598 "{{var_actor}}"
599 ],
600 "operations": [
601 "write",
602 "read"
603 ],
604 "resources": [
605 "events/telemetry",
606 "devices/{{variable}}/#"
607 ]
608 }
609 ]
610 }"#;
611
612 let policy = build_policy(json);
613
614 assert_eq!(2, policy.static_rules.len());
616 assert_eq!(
617 policy.static_rules["actor_a"].0["write"].0["events/telemetry"],
618 EffectOrd {
619 effect: Effect::Allow,
620 order: 0
621 }
622 );
623 assert_eq!(
624 policy.static_rules["actor_a"].0["read"].0["events/telemetry"],
625 EffectOrd {
626 effect: Effect::Allow,
627 order: 0
628 }
629 );
630 assert_eq!(
631 policy.static_rules["actor_b"].0["write"].0["events/telemetry"],
632 EffectOrd {
633 effect: Effect::Allow,
634 order: 0
635 }
636 );
637 assert_eq!(
638 policy.static_rules["actor_b"].0["read"].0["events/telemetry"],
639 EffectOrd {
640 effect: Effect::Allow,
641 order: 0
642 }
643 );
644
645 assert_eq!(3, policy.variable_rules.len());
647 assert_eq!(
648 policy.variable_rules["actor_a"].0["write"].0["devices/{{variable}}/#"],
649 EffectOrd {
650 effect: Effect::Allow,
651 order: 0
652 }
653 );
654 assert_eq!(
655 policy.variable_rules["actor_a"].0["read"].0["devices/{{variable}}/#"],
656 EffectOrd {
657 effect: Effect::Allow,
658 order: 0
659 }
660 );
661 assert_eq!(
662 policy.variable_rules["actor_b"].0["write"].0["devices/{{variable}}/#"],
663 EffectOrd {
664 effect: Effect::Allow,
665 order: 0
666 }
667 );
668 assert_eq!(
669 policy.variable_rules["actor_b"].0["read"].0["devices/{{variable}}/#"],
670 EffectOrd {
671 effect: Effect::Allow,
672 order: 0
673 }
674 );
675 assert_eq!(
676 policy.variable_rules["{{var_actor}}"].0["write"].0["devices/{{variable}}/#"],
677 EffectOrd {
678 effect: Effect::Allow,
679 order: 0
680 }
681 );
682 assert_eq!(
683 policy.variable_rules["{{var_actor}}"].0["read"].0["devices/{{variable}}/#"],
684 EffectOrd {
685 effect: Effect::Allow,
686 order: 0
687 }
688 );
689 }
690
691 #[test]
692 fn policy_validation_test() {
693 let json = r#"{
694 "statements": [
695 {
696 "effect": "allow",
697 "identities": [
698 "actor_a"
699 ],
700 "operations": [
701 "write"
702 ],
703 "resources": [
704 "events/telemetry"
705 ]
706 },
707 {
708 "effect": "allow",
709 "identities": [
710 "monitor"
711 ],
712 "operations": [
713 "read"
714 ],
715 "resources": [
716 "events/telemetry"
717 ]
718 }
719 ]
720 }"#;
721
722 let result = PolicyBuilder::from_json(json)
723 .with_validator(FailAllValidator)
724 .with_default_decision(Decision::Denied)
725 .build();
726
727 assert_matches!(result, Err(Error::Validation(_)));
728 }
729
730 #[derive(Debug)]
731 struct FailAllValidator;
732
733 impl PolicyValidator for FailAllValidator {
734 type Error = ValidatorError;
735
736 fn validate(&self, _definition: &PolicyDefinition) -> StdResult<(), Self::Error> {
737 Err(ValidatorError::ValidationSummary(vec!["error".to_string()]))
738 }
739 }
740}