Assigning and referencing variables in Guard rules
You can assign variables in your Amazon CloudFormation Guard rules files to store information that you want to reference in your Guard rules. Guard supports one-shot variable assignment. Variables are evaluated lazily, meaning that Guard only evaluates variables when rules are run.
Topics
Assigning variables
Use the let keyword to initialize and assign a variable. As a best
practice, use snake case for variable names. Variables can store static literals or
dynamic properties resulting from queries. In the following example, the variable
ecs_task_definition_task_role_arn stores the static string value
arn:aws:iam:123456789012:role/my-role-name.
let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-role-name'
In the following example, the variable ecs_tasks stores the results of a
query that searches for all AWS::ECS::TaskDefinition resources in an Amazon CloudFormation
template. You could reference ecs_tasks to access information about those
resources when you write rules.
let ecs_tasks = Resources.*[ Type == 'AWS::ECS::TaskDefinition' ]
Referencing variables
Use the % prefix to reference a variable.
Based on the ecs_task_definition_task_role_arn variable example in Assigning variables, you can
reference ecs_task_definition_task_role_arn in the query|value
literal section of a Guard rule clause. Using that reference ensures
that the value specified for the TaskDefinitionArn property of any
AWS::ECS::TaskDefinition resources in a CloudFormation template is the
static string value arn:aws:iam:123456789012:role/my-role-name.
Resources.*.Properties.TaskDefinitionArn == %ecs_task_definition_role_arn
Based on the ecs_tasks variable example in Assigning variables, you can
reference ecs_tasks in a query (for example, %ecs_tasks.Properties). First,
Guard evaluates the variable ecs_tasks and then uses the returned
values to traverse the hierarchy. If the variable ecs_tasks resolves to
non-string values, then Guard throws an error.
Note
Currently, Guard doesn't support referencing variables inside custom error messages.
Variable scope
Scope refers to the visibility of variables defined in a rules file. A variable name can only be used once within a scope. There are three levels where a variable can be declared, or three possible variable scopes:
-
File-level – Usually declared at the top of the rules file, you can use file-level variables in all rules within the rules file. They are visible to the entire file.
In the following example rules file, the variables
ecs_task_definition_task_role_arnandecs_task_definition_execution_role_arnare initialized at the file-level.let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-task-role-name' let ecs_task_definition_execution_role_arn = 'arn:aws:iam::123456789012:role/my-execution-role-name' rule check_ecs_task_definition_task_role_arn { Resources.*.Properties.TaskRoleArn == %ecs_task_definition_task_role_arn } rule check_ecs_task_definition_execution_role_arn { Resources.*.Properties.ExecutionRoleArn == %ecs_task_definition_execution_role_arn } -
Rule-level – Declared within a rule, rule-level variables are only visible to that specific rule. Any references outside of the rule result in an error.
In the following example rules file, the variables
ecs_task_definition_task_role_arnandecs_task_definition_execution_role_arnare initialized at the rule-level. Theecs_task_definition_task_role_arncan only be referenced within thecheck_ecs_task_definition_task_role_arnnamed rule. You can only reference theecs_task_definition_execution_role_arnvariable within thecheck_ecs_task_definition_execution_role_arnnamed rule.rule check_ecs_task_definition_task_role_arn { let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-task-role-name' Resources.*.Properties.TaskRoleArn == %ecs_task_definition_task_role_arn } rule check_ecs_task_definition_execution_role_arn { let ecs_task_definition_execution_role_arn = 'arn:aws:iam::123456789012:role/my-execution-role-name' Resources.*.Properties.ExecutionRoleArn == %ecs_task_definition_execution_role_arn } -
Block-level – Declared within a block, such as a
whenclause, block-level variables are only visible to that specific block. Any references outside of the block result in an error.In the following example rules file, the variables
ecs_task_definition_task_role_arnandecs_task_definition_execution_role_arnare initialized at the block-level within theAWS::ECS::TaskDefinitiontype block. You can only reference theecs_task_definition_task_role_arnandecs_task_definition_execution_role_arnvariables within theAWS::ECS::TaskDefinitiontype blocks for their respective rules.rule check_ecs_task_definition_task_role_arn { AWS::ECS::TaskDefinition { let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-task-role-name' Properties.TaskRoleArn == %ecs_task_definition_task_role_arn } } rule check_ecs_task_definition_execution_role_arn { AWS::ECS::TaskDefinition { let ecs_task_definition_execution_role_arn = 'arn:aws:iam::123456789012:role/my-execution-role-name' Properties.ExecutionRoleArn == %ecs_task_definition_execution_role_arn } }
Examples of variables in Guard rules files
The following sections provide examples of both static and dynamic assignment of variables.
Static assignment
The following is an example CloudFormation template.
Resources: EcsTask: Type: 'AWS::ECS::TaskDefinition' Properties: TaskRoleArn: 'arn:aws:iam::123456789012:role/my-role-name'
Based on this template, you can write a rule called
check_ecs_task_definition_task_role_arn that ensures that the
TaskRoleArn property of all AWS::ECS::TaskDefinition
template resources is
arn:aws:iam::123456789012:role/my-role-name.
rule check_ecs_task_definition_task_role_arn { let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-role-name' Resources.*.Properties.TaskRoleArn == %ecs_task_definition_task_role_arn }
Within the scope of the rule, you can initialize a variable called
ecs_task_definition_task_role_arn and assign to it the static
string value 'arn:aws:iam::123456789012:role/my-role-name'. The rule
clause checks whether the value specified for the TaskRoleArn property
of the EcsTask resource is
arn:aws:iam::123456789012:role/my-role-name by referencing the
ecs_task_definition_task_role_arn variable in the query|value
literal section.
Dynamic assignment
The following is an example CloudFormation template.
Resources: EcsTask: Type: 'AWS::ECS::TaskDefinition' Properties: TaskRoleArn: 'arn:aws:iam::123456789012:role/my-role-name'
Based on this template, you can initialize a variable called
ecs_tasks within the scope of the file and assign to it the query
Resources.*[ Type == 'AWS::ECS::TaskDefinition'. Guard
queries all resources in the input template and stores information about them in
ecs_tasks. You can also write a rule called
check_ecs_task_definition_task_role_arn that ensures that the
TaskRoleArn property of all AWS::ECS::TaskDefinition
template resources is
arn:aws:iam::123456789012:role/my-role-name
let ecs_tasks = Resources.*[ Type == 'AWS::ECS::TaskDefinition' ] rule check_ecs_task_definition_task_role_arn { %ecs_tasks.Properties.TaskRoleArn == 'arn:aws:iam::123456789012:role/my-role-name' }
The rule clause checks whether the value specified for the
TaskRoleArn property of the EcsTask resource is
arn:aws:iam::123456789012:role/my-role-name by referencing the
ecs_task_definition_task_role_arn variable in the
query section.
Enforcing Amazon CloudFormation template configuration
Let’s walk through a more complex example of a production use case. In this example, we write Guard rules to ensure stricter controls on how Amazon ECS tasks are defined.
The following is an example CloudFormation template.
Resources: EcsTask: Type: 'AWS::ECS::TaskDefinition' Properties: TaskRoleArn: 'Fn::GetAtt': [TaskIamRole, Arn] ExecutionRoleArn: 'Fn::GetAtt': [ExecutionIamRole, Arn] TaskIamRole: Type: 'AWS::IAM::Role' Properties: PermissionsBoundary: 'arn:aws:iam::123456789012:policy/MyExamplePolicy' ExecutionIamRole: Type: 'AWS::IAM::Role' Properties: PermissionsBoundary: 'arn:aws:iam::123456789012:policy/MyExamplePolicy'
Based on this template, we write the following rules to ensure that these requirements are met:
-
Each
AWS::ECS::TaskDefinitionresource in the template has both a task role and an execution role attached. -
The task roles and execution roles are Amazon Identity and Access Management (IAM) roles.
-
The roles are defined in the template.
-
The
PermissionsBoundaryproperty is specified for each role.
# Select all Amazon ECS task definition resources from the template let ecs_tasks = Resources.*[ Type == 'AWS::ECS::TaskDefinition' ] # Select a subset of task definitions whose specified value for the TaskRoleArn property is an Fn::Gett-retrievable attribute let task_role_refs = some %ecs_tasks.Properties.TaskRoleArn.'Fn::GetAtt'[0] # Select a subset of TaskDefinitions whose specified value for the ExecutionRoleArn property is an Fn::Gett-retrievable attribute let execution_role_refs = some %ecs_tasks.Properties.ExecutionRoleArn.'Fn::GetAtt'[0] # Verify requirement #1 rule all_ecs_tasks_must_have_task_end_execution_roles when %ecs_tasks !empty { %ecs_tasks.Properties { TaskRoleArn exists ExecutionRoleArn exists } } # Verify requirements #2 and #3 rule all_roles_are_local_and_type_IAM when all_ecs_tasks_must_have_task_end_execution_roles { let task_iam_references = Resources.%task_role_refs let execution_iam_reference = Resources.%execution_role_refs when %task_iam_references !empty { %task_iam_references.Type == 'AWS::IAM::Role' } when %execution_iam_reference !empty { %execution_iam_reference.Type == 'AWS::IAM::Role' } } # Verify requirement #4 rule check_role_have_permissions_boundary when all_ecs_tasks_must_have_task_end_execution_roles { let task_iam_references = Resources.%task_role_refs let execution_iam_reference = Resources.%execution_role_refs when %task_iam_references !empty { %task_iam_references.Properties.PermissionsBoundary exists } when %execution_iam_reference !empty { %execution_iam_reference.Properties.PermissionsBoundary exists } }