Get started with Pulumi policy as code
Pulumi CrossGuard is a product that provides gated deployments via Policy as Code.
Often organizations want to empower developers to manage their infrastructure yet are concerned about giving them full access. CrossGuard allows administrators to provide autonomy to their developers while ensuring compliance to defined organization policies.
Using Policy as Code, users can express business or security rules as functions that are executed against resources in their stacks. Then using CrossGuard, organization administrators can apply these rules to particular stacks within their organization. When policies are executed as part of your Pulumi deployments, any violation will gate or block that update from proceeding.
Policies can be written in TypeScript/JavaScript (Node.js) or Python and can be applied to Pulumi stacks written in any language. Learn more about language support for policies.
Terminology
- Policy Pack - a set of related policies - i.e. “Security”, “Cost Optimization”, “Data Location”. The categorization of policies into a policy pack is left up to the user.
- Policy - an individual policy - i.e. “prohibit use of instances larger than t3.medium”.
- Enforcement Level - the impact of a policy violation - i.e. “mandatory” or “advisory”.
Learn more about Policy as Code core concepts.
Creating a Policy Pack
Let’s start with authoring your first Policy Pack.
Policies can be written in TypeScript/JavaScript (Node.js) or Python and can be applied to Pulumi stacks written in any language. More information on language support for policies.
Install prerequisites.
Create a directory for your new Policy Pack, and change into it.
$ mkdir policypack && cd policypack
Run the
pulumi policy new
command.$ pulumi policy new aws-typescript
Tweak the Policy Pack in the
index.ts
file as desired. The existing policy in the template (which is annotated below) mandates that an AWS S3 bucket not have public read or write permissions enabled.Each Policy must have a unique name, description, and validation function. Here we use the
validateResourceOfType
helper so that our validation function is only called for AWS S3 bucket resources. An enforcement level can be set on the Policy Pack (applies to all policies) and/or on each individual policy (overriding any Policy Pack value).// Create a new Policy Pack. new PolicyPack("policy-pack-typescript", { // Specify the Policies in the Policy Pack. policies: [{ // The name for the Policy must be unique within the Pack. name: "s3-no-public-read", // The description should document what the Policy does and why it exists. description: "Prohibits setting the publicRead or publicReadWrite permission on AWS S3 buckets.", // The enforcement level can be "advisory", "mandatory", or "disabled". An "advisory" enforcement level // simply prints a warning for users, while a "mandatory" policy will block an update from proceeding, and // "disabled" disables the policy from running. enforcementLevel: "mandatory", // The validateResourceOfType function allows you to filter resources. In this case, the rule only // applies to S3 buckets and reports a violation if the acl is "public-read" or "public-read-write". validateResource: validateResourceOfType(aws.s3.Bucket, (bucket, args, reportViolation) => { if (bucket.acl === "public-read" || bucket.acl === "public-read-write") { reportViolation( "You cannot set public-read or public-read-write on an S3 bucket. " + "Read more about ACLs here: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html"); } }), }], });
Install prerequisites.
Create a directory for your new Policy Pack, and change into it.
$ mkdir policypack && cd policypack
Run the
pulumi policy new
command.$ pulumi policy new aws-python
Tweak the Policy Pack in the
__main__.py
file as desired. The existing policy in the template (which is annotated below) mandates that an AWS S3 bucket not have public read or write permissions enabled.Each Policy must have a unique name, description, and validation function. An enforcement level can be set on the Policy Pack (applies to all policies) and/or on each individual policy (overriding any Policy Pack value).
# The validation function is called before each resource is created or updated. # In this case, the rule only applies to S3 buckets and reports a violation if the # acle is "public-read" or "public-read-write". def s3_no_public_read_validator(args: ResourceValidationArgs, report_violation: ReportViolation): if args.resource_type == "aws:s3/bucket:Bucket" and "acl" in args.props: acl = args.props["acl"] if acl == "public-read" or acl == "public-read-write": report_violation( "You cannot set public-read or public-read-write on an S3 bucket. " + "Read more about ACLs here: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html") s3_no_public_read = ResourceValidationPolicy( # The name for the Policy must be unique within the Pack. name="s3-no-public-read", # The description should document what the Policy does and why it exists. description="Prohibits setting the publicRead or publicReadWrite permission on AWS S3 buckets.", # The enforcement level can be ADVISORY, MANDATORY, or DISABLED. An ADVISORY enforcement level # simply prints a warning for users, while a MANDATORY policy will block an update from proceeding, and # DISABLED disables the policy from running. enforcement_level=EnforcementLevel.MANDATORY, # The validation function, defined above. validate=s3_no_public_read_validator, ) # Create a new Policy Pack. PolicyPack( name="policy-pack-python", # Specify the Policies in the Policy Pack. policies=[ s3_no_public_read, ], )
You can find more example Policy Packs in the examples repo. Policy Pack best practices details the best practices for writing a Policy Pack.
Running Locally
Now let’s take a look at how to run the Policy Pack locally against a Pulumi program.
Use the
--policy-pack
flag withpulumi preview
orpulumi up
to specify the path to the directory containing your Policy Pack when previewing/updating a Pulumi program.If you don’t have a Pulumi program readily available, you can create a new program for testing by running
pulumi new aws-typescript
in an empty directory. This AWS example will create an S3 bucket, which is perfect for testing our Policy.$ mkdir test-program && cd test-program $ pulumi new aws-typescript
In the Pulumi program’s directory run:
$ pulumi preview --policy-pack <path-to-policy-pack-directory>
If the Pulumi stack is in compliance, we expect the output to tell us which Policy Packs were run.
Previewing update (dev): Type Name Plan + pulumi:pulumi:Stack test-dev create + └─ aws:s3:Bucket my-bucket create Resources: + 2 to create Policy Packs run: Name Version aws-typescript (/Users/user/path/to/policy-pack) (local)
We can then edit the stack code to specify the ACL to be public-read.
const bucket = new aws.s3.Bucket("my-bucket", { acl: "public-read", });
We then run the
pulumi preview
command again and this time get an error message indicating we failed the preview because of a policy violation.Previewing update (dev): Type Name Plan Info + pulumi:pulumi:Stack test-dev create 1 error + └─ aws:s3:Bucket my-bucket create Diagnostics: pulumi:pulumi:Stack (test-dev): error: preview failed Policy Violations: [mandatory] aws-typescript v0.0.1 s3-no-public-read (my-bucket: aws:s3/bucket:Bucket) Prohibits setting the publicRead or publicReadWrite permission on AWS S3 buckets. You cannot set public-read or public-read-write on an S3 bucket. Read more about ACLs here: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html
Use the
--policy-pack
flag withpulumi preview
orpulumi up
to specify the path to the directory containing your Policy Pack when previewing/updating a Pulumi program.If you don’t have a Pulumi program readily available, you can create a new program for testing by running
pulumi new aws-python
in an empty directory. This AWS example will create an S3 bucket, which is perfect for testing our Policy.$ mkdir test-program && cd test-program $ pulumi new aws-python
In the Pulumi program’s directory, run:
$ pulumi preview --policy-pack <path-to-policy-pack-directory>
If the Pulumi stack is in compliance, we expect the output to tell us which Policy Packs were run.
Previewing update (dev): Type Name Plan + pulumi:pulumi:Stack test-dev create + └─ aws:s3:Bucket my-bucket create Resources: + 2 to create Policy Packs run: Name Version aws-python (/Users/user/path/to/policy-pack) (local)
We can then edit the stack code to specify the ACL to be public-read.
bucket = s3.Bucket('my-bucket', acl="public-read")
We then run the
pulumi preview
command again and this time get an error message indicating we failed the preview because of a policy violation.Previewing update (dev): Type Name Plan Info + pulumi:pulumi:Stack test-dev create 1 error + └─ aws:s3:Bucket my-bucket create Diagnostics: pulumi:pulumi:Stack (test-dev): error: preview failed Policy Violations: [mandatory] aws-python v0.0.1 s3-no-public-read (my-bucket: aws:s3/bucket:Bucket) Prohibits setting the publicRead or publicReadWrite permission on AWS S3 buckets. You cannot set public-read or public-read-write on an S3 bucket. Read more about ACLs here: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html
Now that your Policy Pack is ready to go, let’s enforce the pack across your organization.
Enforcing a Policy Pack
Once you’ve validated the behavior of your policies, an organization administrator can publish them to the Pulumi Cloud to be enforced across your organization. Any Pulumi client (a developer’s workstation, CI/CD tool, etc) that interacts with a stack via the Pulumi Cloud will have policy enforcement during the execution of preview
and update
. Policy Packs are versioned by the Pulumi Cloud so that updated policies can be published and applied as ready and also reverted to previous versions as needed.
From within the Policy Pack directory, run the following command to publish your pack:
$ pulumi policy publish <org-name>
The output will tell you what version of the Policy Pack you just published. The Pulumi Cloud provides a monotonic version number for Policy Packs.
Obtaining policy metadata from policy plugin Compressing policy pack Uploading policy pack to Pulumi Cloud Publishing my-policy-pack to myorg Published as version 1.0.0
The Policy Pack version is specified in the
package.json
file for TypeScript/JavaScript (Node.js) packs and in thePulumiPolicy.yaml
file for Python packs. A version can only be used one time and once published the version can never be used by that Policy Pack again.You can enable this Policy Pack to your organization’s default Policy Group by running:
$ pulumi policy enable <org-name>/<policy-pack-name> <latest|version>
For example, to enable the Policy Pack created in the previous step:
$ pulumi policy enable myorg/my-policy-pack latest
The CLI by default enables the Policy Pack to your default Policy Group. If you would like to add the Policy Pack to a different Policy Group, you can use the
--policy-group
flag.
Next Steps
Now that you have published your first Policy Pack, you now have all the tools needed to enforce compliance amongst your organization. For more example Policy Packs, you can check out the examples repo. You can also find more documentation in the CrossGuard guide.
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.