# Create a Custom Policy Pack

In this step, we’ll create a policy pack to enforce the following rules for AWS resources:

1. S3 buckets must be prefixed with the product name `myproduct-`.
2. EC2 instances must use the `t2.micro` instance type.
3. All AWS resources must have at least one tag defined.

### Set up your policy pack project

First, create a new directory for your policy pack project:

```bash
mkdir custom-policy-pack
cd custom-policy-pack
```

Then, initialize your project. Choose Python or TypeScript based on your preferred language.

- TypeScript
- Python

```bash
pulumi policy new aws-typescript
```

This will create the following files and directories:

- `PulumiPolicy.yaml`: A [Pulumi project file](/content/docs/iac/concepts/projects/index.html) that indicates this a policy pack.
- `index.ts`: The TypeScript entry point where the policies will be defined in code.
- `node_modules/`: The [NPM modules directory](https://docs.npmjs.com/cli/v9/configuring-npm/folders#node-modules)
- `package-lock.json`: A list of the module dependencies used by [`npm`](https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json).
- `package.json`: The [`npm` package description file](https://docs.npmjs.com/cli/v11/configuring-npm/package-json).
- `tsconfig.json`: The [TypeScript configuration file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).

In this example, we are using the [`aws-typescript`](https://github.com/pulumi/templates-policy/tree/master/aws-typescript) policy pack template.

```bash
pulumi policy new aws-python
```

This will create the following files and directories:

- `PulumiPolicy.yaml`: A [Pulumi project file](/content/docs/iac/concepts/projects/index.html) that indicates this a policy pack.
- `__main__.py`: The Python entry point where the policies will be defined in code.
- `requirements.txt`: A list of the module dependencies used by [`pip`](https://pip.pypa.io/en/stable/reference/requirements-file-format/).
- `venv/`: The Python [virtual environment](https://docs.python.org/3/library/venv.html).

In this example, we are using the [`aws-python`](https://github.com/pulumi/templates-policy/tree/master/aws-python) policy pack template.

### Define Policies

Policies are written in Python or TypeScript. Like Pulumi Programs, you can use the full power of your preferred language, including standard features like leveraging third-party modules, using conditional logic and control flow, and can be validated with unit testing frameworks.

**File: `custom-policy-pack/index.ts`**

```typescript
import * as aws from "@pulumi/aws";
import { PolicyPack, validateResourceOfType } from "@pulumi/policy";

new PolicyPack("aws-typescript", {
    policies: [{
        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. " +
                    "Read more about ACLs here: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html");
            }
        }),
    }],
});
```

**File: `custom-policy-pack/__main__.py`**

```python
from pulumi_policy import (
    EnforcementLevel,
    PolicyPack,
    ReportViolation,
    ResourceValidationArgs,
    ResourceValidationPolicy,
)

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(
    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,
)

PolicyPack(
    name="aws-python",
    policies=[
        s3_no_public_read,
    ],
)
```

Here you can see the basic structure of a policy pack:

- Some imports from the Pulumi Crossguard SDK
- a _function_ that implements the policy
- a _policy_ definition that wraps the implementation and describes the policy
- a _policy pack_ definition that packages the policies together

While this example is a useful policy, it’s not what we need right now. Let’s delete all of that code and create some new custom policies.

Replace the contents of `index.ts` with the following:

```typescript
import * as aws from "@pulumi/aws";
import { PolicyPack, ReportViolation, ResourceValidationArgs, validateResourceOfType, ResourceValidationPolicy } from "@pulumi/policy";

export const REQUIRED_S3_PREFIX: string = "myproduct-";

// Policy: Ensure S3 buckets have product prefix enabled
export const s3ProductPrefixPolicy: ResourceValidationPolicy = {
    name: "s3-product-prefix",
    description: "Ensures S3 buckets have the correct product prefix.",
    enforcementLevel: "mandatory",
    validateResource: validateResourceOfType(aws.s3.Bucket, (bucket, args, reportViolation) => {
        const prefix = bucket.bucketPrefix || "";
        if (prefix != REQUIRED_S3_PREFIX) {
            reportViolation(`Invalid prefix: '${prefix}'. S3 buckets must use '${REQUIRED_S3_PREFIX}' prefix.`);
        }
    }),
};

export const REQUIRED_INSTANCE_TYPE: string = "t2.micro";

// Policy: Restrict EC2 instance types
export const ec2InstanceTypeRestrictedPolicy: ResourceValidationPolicy = {
    name: "ec2-instance-type-restricted",
    description: "Ensures EC2 instances use approved instance type.",
    enforcementLevel: "mandatory",
    validateResource: validateResourceOfType(aws.ec2.Instance, (instance, args, reportViolation) => {
        const instanceType = instance.instanceType || "";
        if (instanceType !== REQUIRED_INSTANCE_TYPE) {
            reportViolation(`Invalid instance type: '${instanceType}'. EC2 instances must use '${REQUIRED_INSTANCE_TYPE}' instance type.`);
        }
    }),
};

// Policy: Ensure all AWS resources have at least one tag
export const allAwsResourcesMustHaveTagsPolicy: ResourceValidationPolicy = {
    name: "all-aws-resources-must-have-tags",
    description: "Ensures all AWS resources have at least one tag.",
    enforcementLevel: "mandatory",
    validateResource: (args: ResourceValidationArgs, reportViolation: ReportViolation) => {
        if (args.type.startsWith("aws")) {
            const tags = args.props.tags || {};
            if (Object.keys(tags).length === 0) {
                reportViolation("All AWS resources must have at least one tag.");
            }
        }
    },
};

new PolicyPack("custom-policy-pack", {
    policies: [s3ProductPrefixPolicy, ec2InstanceTypeRestrictedPolicy, allAwsResourcesMustHaveTagsPolicy],
});
```

Here we define three different policies:

- **s3-product-prefix**: Ensures S3 buckets are prefixed with the product name by checking the `bucketPrefix` property on all `aws:s3:Bucket` resources.
- **ec2-instance-type-restricted**: Restricts EC2 instance types to only use the affordable `t2.micro` type, by checking the `instanceType` property on all `aws:ec2/instance:Instance` resources.
- **all-aws-resources-must-have-tags**: Ensures all AWS resources have at least one tag by checking the `tags` property on all resources whose type starts with `aws`.

Each of the policies uses the same pattern:

1. Define a `ResourceValidationPolicy` with a bit of metadata and a validation function. Each policy can have an individual enforcement level, name, and description.
2. The validation function is defined inline using the `validateResourceOfType` helper function.
3. The validation functions take an instance of the resource, an args property bag, and a function for reporting policy violations.
4. If there is a problem with the property value, or some other aspect of the resource is out of compliance, we use the `reportViolation` function that was passed in to indicate that there’s a problem. The error message should be a full sentence and give useful information on how to remediate the problem.

If you’re not sure what the correct resource type string is for your particular set of resources, you can run `pulumi stack` to list the resources in your current stack.

Finally we assemble the policies into a policy pack object, giving it the name `custom-policy-pack`.
