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