# rego-engine
Validates CloudFormation templates using Rego policies evaluated by the [Regorus](https://github.com/microsoft/regorus)
interpreter. Implements the [ValidationEngine](../validation-engine/README.md) trait. Custom builtins bridge Rego
policies to the [SemanticModel](../template-model/README.md) for deep template introspection that pure Rego cannot
express.
## Architecture
Rules are organized into Rego packages by category:
| `structure` | `data.structure.violation` | Template structure |
| `intrinsics` | `data.intrinsics.violation` | Intrinsic functions |
| `references` | `data.references.violation` | Cross-resource refs |
| `best_practices` | `data.best_practices.violation` | Best practices |
| `resources` | `data.resources.violation` | Resource-specific |
Rules come from four sources: handwritten Rego policies embedded by this crate's build script, generated shared data
embedded by data-source, user-provided custom Rego policies, and Guard DSL files. Guard files are not translated to
Rego: they are evaluated by the shared Guard evaluator in [validation-engine](../validation-engine/README.md), which
every engine calls, so the same Guard rule yields the same findings in every engine.
## Custom Builtins
These builtins are registered with the Regorus interpreter and called as bare functions in Rego policies - there is no
namespace prefix. For example, a policy calls `resolve(name, "Properties.BucketName")`, not `cfn.resolve(...)`.
### Template Resolution
| `resolve` | `(resource_id, path) → value` | Resolve a property value through intrinsic functions; a reference is rendered per the evaluating package, see below |
| `resolve_all` | `(resource_id, path) → [values]` | Resolve all scenario values for a property |
| `resolve_scenarios` | `(resource_id, path) → [{value, conditions}]` | Resolve all (value, condition_map) pairs |
| `resolve_ref_target` | `(resource_id, path) → {resourceType, condition, properties}` | Resolve the target of a reference |
| `resolve_type` | `(resource_id, path) → type_string` | Get the resolved value type |
| `is_dynamic` | `(resource_id, path) → bool` | Check if a property contains unresolvable content |
| `is_from_parameter` | `(resource_id, path) → bool` | Check if a property originates from a parameter |
| `is_from_intrinsic` | `(resource_id, path) → bool` | Check if a property originates from an intrinsic |
| `follow_ref` | `(resource_id, path) → target_id` | Follow a Ref/GetAtt to its target resource |
| `flatten_list` | `(resource_id, path) → [{value, index}]` | Flatten nested arrays |
#### Reference rendering
A `Ref`/`Fn::GetAtt` to a template resource has no literal before deployment, and `resolve` (and a reference inside a
list returned by `resolve_all`) renders it according to the package being evaluated:
- **Handwritten built-in policies** receive the `{"__ref": target}` marker object, the same shape the `input` document
uses. It is never a bare string, so a format or enum check that guards with `is_string` skips the reference instead of
judging a logical ID as if it were the value, while a presence check still sees a value.
- **Custom rules** receive the target's logical ID as a plain string, the contract they were written against; a custom
rule may look the target up in `input.resources` or compare it with another logical ID. Rules that validate literal
content should exclude a logical ID with `not input.resources[value]`, or read the reference explicitly with
`follow_ref` or `authored_form`.
The rendering is selected per package around each `eval_rule` query, so the two kinds evaluate on one engine instance
without observing each other's rendering. Guard rules are not affected: they never evaluate through Rego.
### Resource Queries
| `get_resource` | `(resource_id) → resource_object` | Get full resolved resource data |
| `has_property` | `(resource_id, path) → bool` | Check if a property is authored, even when its value is only known at deployment |
| `resources_of_type` | `(type_name) → [resource_ids]` | Get all logical IDs of a resource type |
| `has_transform` | `(transform_name) → bool` | Check if a transform is declared |
### Graph Queries
| `ref_targets` | `(resource_id) → [target_ids]` | Outgoing reference targets |
| `ref_sources` | `(resource_id) → [source_ids]` | Incoming reference sources |
| `depends_on` | `(resource_a, resource_b) → bool` | Transitive dependency check |
| `edges_from` | `(resource_id) → [edge_objects]` | Detailed outgoing edges |
| `edges_to` | `(resource_id) → [edge_objects]` | Detailed incoming edges |
| `pipeline_artifacts` | `(resource_id) → {issues: [{message}]}` | Extract CodePipeline artifact relationships |
### Condition Analysis
| `conditions_compatible` | `(resource_a, resource_b) → bool` | Can both resources' conditions be true simultaneously? |
| `condition_implies` | `(cond_a, cond_b) → bool` | Does cond_a=true force cond_b=true? |
| `conjunction_implies` | `(guard1, guard2, target) → bool` | Does the conjunction of two guards imply target? |
| `resource_condition` | `(resource_id) → condition_name` | Get the Condition on a resource |
| `is_satisfiable` | `(assumptions) → bool` | Is a set of condition assumptions satisfiable? |
| `unreachable_if_branches` | `(resource_id) → [{resourceId, path, message}]` | Find unreachable Fn::If branches in a resource |
### Parameter / Mapping
| `param_allowed_values` | `(param_name) → [values]` | Get AllowedValues for a parameter |
| `param_type` | `(param_name) → type_string` | Get the declared Type of a parameter |
| `mapping_value` | `(map, key1, key2) → value` | Look up a value in Mappings |
### Schema Introspection
| `schema_properties` | `(resource_type) → [property_names]` | List schema-defined properties |
| `schema_required` | `(resource_type) → [required_names]` | List required properties |
| `schema_type` | `(resource_type, property) → type_string` | Primary schema type (first declared when several) |
| `schema_enum` | `(resource_type, property) → [values]` | Allowed values (incl. case-insensitive enums) |
| `attribute_type` | `(resource_type, property) → type_string` | Get schema attribute type |
| `getatt_return_type` | `(resource_type, attribute) → type_string` | Get GetAtt return type |
| `schema_string_length` | `(resource_type, property) → {minLength?, maxLength?}` | Get string length constraints |
| `schema_requires_unique_items` | `(resource_type, property) → bool` | True when a property schema sets `uniqueItems` |
### Diagnostic Construction
| `make_diag` | `(rule_id, severity, resource_id, message) → diag` | Create a basic diagnostic |
| `make_diag_at` | `(rule_id, severity, resource_id, prop_path, message) → diag` | Diagnostic with property path |
| `make_diag_full` | `(rule_id, severity, resource_id, prop_path, message, fix, doc_url) → diag` | Full diagnostic with fix and URL |
| `make_diag_related` | `(rule_id, severity, resource_id, prop_path, message, related_locations) → diag` | Diagnostic with related locations |
| `make_diag_conditional` | `(rule_id, severity, resource_id, prop_path, message, conditions) → diag` | Diagnostic with condition context |
### Utilities
| `arn_matches` | `(arn, pattern) → bool` | Match an ARN against a pattern with wildcards |
| `ip_overlaps` | `(cidr_a, cidr_b) → bool` | Check if two CIDR blocks overlap |
| `ip_subnet_of` | `(subnet, supernet) → bool` | Check if a CIDR is a subnet of another |
| `is_valid_cidr_strict` | `(cidr) → bool` | Validate CIDR notation |
| `ensure_list` | `(value) → [value]` | Wrap scalar in array, pass arrays through |
| `input_region` | `() → region_string` | Get the configured AWS region |
| `region_flat_invalid` | `(region_map, value) → message` | Diagnostic message when a flat instance/node-type value is invalid for the effective scope (configured region, or union of all regions when unset), else undefined |
| `region_conditional_invalid` | `(region_map, target_prop, normalize_engine_case, value, props) → message` | Diagnostic message when a conditional RDS instance-class value is invalid for the effective scope, else undefined |
| `coerce_to_number` | `(value) → number` | CloudFormation-style number coercion |
| `coerce_to_string` | `(value) → string` | CloudFormation-style string coercion |
| `cfn_type_compatible` | `(value, expected_type) → bool` | Check CFN type compatibility with coercion |
| `estimated_string_length_bounds` | `(resource_id, path) → {shortest, longest}` | Shortest and longest resolved string length; undefined when the length cannot be pinned for every possibility |