Keep a rule's allowlist up to date; for example, rotating pre-approved AMIs monthly. Make the base rule easy to patch, create an ORL patch rule driven by a variable, then run a periodic workflow to apply updates.
Prerequisites
One-time setup for the maintenance loop.
Step 1: Patchable Rule
Start from the policy-as-code example and add this to the prompt so the generated rule is easy to update later:
**Important**: I will need to update the list of AMIs frequently, so make sure the rule is easy to update later.
Modified Rule
The modified rule matches the policy-as-code example, with the AMI list centralized in skip_finding for easier patching.
---type:Rulesetversion:v1metadata:name:"ensure-ami-is-in-approved-allowlist"display_name:"Ensure AMI is from the approved allowlist"description:| ## Description Ensure that EC2 instances, launch configurations, and launch templates use only approved AMIs. Resources referencing an AMI that is not in the allowlist will be flagged with an audit error but will not be automatically remediated.annotations:contributed-by:userprovider:AWSresource:aws_instance, aws_launch_configuration, aws_launch_templatespec:template:language:terraformaudit_language:astskip_finding:| let approved_amis = ["ami-0a1b2c3d4e5f67890", "ami-0987654321fedcba0", "ami-0ff11223344556677", "ami-0abcdef1234567890", "ami-0123456789abcdef0"]; let ami = trim($.value, "\""); !hasPrefix($.value, "\"") || ami in approved_amisremediation:-command:auditflags:level:ERRORvalue:"(PaC) The AMI is not in the approved allowlist. Valid AMIs are: ami-0a1b2c3d4e5f67890, ami-0987654321fedcba0, ami-0ff11223344556677, ami-0abcdef1234567890, ami-0123456789abcdef0"rules:-name:ensure-aws_instance-uses-approved-amiaudit:| {{ aResource("aws_instance", anAttribute("ami")) }}-name:ensure-aws_launch_template-uses-approved-amiaudit:| {{ aResource("aws_launch_template", anAttribute("image_id")) }}-name:ensure-aws_launch_configuration-uses-approved-amiaudit:| {{ aResource("aws_launch_configuration", anAttribute("image_id")) }}
Step 2: Generate the Patch
Generate a patch rule that updates the allowlist from a variable:
/gomboc:fixI have an existing rule `ensure-ami-is-in-approved-allowlist` that enforces an AMI allowlist. I want to create a new rule that can update that existing rule using the fact that `orl` is a supported language. The new rule should use the `vars.custom.ami_ids` variable as a comma-separated list of AMIs that should be used. This should replace the AMI list in `skip_finding` as an EXPR-style list (see existing rule format), as well as the remediation value as human-readable text.**Important**: The rule should skip itself (using `skip_expression`) if the variable is not set. If skipped the `skip_reason` should be in the form "Variable 'vars.custom.ami_ids' is not defined. It should be the complete list of valid AMIs".
This prompt is simplified for demo purposes. Depending on how you set up your update workflow, you may need to add additional protections like only patching rules by name or annotation.
Rule Output Example
Update Workflow
The update workflow consists of the steps that are run repeatedly when the underlying rule needs to be updated.
Step 1: Pull updated values
Filter to images tagged Approved=true. This command returns a comma-separated list of image IDs:
Step 2: Write the variable
Take the string from the first step and replace <ami list here> in the template below. This file should be placed in the same directory as the file you will be patching. We will use ./rules for this.
Step 3: Pull
Pull both rules from the Rules Service:
If you store the rules in the workflow repository instead, skip this step.
Step 4: Patch
Now we should have the rule to be patched and the variables in ./rules, and the patch in ./patches. To patch the rules, use the following prompt:
This tells ORL to patch rules in ./rules using rules in ./patches. Variables are automatically loaded from ./rules.
Step 5: Publish
With the rule updated, run the following prompt:
This publishes the patched rule to the Rules Service.
---
type: Ruleset
version: v1
metadata:
name: orl/ensure-ami-allowlist-from-custom-variable
display_name: "Sync AMI Allowlist in ORL Rules from vars.custom.ami_ids"
description: |
## Description
Updates an ORL rule's approved AMI allowlist to use `vars.custom.ami_ids`
(a comma-separated list of approved AMI IDs) instead of a hardcoded list.
Patches two locations in the target ORL rule file:
1. `spec.template.skip_finding` — rewrites the `let approved_amis = [...]` line
as an Expr list derived from vars.custom.ami_ids.
2. `spec.template.remediation[].value` — rewrites the human-readable AMI list
at the end of the audit message with the value of vars.custom.ami_ids.
The rule skips itself entirely when `vars.custom.ami_ids` is not defined.
annotations:
contributed-by: in-context
gomboc-ai/remediation-assessment: FULL_REMEDIATION
gomboc-ai/description-plain: "Replaces hardcoded AMI allowlists in ORL rules with the value from vars.custom.ami_ids."
priority: 100
spec:
template:
language: orl
audit_language: ast
skip_expression: "vars?.custom?.ami_ids == nil || vars.custom.ami_ids == \"\""
skip_reason: "Variable 'vars.custom.ami_ids' is not defined. It should be the complete list of valid AMIs"
rules:
- name: ensure-skip-finding-uses-ami-ids-from-variable
audit: |
(block_mapping_pair
key: (flow_node (plain_scalar (string_scalar) @_key))
value: (block_node (block_scalar) @skip_finding_val)
(#eq? @_key "skip_finding")
(#match? @skip_finding_val "let approved_amis")
)
remediation:
- command: replace
path: skip_finding_val
value: |-
|
let approved_amis = {{ "[\"" + join(map(split(vars.custom.ami_ids, ","), trim(#, " ")), "\", \"") + "\"]" }};
let ami = trim($.value, "\"");
!hasPrefix($.value, "\"") || ami in approved_amis
- name: ensure-remediation-value-uses-ami-ids-from-variable
audit: |
(block_mapping_pair
key: (flow_node (plain_scalar (string_scalar) @_key))
value: (flow_node (double_quote_scalar) @remediation_val)
(#eq? @_key "value")
(#match? @remediation_val "ami-[0-9a-f]")
)
remediation:
- command: replace
path: remediation_val
value: '"(PaC) The AMI is not in the approved allowlist. Valid AMIs are: {{ vars.custom.ami_ids }}"'