Skip to main content

regorus/
compiled_policy.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3#![allow(
4    clippy::redundant_pub_crate,
5    clippy::missing_const_for_fn,
6    clippy::option_if_let_else,
7    clippy::pattern_type_mismatch
8)]
9
10use crate::{
11    ast::*, compiler::hoist::HoistedLoopsLookup, engine::Engine, scheduler::*, utils::*, *,
12};
13
14use alloc::collections::BTreeMap;
15use anyhow::Result;
16
17#[cfg(feature = "azure_policy")]
18use crate::target::Target;
19
20pub(crate) type DefaultRuleInfo = (Ref<Rule>, Option<crate::String>);
21
22#[cfg(feature = "azure_policy")]
23pub(crate) type ResourceTypeInfo = (Rc<str>, Rc<Schema>);
24
25#[cfg(feature = "azure_policy")]
26pub(crate) type InferredResourceTypes = BTreeMap<Ref<Query>, ResourceTypeInfo>;
27
28/// Wrapper around CompiledPolicyData that holds an Rc reference.
29#[derive(Debug, Clone)]
30pub struct CompiledPolicy {
31    pub(crate) inner: Rc<CompiledPolicyData>,
32}
33
34impl CompiledPolicy {
35    /// Create a new CompiledPolicy from CompiledPolicyData.
36    pub(crate) fn new(inner: Rc<CompiledPolicyData>) -> Self {
37        Self { inner }
38    }
39
40    /// Get access to the rules in the compiled policy for downstream consumers like the RVM compiler.
41    pub fn get_rules(&self) -> &Map<String, Vec<Ref<Rule>>> {
42        &self.inner.rules
43    }
44
45    /// Get access to the modules in the compiled policy.
46    pub fn get_modules(&self) -> &Vec<Ref<Module>> {
47        self.inner.modules.as_ref()
48    }
49
50    /// Returns true when the compiled policy should use Rego v0 semantics.
51    pub fn is_rego_v0(&self) -> bool {
52        !self.inner.modules.iter().any(|module| module.rego_v1)
53    }
54}
55
56impl CompiledPolicy {
57    /// Evaluate the compiled policy with the given input.
58    ///
59    /// For target policies, evaluates the target's effect rule.
60    /// For regular policies, evaluates the originally compiled rule.
61    ///
62    /// * `input`: Input data (resource) to validate against the policy.
63    ///
64    /// Returns the result of evaluating the rule.
65    pub fn eval_with_input(&self, input: Value) -> Result<Value> {
66        let mut engine = Engine::new_from_compiled_policy(self.inner.clone());
67
68        // Set input
69        engine.set_input(input);
70
71        // Evaluate the rule
72        #[cfg(feature = "azure_policy")]
73        if let Some(target_info) = self.inner.target_info.as_ref() {
74            return engine.eval_rule(target_info.effect_path.to_string());
75        }
76        engine.eval_rule(self.inner.rule_to_evaluate.to_string())
77    }
78
79    /// Get information about the compiled policy including metadata about modules,
80    /// target configuration, and resource types.
81    ///
82    /// Returns a [`crate::policy_info::PolicyInfo`] struct containing comprehensive
83    /// information about the compiled policy such as module IDs, target name,
84    /// applicable resource types, entry point rule, and parameters.
85    ///
86    /// # Examples
87    ///
88    /// ```no_run
89    /// use regorus::*;
90    /// # use std::sync::Arc;
91    ///
92    /// # fn main() -> anyhow::Result<()> {
93    /// # // Register a target for the example
94    /// # #[cfg(feature = "azure_policy")]
95    /// # {
96    /// #    let target = regorus::target::Target::from_json_file("tests/interpreter/cases/target/definitions/sample_target.json")?;
97    /// #    regorus::registry::targets::register(std::sync::Arc::new(target))?;
98    /// # }
99    ///
100    /// // Compile the policy
101    /// let policy_rego = r#"
102    ///     package policy.example
103    ///     import rego.v1
104    ///     __target__ := "target.tests.sample_test_target"
105    ///     
106    ///     effect := "allow" if {
107    ///         input.type == "storage_account"
108    ///         input.location in ["eastus", "westus"]
109    ///     }
110    /// "#;
111    ///
112    /// let modules = vec![regorus::PolicyModule {
113    ///     id: "policy.rego".into(),
114    ///     content: policy_rego.into(),
115    /// }];
116    ///
117    /// #[cfg(feature = "azure_policy")]
118    /// let compiled = regorus::compile_policy_for_target(Value::new_object(), &modules)?;
119    /// #[cfg(not(feature = "azure_policy"))]
120    /// let compiled = regorus::compile_policy_with_entrypoint(Value::new_object(), &modules, "allow".into())?;
121    /// let info = compiled.get_policy_info()?;
122    ///
123    /// assert_eq!(info.target_name, Some("target.tests.sample_test_target".into()));
124    /// assert_eq!(info.effect_rule, Some("effect".into()));
125    /// assert!(info.module_ids.len() > 0);
126    /// # Ok(())
127    /// # }
128    /// ```
129    pub fn get_policy_info(&self) -> Result<crate::policy_info::PolicyInfo> {
130        // Extract module IDs from the compiled policy
131        let module_ids: Vec<Rc<str>> = self
132            .inner
133            .modules
134            .iter()
135            .enumerate()
136            .map(|(i, module)| {
137                // Use source file path if available, otherwise generate an ID
138                let source_path = module.package.span.source.get_path();
139                if source_path.is_empty() {
140                    format!("module_{}", i).into()
141                } else {
142                    source_path.clone().into()
143                }
144            })
145            .collect();
146
147        // Extract target name and effect rule
148        #[cfg(feature = "azure_policy")]
149        let (target_name, effect_rule) = if let Some(target_info) = &self.inner.target_info {
150            (
151                Some(target_info.target.name.clone()),
152                Some(target_info.effect_name.clone()),
153            )
154        } else {
155            (None, None)
156        };
157
158        #[cfg(not(feature = "azure_policy"))]
159        let (target_name, effect_rule) = (None, None);
160
161        // Extract applicable resource types from inferred types
162        #[cfg(feature = "azure_policy")]
163        let applicable_resource_types: Vec<Rc<str>> =
164            if let Some(inferred_types) = &self.inner.inferred_resource_types {
165                inferred_types
166                    .values()
167                    .map(|(resource_type, _schema)| resource_type.clone())
168                    .collect::<std::collections::BTreeSet<_>>() // Remove duplicates
169                    .into_iter()
170                    .collect()
171            } else {
172                Vec::new()
173            };
174
175        #[cfg(not(feature = "azure_policy"))]
176        let applicable_resource_types: Vec<Rc<str>> = Vec::new();
177
178        // Get parameters from the modules
179        #[cfg(feature = "azure_policy")]
180        let parameters = {
181            // Create a new engine from the compiled modules to extract parameters
182            let temp_engine = crate::engine::Engine::new_from_compiled_policy(self.inner.clone());
183
184            temp_engine.get_policy_parameters()?
185        };
186
187        Ok(crate::policy_info::PolicyInfo {
188            module_ids,
189            target_name,
190            applicable_resource_types,
191            entrypoint_rule: self.inner.rule_to_evaluate.clone(),
192            effect_rule,
193            #[cfg(feature = "azure_policy")]
194            parameters,
195        })
196    }
197}
198
199#[cfg(feature = "azure_policy")]
200#[derive(Debug, Clone)]
201pub(crate) struct TargetInfo {
202    pub(crate) target: Rc<Target>,
203    pub(crate) package: String,
204    pub(crate) effect_schema: Rc<Schema>,
205    pub(crate) effect_name: Rc<str>,
206    pub(crate) effect_path: Rc<str>,
207}
208
209#[derive(Debug, Clone, Default)]
210pub(crate) struct CompiledPolicyData {
211    pub(crate) modules: Rc<Vec<Ref<Module>>>,
212    pub(crate) schedule: Option<Rc<Schedule>>,
213    pub(crate) rules: Map<String, Vec<Ref<Rule>>>,
214    pub(crate) default_rules: Map<String, Vec<DefaultRuleInfo>>,
215    pub(crate) imports: BTreeMap<String, Ref<Expr>>,
216    pub(crate) functions: FunctionTable,
217    pub(crate) rule_paths: Set<String>,
218    #[cfg(feature = "azure_policy")]
219    pub(crate) target_info: Option<TargetInfo>,
220    #[cfg(feature = "azure_policy")]
221    pub(crate) inferred_resource_types: Option<InferredResourceTypes>,
222
223    // User-defined rule to evaluate
224    pub(crate) rule_to_evaluate: Rc<str>,
225
226    // User-defined data
227    pub(crate) data: Option<Value>,
228
229    // Evaluation settings
230    pub(crate) strict_builtin_errors: bool,
231
232    // The semantics of extensions ought to be changes to be more Clone friendly.
233    pub(crate) extensions: Map<String, (u8, Rc<Box<dyn Extension>>)>,
234
235    // Pre-computed loop hoisting information
236    pub(crate) loop_hoisting_table: HoistedLoopsLookup,
237}