Pulumi policy as code concepts
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.
Policy
A Policy contains specific logic you would like to enforce. For example, you may want to prevent the creation of public, world-readable storage objects. (e.g. on AWS S3, Azure BlobStore, etc.) or prevent the creation of a virtual machine without the proper security groups in-place.
Policies are written as validation functions that are evaluated against all resources in your Pulumi stack. If the validation function calls reportViolation
reportViolation
report_violation
reportViolation
reportViolation
report_violation
Policies validation functions are executed during pulumi preview
and pulumi up
, asserting that cloud resource definitions comply with the policy immediately before they are created or updated.
During preview, every resource is checked for policy violations, which are batched up into a final report. During the update, the first policy violation will halt the deployment.
A policy can have an optional enforcement level of "advisory"
"advisory"
EnforcementLevel.ADVISORY
"mandatory"
"mandatory"
EnforcementLevel.MANDATORY
"disabled"
"disabled"
EnforcementLevel.DISABLED
prints a warning message when there is a violation."advisory"
"advisory"
EnforcementLevel.ADVISORY
means that an update will halt before creating a resource that violates that policy."mandatory"
"mandatory"
EnforcementLevel.MANDATORY
prevents the policy from running."disabled"
"disabled"
EnforcementLevel.DISABLED
The enforcement level can be set on the Policy Pack, which will apply to all policies in the Policy Pack, unless specified on a policy directly, which will override the value for that policy. If no enforcement level is specifed on either the policy pack or policy, the default of "advisory"
"advisory"
EnforcementLevel.ADVISORY
There are two types of policies:
ResourceValidationPolicy
validates a particular resource in a stack before the resource is created or updated, looking at the resource’s input properties.StackValidationPolicy
validates all resources in the stack after they’ve been created/updated, but before the Pulumi preview/update has completed, looking at each resource’s output properties.
Resource Validation
Policies of ResourceValidationPolicy
validate against a particular resource in a stack. These policies are run before a resource is registered and thus block an out-of-compliance resource from ever being created.
A resource validation is passed args
with more information about the resource and a reportViolation
reportViolation
report_violation
validateResourceOfType
to filter the resource type you want to validate.
An example resource validation policy is shown below:
const s3NoPublicRead: ResourceValidationPolicy = {
name: "s3-no-public-read",
description: "Prohibits setting the publicRead or publicReadWrite permission on AWS S3 buckets.",
enforcementLevel: "mandatory",
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.");
}
}),
};
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.")
s3_no_public_read = ResourceValidationPolicy(
name="s3-no-public-read",
description="Prohibits setting the publicRead or publicReadWrite permission on AWS S3 buckets.",
enforcement_level=EnforcementLevel.MANDATORY,
validate=s3_no_public_read_validator,
)
If you have multiple resources that require a similar policy, you can group them together under one policy. This is possible by setting validateResource
validateResource
validate
ResourceValidationPolicy
. The example below shows a case where we want to run similar checks against multiple types of resources.
const elbLoggingEnabled: ResourceValidationPolicy = {
name: "elb-logging-enabled",
description: "Checks whether the Application Load Balancers and the Classic Load Balancers have logging enabled.",
enforcementLevel: "mandatory",
validateResource: [
validateResourceOfType(aws.elasticloadbalancing.LoadBalancer, (loadBalancer, args, reportViolation) => {
if (!loadBalancer.accessLogs || !loadBalancer.accessLogs.enabled) {
reportViolation("Elastic Load Balancer must have access logs enabled.");
}
}),
validateResourceOfType(aws.elasticloadbalancingv2.LoadBalancer, (loadBalancer, args, reportViolation) => {
if (!loadBalancer.accessLogs || !loadBalancer.accessLogs.enabled) {
reportViolation("Elastic Load Balancer must have access logs enabled.");
}
}),
validateResourceOfType(aws.applicationloadbalancing.LoadBalancer, (loadBalancer, args, reportViolation) => {
if (!loadBalancer.accessLogs || !loadBalancer.accessLogs.enabled) {
reportViolation("Application Load Balancer must have access logs enabled.");
}
}),
],
};
def elb_logging_enabled_elb_validator(args: ResourceValidationArgs, report_violation: ReportViolation):
if args.resource_type == "aws:elasticloadbalancing/loadBalancer:LoadBalancer":
if "accessLogs" not in args.props or not args.props["accessLogs"]["enabled"]:
report_violation("Elastic Load Balancer must have access logs enabled.")
def elb_logging_enabled_elb2_validator(args: ResourceValidationArgs, report_violation: ReportViolation):
if args.resource_type == "aws:elasticloadbalancingv2/loadBalancer:LoadBalancer":
if "accessLogs" not in args.props or not args.props["accessLogs"]["enabled"]:
report_violation("Elastic Load Balancer must have access logs enabled.")
def elb_logging_enabled_alb_validator(args: ResourceValidationArgs, report_violation: ReportViolation):
if args.resource_type == "aws:applicationloadbalancing/loadBalancer:LoadBalancer":
if "accessLogs" not in args.props or not args.props["accessLogs"]["enabled"]:
report_violation("Application Load Balancer must have access logs enabled.")
elb_logging_enabled = ResourceValidationPolicy(
name="elb-logging-enabled",
description="Checks whether the Application Load Balancers and the Classic Load Balancers have logging enabled.",
enforcement_level=EnforcementLevel.MANDATORY,
validate=[
elb_logging_enabled_elb_validator,
elb_logging_enabled_elb2_validator,
elb_logging_enabled_alb_validator,
],
)
Stack Validation Policy
Policies of StackValidationPolicy
are run against all the resources in a stack. These policies are run after all stack resources are registered and thus do not block an out-of-compliance resource from being created, but do fail the preview or update. To avoid creating out-of-compliance resources, we recommend always running a preview command before an update. This allows you to write policies where one resource depends on the state or existence of another resource.
The below example requires that all dynamoDB tables have an App Autoscaling Policy associated with it.
const dynamodbTableAutoscalingRequired: StackValidationPolicy = {
name: "dynamodb-autoscaling-required",
description: "Requires a dynamoDB table to have an associated App Autoscaling policy.",
enforcementLevel: "mandatory",
validateStack: (args: StackValidationArgs, reportViolation: ReportViolation) => {
const dynamodbTables = args.resources.map(r => r.asType(aws.dynamodb.Table)).filter(r => r);
const appScalingPolicies = args.resources.map(r => r.asType(aws.appautoscaling.Policy)).filter(r => r);
const policyResourceIDMap: Record<string, q.ResolvedResource<aws.appautoscaling.Policy>> = {};
for (const policy of appScalingPolicies) {
policyResourceIDMap[policy.resourceId] = policy;
}
for (const table of dynamodbTables) {
if (policyResourceIDMap[table.id] === undefined) {
reportViolation(`DynamoDB table ${table.id} missing app autoscaling policy.`);
}
}
},
}
def dynamodb_autoscaling_required_validator(args: StackValidationArgs, report_violation: ReportViolation):
tables = filter(lambda r: r.resource_type == "aws:dynamodb/table:Table", args.resources)
policies = filter(lambda r: r.resource_type == "aws:autoscaling/policy:Policy", args.resources)
policy_resource_ids = set()
for policy in policies:
policy_resource_ids.add(policy["resourceId"])
for table in tables:
if table["id"] not in policy_resource_ids:
report_violation(f"DynamoDB table {table['id']} missing app autoscaling policy.")
dynamodb_autoscaling_required = StackValidationPolicy(
name="dynamodb-autoscaling-required",
description="Requires a dynamoDB table to have an associated App Autoscaling policy.",
enforcement_level=EnforcementLevel.MANDATORY,
validate=dynamodb_autoscaling_required_validator,
)
A StackValidationPolicy
can also be used to make validations against a resource that must already be created to validate. For example, a policy that
checks whether an Amazon Certificate Manager (ACM) certificate has expired would require the certificate already be created as it relies on its outputs.
The Pulumi Policy Packs examples repository provides example ResourceValidationPolicy
and StackValidationPolicy
rules for common cloud providers.
Resource Validation vs. Stack Validation
Resource Validation | Stack Validation | |
---|---|---|
What does it check? | Individual resources | All resources in the stack |
When is the check performed? | Before resources are created/modified | After all stack resources have been created/modified |
What information is available? | Resource input properties | Resource output properties (Note: inputs are propagated to outputs during preview) |
Policy Pack
A Policy Pack can contain one or more policies to enforce. Packs provide a way to group together similar policies. For example, you may decide to have one pack with AWS policies and another with Kubernetes-specific policies. That being said, there are no restrictions on which policies you combine within a pack, and you should pack them however makes sense for your organization.
As part of CrossGuard, organization administrators can author and publish Policy Packs to the Pulumi Cloud.
Policy Group
A Policy Group is a group of stacks with the same set of Policy Packs enforced. Policy Groups are only available from within the Pulumi Cloud when CrossGuard is enabled. A stack may belong to multiple Policy Groups. An example use of Policy Groups is to have a different group per environment. For example, you can have one for your stacks in production and a more permissive Policy Group for your other environments such as staging
and dev
.
Organization admins can create new Policy Groups, add stacks to a Policy Group, or remove stacks from a group.
Each Policy Group has its own set of enforced Policy Packs. An organization administrator can add, remove, or update the version of the Policy Pack enforced on the Policy Group.
In cases where a stack belongs to multiple Policy Groups and is therefore required to run multiple versions of the same Policy Pack, only the latest version of the Policy Pack gets enforced. For example, if my-stack
belongs to two Policy Groups and you want to enforce my-aws-policy-pack
versions 2 and 3, only version 3 will be enforced. You may view the Policy Groups a stack belongs to as well as the currently enforced Policy Packs for the stack by navigating to a stack’s Settings
tab and then Policies
.
Default Policy Group
Every organization has a default Policy Group. When the default Policy Group is created, all stacks in your organization are automatically added to that Policy Group. Additionally, all stacks that are created after adding the default Policy Group are automatically added to it. Organization admins can remove stacks from the default Policy Group as desired.
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.