Update PaC examples for the changed API
MODIFY
policy-packs/aws-advanced/compute.ts
policy-packs/aws-advanced/compute.ts
// limitations under the License.
import * as aws from "@pulumi/aws";
-import { Policy, typedRule } from "@pulumi/policy";
-import * as assert from "assert";
+import {
+ ReportViolation,
+ ResourceValidationArgs,
+ ResourceValidationPolicy,
+ validateTypedResource,
+} from "@pulumi/policy";
export function requireApprovedAmisById(
name: string,
approvedAmis: string | Iterable<string>,
-): Policy {
+): ResourceValidationPolicy {
const amis = toStringSet(approvedAmis);
return {
name: name,
- description: "Instances should use approved AMIs",
+ description: "Instances should use approved AMIs.",
enforcementLevel: "mandatory",
- rules: [
- typedRule(
- aws.ec2.Instance.isInstance,
- it => amis && assert.ok(amis.has(it.ami), "foo"),
- ),
- typedRule(
- aws.ec2.LaunchConfiguration.isInstance,
- it => amis && assert.ok(amis.has(it.imageId)),
- ),
- typedRule(
- aws.ec2.LaunchTemplate.isInstance,
- it => amis && assert.ok(it.imageId === undefined || amis.has(it.imageId)),
- ),
+ validateResource: [
+ validateTypedResource(aws.ec2.Instance.isInstance, (instance, args, reportViolation) => {
+ if (amis && !amis.has(instance.ami)) {
+ reportViolation("EC2 Instances should use approved AMIs.");
+ }
+ }),
+ validateTypedResource(aws.ec2.LaunchConfiguration.isInstance, (lc, args, reportViolation) => {
+ if (amis && !amis.has(lc.imageId)) {
+ reportViolation("EC2 LaunchConfigurations should use approved AMIs.");
+ }
+ }),
+ validateTypedResource(aws.ec2.LaunchTemplate.isInstance, (lt, args, reportViolation) => {
+ if (amis && lt.imageId && !amis.has(lt.imageId)) {
+ reportViolation("EC2 LaunchTemplates should use approved AMIs.");
+ }
+ }),
],
};
}
@@ -46,18 +53,20 @@ export function requireApprovedAmisById(
// TODO: approved-amis-by-tag
// https://docs.aws.amazon.com/config/latest/developerguide/approved-amis-by-tag.html
-export function requireHealthChecksOnAsgElb(name: string): Policy {
+export function requireHealthChecksOnAsgElb(name: string): ResourceValidationPolicy {
return {
name: name,
description:
"Auto Scaling groups that are associated with a load balancer should use Elastic " +
"Load Balancing health checks",
enforcementLevel: "mandatory",
- rules: typedRule(aws.autoscaling.Group.isInstance, it => {
- const classicLbAttached = it.loadBalancers.length > 0;
- const albAttached = it.targetGroupArns.length > 0;
+ validateResource: validateTypedResource(aws.autoscaling.Group.isInstance, (group, args, reportViolation) => {
+ const classicLbAttached = group.loadBalancers.length > 0;
+ const albAttached = group.targetGroupArns.length > 0;
if (classicLbAttached || albAttached) {
- assert.strictEqual("ELB", it.healthCheckType);
+ if (group.healthCheckType !== "ELB") {
+ reportViolation("Auto Scaling groups that are associated with a load balancer should use");
+ }
}
}),
};
@@ -68,7 +77,7 @@ export function requireInstanceTenancy(
tenancy: "DEDICATED" | "HOST" | "DEFAULT",
imageIds?: string | Iterable<string>,
hostIds?: string | Iterable<string>,
-): Policy {
+): ResourceValidationPolicy {
const images = toStringSet(imageIds);
const hosts = toStringSet(hostIds);
@@ -78,17 +87,23 @@ export function requireInstanceTenancy(
hosts,
)} should use tenancy '${tenancy}'`,
enforcementLevel: "mandatory",
- rules: [
- typedRule(aws.ec2.Instance.isInstance, it => {
- if (hosts !== undefined && hosts.has(it.hostId)) {
- assert.strictEqual(it.tenancy, tenancy);
- } else if (images !== undefined && images.has(it.ami)) {
- assert.strictEqual(it.tenancy, tenancy);
+ validateResource: [
+ validateTypedResource(aws.ec2.Instance.isInstance, (instance, args, reportViolation) => {
+ if (hosts !== undefined && hosts.has(instance.hostId)) {
+ if (instance.tenancy !== tenancy) {
+ reportViolation(`EC2 Instance with host ID '${instance.hostId}' not using tenancy '${tenancy}'.`);
+ }
+ } else if (images !== undefined && images.has(instance.ami)) {
+ if (instance.tenancy !== tenancy) {
+ reportViolation(`EC2 Instance with AMI '${instance.ami}' not using tenancy '${tenancy}'.`);
+ }
}
}),
- typedRule(aws.ec2.LaunchConfiguration.isInstance, it => {
- if (images !== undefined && images.has(it.imageId)) {
- assert.strictEqual(it.placementTenancy, tenancy);
+ validateTypedResource(aws.ec2.LaunchConfiguration.isInstance, (lc, args, reportViolation) => {
+ if (images !== undefined && images.has(lc.imageId)) {
+ if (lc.placementTenancy !== tenancy) {
+ reportViolation(`EC2 LaunchConfiguration with image ID '${lc.imageId}' not using tenancy '${tenancy}'.`);
+ }
}
}),
],
@@ -98,41 +113,57 @@ export function requireInstanceTenancy(
export function requireInstanceType(
name: string,
instanceTypes: aws.ec2.InstanceType | Iterable<aws.ec2.InstanceType>,
-): Policy {
+): ResourceValidationPolicy {
const types = toStringSet(instanceTypes);
return {
name: name,
description: "EC2 instances should use approved instance types.",
enforcementLevel: "mandatory",
- rules: [
- typedRule(aws.ec2.Instance.isInstance, it => assert.ok(types.has(it.instanceType))),
- typedRule(aws.ec2.LaunchConfiguration.isInstance, it =>
- assert.ok(types.has(it.instanceType)),
- ),
- typedRule(aws.ec2.LaunchTemplate.isInstance, it =>
- assert.ok(it.instanceType !== undefined && types.has(it.instanceType)),
- ),
+ validateResource: [
+ validateTypedResource(aws.ec2.Instance.isInstance, (instance, args, reportViolation) => {
+ if (!types.has(instance.instanceType)) {
+ reportViolation("EC2 Instance should use the approved instance types.")
+ }
+ }),
+ validateTypedResource(aws.ec2.LaunchConfiguration.isInstance, (lc, args, reportViolation) => {
+ if (!types.has(lc.instanceType)) {
+ reportViolation("EC2 LaunchConfiguration should use the approved instance types.")
+ }
+ }),
+ validateTypedResource(aws.ec2.LaunchTemplate.isInstance, (lt, args, reportViolation) => {
+ if (!lt.instanceType || !types.has(lt.instanceType)) {
+ reportViolation("EC2 LaunchTemplate should use the approved instance types.")
+ }
+ }),
],
};
}
-export function requireEbsOptimization(name: string): Policy {
+export function requireEbsOptimization(name: string): ResourceValidationPolicy {
// TODO: Enable optimization only for EC2 instances that can be optimized.
return {
name: name,
description: "EBS optimization should be enabled for all EC2 instances",
enforcementLevel: "mandatory",
- rules: typedRule(aws.ec2.Instance.isInstance, it => assert.ok(it.ebsOptimized === true)),
+ validateResource: validateTypedResource(aws.ec2.Instance.isInstance, (instance, args, reportViolation) => {
+ if (instance.ebsOptimized !== true) {
+ reportViolation("EC2 Instance should have EBS optimization enabled.");
+ }
+ }),
};
}
-export function requireDetailedMonitoring(name: string): Policy {
+export function requireDetailedMonitoring(name: string): ResourceValidationPolicy {
return {
name: name,
description: "Detailed monitoring should be enabled for all EC2 instances",
enforcementLevel: "mandatory",
- rules: typedRule(aws.ec2.Instance.isInstance, it => assert.ok(it.monitoring === true)),
+ validateResource: validateTypedResource(aws.ec2.Instance.isInstance, (instance, args, reportViolation) => {
+ if (instance.monitoring !== true) {
+ reportViolation("EC2 Instance should have monitoring enabled.");
+ }
+ }),
};
}
@@ -160,30 +191,34 @@ export function requireDetailedMonitoring(name: string): Policy {
// TODO: ec2-managedinstance-platform-check
// https://docs.aws.amazon.com/config/latest/developerguide/ec2-managedinstance-platform-check.html
-export function requireEbsVolumesOnEc2Instances(name: string): Policy {
+export function requireEbsVolumesOnEc2Instances(name: string): ResourceValidationPolicy {
// TODO: Check if EBS volumes are marked for deletion.
return {
name: name,
description: "EBS volumes should be attached to all EC2 instances",
enforcementLevel: "mandatory",
- rules: typedRule(aws.ec2.Instance.isInstance, it =>
- assert.ok(it.ebsBlockDevices === undefined || it.ebsBlockDevices.length > 0),
- ),
+ validateResource: validateTypedResource(aws.ec2.Instance.isInstance, (instance, args, reportViolation) => {
+ if (instance.ebsBlockDevices !== undefined && instance.ebsBlockDevices.length === 0) {
+ reportViolation("EC2 Instance should have EBS volumes attached.");
+ }
+ }),
};
}
// TODO: eip-attached
// https://docs.aws.amazon.com/config/latest/developerguide/eip-attached.html
-export function requireEbsEncryption(name: string, kmsKeyId?: string): Policy {
+export function requireEbsEncryption(name: string, kmsKeyId?: string): ResourceValidationPolicy {
return {
name: name,
description: "EBS volumes should be encrypted",
enforcementLevel: "mandatory",
- rules: typedRule(aws.ebs.Volume.isInstance, it => {
- assert.ok(it.encrypted);
- if (kmsKeyId !== undefined) {
- assert.strictEqual(it.kmsKeyId, kmsKeyId);
+ validateResource: validateTypedResource(aws.ebs.Volume.isInstance, (volume, args, reportViolation) => {
+ if (!volume.encrypted) {
+ reportViolation("EBS volumes should be encrypted.");
+ }
+ if (kmsKeyId !== undefined && volume.kmsKeyId !== kmsKeyId) {
+ reportViolation(`EBS volumes should be encrypted with KMS ID '${kmsKeyId}'.`);
}
}),
};
@@ -195,21 +230,27 @@ export function requireEbsEncryption(name: string, kmsKeyId?: string): Policy {
// TODO: elb-custom-security-policy-ssl-check
// https://docs.aws.amazon.com/config/latest/developerguide/elb-custom-security-policy-ssl-check.html
-export function requireElbLogging(name: string, bucketName?: string): Policy {
- const assertElbLogs = (lb: {
+export function requireElbLogging(name: string, bucketName?: string): ResourceValidationPolicy {
+ const assertElbLogs = (
+ lb: {
accessLogs?: {
bucket: string;
bucketPrefix?: string;
enabled?: boolean;
interval?: number;
};
- }) => {
- assert.ok(lb.accessLogs !== undefined && lb.accessLogs.enabled === true);
- assert.ok(
- bucketName !== undefined &&
- lb.accessLogs !== undefined &&
- bucketName === lb.accessLogs.bucket,
- );
+ },
+ args: ResourceValidationArgs,
+ reportViolation: ReportViolation,
+ ) => {
+ if (lb.accessLogs === undefined || lb.accessLogs.enabled !== true) {
+ reportViolation("Load Balancer should have logging enabled.");
+ }
+ if (bucketName !== undefined) {
+ if (lb.accessLogs === undefined || bucketName !== lb.accessLogs.bucket) {
+ reportViolation(`Load Balancer should have logging enabled with bucket '${bucketName}'.`);
+ }
+ }
};
return {
@@ -218,9 +259,9 @@ export function requireElbLogging(name: string, bucketName?: string): Policy {
"All Application Load Balancers and the Classic Load Balancers should have " +
"logging enabled.",
enforcementLevel: "mandatory",
- rules: [
- typedRule(aws.elasticloadbalancing.LoadBalancer.isInstance, assertElbLogs),
- typedRule(aws.elasticloadbalancingv2.LoadBalancer.isInstance, assertElbLogs),
+ validateResource: [
+ validateTypedResource(aws.elasticloadbalancing.LoadBalancer.isInstance, assertElbLogs),
+ validateTypedResource(aws.elasticloadbalancingv2.LoadBalancer.isInstance, assertElbLogs),
],
};
}

MODIFY
policy-packs/aws-advanced/index.ts
policy-packs/aws-advanced/index.ts
// limitations under the License.
import * as aws from "@pulumi/aws";
-import { PolicyPack, typedRule } from "@pulumi/policy";
+import { PolicyPack } from "@pulumi/policy";
import * as compute from "./compute";

MODIFY
policy-packs/aws-advanced/package.json
policy-packs/aws-advanced/package.json
"dependencies": {
"@pulumi/pulumi": "0.17.21",
"@pulumi/aws": "0.18.16",
- "@pulumi/policy": "https://pac-releases-0db9dae.s3.us-east-2.amazonaws.com/pulumi-policy-v0.0.1-dev.1565715379.tgz",
+ "@pulumi/policy": "dev"
},
"devDependencies": {
"@types/mocha": "^2.2.42",

MODIFY
policy-packs/aws/index.ts
policy-packs/aws/index.ts
import * as aws from "@pulumi/aws";
-import { PolicyPack, typedRule } from "@pulumi/policy";
-import * as assert from "assert";
+import { PolicyPack, ReportViolation, validateTypedResource } from "@pulumi/policy";
const policies = new PolicyPack("aws", {
policies: [
@@ -8,47 +7,52 @@ const policies = new PolicyPack("aws", {
name: "discouraged-ec2-public-ip-address",
description: "Associating public IP addresses is discouraged.",
enforcementLevel: "advisory",
- rules: typedRule(aws.ec2.Instance.isInstance, it => {
- assert(it.associatePublicIpAddress === false);
+ validateResource: validateTypedResource(aws.ec2.Instance.isInstance, (instance, args, reportViolation) => {
+ if (instance.associatePublicIpAddress) {
+ reportViolation("Consider not setting associatePublicIpAddress to true.");
+ }
}),
},
{
name: "required-name-tag",
description: "A 'Name' tag is required.",
enforcementLevel: "mandatory",
- rules: [
- typedRule(aws.ec2.Instance.isInstance, it => {
- requireNameTag(it.tags);
+ validateResource: [
+ validateTypedResource(aws.ec2.Instance.isInstance, (instance, args, reportViolation) => {
+ requireNameTag(instance.tags, reportViolation);
}),
- typedRule(aws.ec2.Vpc.isInstance, it => {
- requireNameTag(it.tags);
+ validateTypedResource(aws.ec2.Vpc.isInstance, (vpc, args, reportViolation) => {
+ requireNameTag(vpc.tags, reportViolation);
}),
- ]
+ ],
},
{
name: "prohibited-public-internet",
description: "Ingress rules with public internet access are prohibited.",
enforcementLevel: "mandatory",
- rules: typedRule(aws.ec2.SecurityGroup.isInstance, it => {
- const publicInternetRules = it.ingress.find(ingressRule =>
- (ingressRule.cidrBlocks || []).find(cidr =>
- cidr === "0.0.0.0/0"
- )
- );
- assert(publicInternetRules === undefined);
+ validateResource: validateTypedResource(aws.ec2.SecurityGroup.isInstance, (sg, args, reportViolation) => {
+ const publicInternetRules = sg.ingress.find(ingressRule =>
+ (ingressRule.cidrBlocks || []).find(cidr => cidr === "0.0.0.0/0"));
+ if (publicInternetRules) {
+ reportViolation("Ingress rules with public internet access are prohibited.");
+ }
}),
},
{
name: "prohibited-elasticbeanstalk",
description: "Use of Elastic Beanstalk is prohibited.",
enforcementLevel: "mandatory",
- rules: (type: string) => {
- assert(type.startsWith("aws:elasticbeanstalk") === false);
+ validateResource: (args, reportViolation) => {
+ if (args.type.startsWith("aws:elasticbeanstalk")) {
+ reportViolation("Use of Elastic Beanstalk is prohibited.");
+ }
},
},
],
});
-const requireNameTag = function (tags: any) {
- assert((tags || {})["Name"] !== undefined);
+const requireNameTag = function (tags: any, reportViolation: ReportViolation) {
+ if ((tags || {})["Name"] === undefined) {
+ reportViolation("A 'Name' tag is required.");
+ }
};

MODIFY
policy-packs/azure/index.ts
policy-packs/azure/index.ts
import * as azure from "@pulumi/azure";
-import { PolicyPack, typedRule } from "@pulumi/policy";
+import { PolicyPack, validateTypedResource } from "@pulumi/policy";
import * as assert from "assert";
const policies = new PolicyPack("azure", {
@@ -8,25 +8,31 @@ const policies = new PolicyPack("azure", {
name: "discouraged-public-ip-address",
description: "Associating public IP addresses is discouraged.",
enforcementLevel: "advisory",
- rules: typedRule(azure.network.NetworkInterface.isInstance, it => {
- const publicIpAssociations = it.ipConfigurations.find(cfg => cfg.publicIpAddressId !== undefined);
- assert(publicIpAssociations === undefined);
+ validateResource: validateTypedResource(azure.network.NetworkInterface.isInstance, (ni, args, reportViolation) => {
+ const publicIpAssociations = ni.ipConfigurations.find(cfg => cfg.publicIpAddressId !== undefined);
+ if (publicIpAssociations !== undefined) {
+ reportViolation("Associating public IP addresses is discouraged.");
+ }
}),
},
{
name: "prohibited-public-internet",
description: "Inbound rules with public internet access are prohibited.",
enforcementLevel: "mandatory",
- rules: typedRule(azure.network.NetworkSecurityRule.isInstance, it => {
- assert(it.sourceAddressPrefix !== "*");
+ validateResource: validateTypedResource(azure.network.NetworkSecurityRule.isInstance, (securityRule, args, reportViolation) => {
+ if (securityRule.sourceAddressPrefix === "*") {
+ reportViolation("Inbound rules with public internet access are prohibited.");
+ }
}),
},
{
name: "prohibited-iot",
description: "Use of IOT services is prohibited.",
enforcementLevel: "mandatory",
- rules: (type: string) => {
- assert(type.startsWith("azure:iot") === false);
+ validateResource: (args, reportViolation) => {
+ if (args.type.startsWith("azure:iot")) {
+ reportViolation("Use of IOT services is prohibited.");
+ }
},
},
],

MODIFY
policy-packs/gcp/index.ts
policy-packs/gcp/index.ts
import * as gcp from "@pulumi/gcp";
-import { PolicyPack, typedRule } from "@pulumi/policy";
-import * as assert from "assert";
+import { PolicyPack, validateTypedResource } from "@pulumi/policy";
const policies = new PolicyPack("gcp", {
policies: [
@@ -8,29 +7,32 @@ const policies = new PolicyPack("gcp", {
name: "discouraged-gcp-public-ip-address",
description: "Associating public IP addresses is discouraged.",
enforcementLevel: "advisory",
- rules: typedRule(gcp.compute.Instance.isInstance, it => {
- const publicIps = it.networkInterfaces.find(net =>
- net.accessConfigs !== undefined)
- assert(publicIps === undefined);
+ validateResource: validateTypedResource(gcp.compute.Instance.isInstance, (instance, args, reportViolation) => {
+ const publicIps = instance.networkInterfaces.find(net => net.accessConfigs !== undefined);
+ if (publicIps !== undefined) {
+ reportViolation("Associating public IP addresses is discouraged.");
+ }
}),
},
{
name: "prohibited-public-internet",
description: "Ingress rules with public internet access are prohibited.",
enforcementLevel: "mandatory",
- rules: typedRule(gcp.compute.Firewall.isInstance, it => {
- const publicInternetRules = (it.sourceRanges || []).find(ranges =>
- ranges === "0.0.0.0/0"
- );
- assert(publicInternetRules === undefined);
+ validateResource: validateTypedResource(gcp.compute.Firewall.isInstance, (firewall, args, reportViolation) => {
+ const publicInternetRules = (firewall.sourceRanges || []).find(ranges => ranges === "0.0.0.0/0");
+ if (publicInternetRules !== undefined) {
+ reportViolation("Ingress rules with public internet access are prohibited.");
+ }
}),
},
{
name: "prohibited-bigtable",
description: "Use of Bigtable is prohibited.",
enforcementLevel: "mandatory",
- rules: (type: string) => {
- assert(type.startsWith("gcp:bigtable") === false);
+ validateResource: (args, reportViolation) => {
+ if (args.type.startsWith("gcp:bigtable")) {
+ reportViolation("Use of Bigtable is prohibited.");
+ }
},
},
],

MODIFY
policy-packs/kubernetes/index.ts
policy-packs/kubernetes/index.ts
// limitations under the License.
import * as k8s from "@pulumi/kubernetes";
-import { assert, PolicyPack, typedRule } from "@pulumi/policy";
+import { PolicyPack, validateTypedResource } from "@pulumi/policy";
const policies = new PolicyPack("kubernetes", {
policies: [
@@ -21,15 +21,13 @@ const policies = new PolicyPack("kubernetes", {
name: "no-public-services",
description: "Kubernetes Services should be cluster-private",
enforcementLevel: "mandatory",
- rules: typedRule(k8s.core.v1.Service.isInstance, svc => {
- assert.isNotEqual(
- "LoadBalancer",
- svc.spec.type,
- `Kubernetes Services that have .type === "LoadBalancer" are exposed to ` +
+ validateResource: validateTypedResource(k8s.core.v1.Service.isInstance, (svc, args, reportViolation) => {
+ if (svc.spec.type == "LoadBalancer") {
+ reportViolation(`Kubernetes Services that have .type === "LoadBalancer" are exposed to ` +
`anything that can reach the Kubernetes cluster, likely including the ` +
`public Internet. The security team has disallowed this to prevent ` +
- `unauthorized access.`,
- );
+ `unauthorized access.`);
+ }
}),
},
],

MODIFY
policy-packs/policy-pack-typescript/index.ts
policy-packs/policy-pack-typescript/index.ts
// limitations under the License.
import * as aws from "@pulumi/aws";
-import { PolicyPack, typedRule } from "@pulumi/policy";
-import * as assert from "assert";
+import { PolicyPack, validateTypedResource } from "@pulumi/policy";
new PolicyPack("policy-pack-typescript", {
policies: [{
name: "s3-no-public-read",
description: "Prohibits setting the publicRead or publicReadWrite permission on AWS S3 buckets.",
enforcementLevel: "mandatory",
- rules: [
- typedRule(aws.s3.Bucket.isInstance, it => assert.ok(it.acl !== "public-read"
- && it.acl !== "public-read-write",
+ validateResource: validateTypedResource(aws.s3.Bucket.isInstance, (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")),
- ],
+ "Read more about ACLs here: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html");
+ }
+ }),
}],
});

MODIFY
policy-packs/policy-pack-typescript/package.json
policy-packs/policy-pack-typescript/package.json
"dependencies": {
"@pulumi/aws": "^1.0.0",
"@pulumi/pulumi": "^1.0.0",
- "@pulumi/policy": "latest"
+ "@pulumi/policy": "dev"
},
"devDependencies": {
"@types/node": "^8.0.0"

Update PaC examples for the changed API (#441)