Tagging Helpers
To help with complex tagging use cases, Guardrails offers additional functionality to assist in writing calculated policies for tagging. Collectively, these improvements are known as "Tagging Helpers".
createdBy and createTimestamp
A common tagging requirement is to tag a resource with the identity of the creator and the time it was created. Here is an example graphql query for the resource metadata (which includes the creation information).
{ resource { metadata }}
Query results:
metadata: aws: accountId: 012345678912 partition: aws region: us-east-1 createTimestamp: 2023-01-28T05:31:46.000Z createdBy: "arn:aws:sts::013122550996:user/dwight"
createdBy data source
Guardrails populates the createdBy attribute based on the identity found in the creation event. For resources that exist prior to Guardrails discovering them, createdBy will be set to null. The format for the createdBy value varies by cloud provider:
AWS
- userIdentity.arn: The full ARN of the identity that created the resource.
Azure
- initiatedBy.userPrincipalName (Active Directory resources)
- caller (Azure resources)
GCP
- authenticationInfo.principalEmail
Example of how to use createdBy and createTimestamp
This example shows a generic template_input
and template
that will work for any AWS, Azure or GCP resource type.
template_input:
{ resource { metadata turbot{ tags } }}
{# As createdBy and createTimestamp can be null, it's important to test that they are available. -#}{% if $.resource.metadata.createdBy %}- "creator": "{{ $.resource.metadata.createdBy }}"{% endif %}{% if $.resource.metadata.createTimestamp %}- "createTimestamp": "{{ $.resource.metadata.createTimestamp }}"{% endif %}
Tag Maps with setAttribute
In more complex tagging scenarios, accumulating tag key:value pairs into a map can assist in writing clean readable code. Nunjucks does not natively supply a way to create mutable dictionaries. To overcome this limitation, the setAttribute()
function can help. The function accepts a dictionary object and two string parameters.
Template
{% set currentTags = {"Key1":"Value1"} -%}{% set currentTags = setAttribute(currentTags, "Key2", "ValueTwo") %}{% set currentTags = setAttribute(currentTags, "Key1", "ValueOne") %}{% for key,value in currentTags -%}- "{{key}}": "{{value}}"{% endfor -%}
Output With output of:
- "Key1": "ValueOne"- "Key2": "ValueTwo"
Note: Appending to arrays is not supported by this function but can be accomplished in other ways.
Rectify and cleanup bad keys and values
Fixing incorrect keys and values is the most common tagging use case. The transformMap
function can be used in calculated policies to easily process rules across all tags on any given resource.
transformMap(tags_map, rules) -> transformed_map
Transforms tags_map
based on rules
and returns transformed_map
; the tags_map
paramater and the transformed_map
return value are simple arrays of key:value
pairs e.g.:
{ "foo": "bar", "fizz": "buzz", "crop": "beets"}
or
- foo: bar,- fizz: buzz,- crop: beets
The rules
object must conform to a specific schema which will be outlined in a series of examples. All examples will use the following starting tags_map
(the current tags on the resource):
- Env: prd,- CostCenter: scranton-1138,- owner: dwight
Example: Create remediated tags while preserving existing tags
Rules
environment: incorrectKeys: - Env
Transformed Tags
- "CostCenter": "scranton-1138"- "Env": "prd"- "environment": "prd"- "owner": "dwight"
Example: Replace an incorrect tag key.
Rules
environment: incorrectKeys: - Env replacementValue: undefinedcost_center: incorrectKeys: - CostCenter replacementValue: undefined
Reminder: "undefined" is a reserved value in Guardrails to indicate a tag that should be removed!
Transformed Tags
- "cost_center": "scranton-1138"- "environment": "prd"- "owner": "dwight"
Example: Replace an incorrect tag value.
Rules
owner: values: dwight.schrute@dmi.com: incorrectValues: - dwight - dks - dwight.schrute pam.beasly@dmi.com: incorrectValues: - Beasly - pam
Transformed Tags
- "CostCenter": "scranton-1138"- "Env": "prd"- "owner": "dwight.schrute@dmi.com"
Example: Combine value and key replacement.
Rules
environment: incorrectKeys: - Env replacementValue: undefined values: production: incorrectValues: - prod - prd - PROD development: incorrectValues: - dev - DEVcost_center: incorrectKeys: - CostCenter replacementValue: undefined values: SCR1138: incorrectValues: - scranton-1138 NSH1234: incorrectValues: - nashua-1234owner: values: dwight.schrute@dmi.com: incorrectValues: - dwight - dks - dwight.schrute pam.beasly@dmi.com: incorrectValues: - Beasly - pam
Transformed Tags
- "cost_center": "SCR1138"- "environment": "production"- "owner": "dwight.schrute@dmi.com"
Example: Using regex matches
Regular expressions can be used in incorrectValues
and incorrectKeys
are identified by this regex:
^/((?:\\/|[^/])*)/([dgimsuy]*)$
Malformed regexes are treated as string literals.
Rules
environment: incorrectKeys: - /env.*/gi replacementValue: undefined values: production: incorrectValues: - /pr.*/gi development: incorrectValues: - /dev.*/gicost_center: incorrectKeys: - /cost.*cent.*/gi replacementValue: undefined values: SCR1138: incorrectValues: - /.*1138.*/ NSH1234: incorrectValues: - /.*1234.*/
Transformed Tags
- "cost_center": "SCR1138"- "environment": "production"- "owner": "dwight"
Edge Cases
- If there are multiple matches for an incorrect value then the first alphabetical match for the correct key will be used.
- Malformed regexes are treated as string literals.
- If there is no match for the incorrectKeys or in incorrectValues, the output map will match the input.
- Multiple matches for an incorrect value will result in the the first alphabetical match being used.
Rules Schema
Structure of the Rules YAML
The transformMap function expects a JSON object with the following structure:
{ "key1": { "incorrectKeys": [ "badkey1a", "badkey1b" ], "replacementValue": "newKey1", "values": { "value1": { "incorrectValues": [ "badValue1a", "badValue1b" ] } } }, "key2": { "incorrectKeys": [ "badkey2a", "badkey2b" ], "replacementValue": "newKey2", "values": { "value2": { "incorrectValues": [ "badValue2a", "badValue2b" ] } } }}
The rules object can be stored in Guardrails using the policy Turbot > Tags > Transform Rules
. When setting this policy via the Guardrails console yaml format can be used in addition to json:
key1: incorrectKeys: - badkey1a - badkey1b replacementValue: newKey1 values: value1: incorrectValues: - badValue1a - badValue1bkey2: incorrectKeys: - badkey2a - badkey2b replacementValue: newKey2 values: value2: incorrectValues: - badValue2a - badValue2b
Storing rules in a Guardrails File
The recommended way of managing transform rules as code, is to store them in a Guardrails File
. Here is an example terraform template for creating a Guardrails file.
resource "turbot_file" "tag_rules" { parent = "tmod:@turbot/turbot#/" title = "Tag Transform Rules" akas = ["tag_rules"] content = <<-EOT { "key1": { "incorrectKeys": [ "badkey1a", "badkey1b" ], "replacementValue": "newKey1", "values": { "value1": { "incorrectValues": [ "badValue1a", "badValue1b" ] } } }, "key2": { "incorrectKeys": [ "badkey2a", "badkey2b" ], "replacementValue": "newKey2", "values": { "value2": { "incorrectValues": [ "badValue2a", "badValue2b" ] } } } } EOT}
Guardrails Files only accept JSON, but YAML can still be used for the rules here by using the built-in YAML and JSON encode and decode functions in Terraform:
locals { yaml_string = <<-EOT key1: incorrectKeys: - badkey1a - badkey1b replacementValue: newKey1 values: value1: incorrectValues: - badValue1a - badValue1b key2: incorrectKeys: - badkey2a - badkey2b replacementValue: newKey2 values: value2: incorrectValues: - badValue2a - badValue2b EOT}
resource "turbot_file" "tag_rules" { parent = "tmod:@turbot/turbot#/" title = "Tag Transform Rules" akas = ["tag_rules"] content = jsonencode(yamldecode(local.yaml_string))}
Example Calculated Policy
The example creates a Policy Pack, sets the AWS > S3 > Bucket > Tags
guardrail to Enforce: Set tags
, and creates our calculated policy that reads the rules from the Guardrails File specified in the previous section.
resource "turbot_policy_pack" "tag_transform_example" { parent = "tmod:@turbot/turbot#/" title = "Tagging Transformation Example"}
resource "turbot_policy_setting" "aws_s3_bucket_tags" { resource = turbot_policy_pack.tag_transform_example.id type = "tmod:@turbot/aws-s3#/policy/types/bucketTags" value = "Enforce: Set tags"}
resource "turbot_policy_setting" "aws_s3_bucket_tags_template" { resource = turbot_policy_pack.tag_transform_example.id type = "tmod:@turbot/aws-s3#/policy/types/bucketTagsTemplate" template_input = <<-EOT { rules: resource(id:"tag_rules") { data } resource { turbot { tags } } } EOT template = <<-EOT {%- set tags_map = $.resource.turbot.tags -%} {%- set rules = $.rules.data -%} {% for key,value in transformMap(tags_map, rules) -%} - "{{key}}": "{{value}}" {% endfor -%} EOT}