Declaring your infrastructure as code has all sorts of benefits, but it still generally depends on the code being correct and matching the intentions! Let’s take a look at a resource that we codified in an earlier post:
import * as aws from '@pulumi/aws'; | |
const defaultTags = { Creator: 'pulumi' }; | |
const timUser = new aws.iam.User('tim.myers', { | |
name: 'tim.myers', | |
tags: defaultTags, | |
}); |
This resource has a Creator
tag set on it. We set this so that if we happen to be inspecting the resource somewhere else, we have a means to tell that it was provisioned via code. Now imagine that we create another resource similar to the first one, but this is the code we end up writing:
const bobUser = new aws.iam.User('bob.bobby', { | |
name: 'bob.bobby', | |
tags: { Name: 'bob.bobby }, | |
}) |
We’ve specified a Name
tag on the resource. This may seem redundant, but many times in the AWS console, if this tag exists, it will be treated special in the UI and displayed, while a name
parameter for the resource might be displayed somewhere else, or not at all. Welcome to one of the joys of AWS! You might have noticed however, that in specifying this, we have forgotten the Creator
tag.
Enforcing resource tags with policy
Policy as Code is a means to encapsulate simple (or complex) rules, that the resources in our IaC must follow. We want to write a policy that says all IAM users must specify a Creator
tag so that we can avoid the coding error above. Pulumi has their own Policy as Code capability called CrossGuard, that allows us to specify these policies in a similar way to the infrastructure.
If you want to get started with using CrossGuard, the documentation is a great place to look. We’ll just take a peek at what a policy for our purposes might look like:
import * as aws from '@pulumi/aws'; | |
import { PolicyPack, ReportViolation, validateResourceOfType } from '@pulumi/policy'; | |
new PolicyPack('aws', { | |
policies: [ | |
{ | |
name: 'required-tags', | |
description: 'Certain tags are required.', | |
enforcementLevel: 'mandatory', | |
validateResource: [ | |
validateResourceOfType(aws.iam.User, (user, args, reportViolation) => { | |
requireCreatorTag(user.tags, reportViolation); | |
}), | |
], | |
}, | |
], | |
}); | |
function requireCreatorTag(tags: any, reportViolation: ReportViolation) { | |
if ((tags || {})['Creator'] === undefined) { | |
reportViolation(`A 'Creator' tag is required.`); | |
} | |
} |
This “policy pack” specifies a single policy to described the tags we require for specific AWS resources. We’ve succinctly written that all IAM User resources must have a Creator
tag. If we tried to run the IaC we wrote before with this policy enabled, it would have failed with an error since bobUser
would fail the mandatory validation before ever creating the resource. We would also get a helpful error message as to why thanks to the messages in our policy.
Once we have fixed the code, adding the requisite tag, the policy will find no violations, and our IaC will successfully be applied.
Well Behaved Infrastructure
Your definition of well behaved will obviously vary, and with policy defined in real programming languages, the sky is the limit as to what you can enforce. The most effective policies getting started will be simple ones like we defined here. You could also use policy as code to disallow certain EC2 instance types (like the really expensive ones), or enforce that only certain regions are ever provisioned into. It’s a great way to set up guardrails, and to ensure that your infrastructure always behaves the way you want it to.