Authorization and Amazon Verified Permissions - A New Way to Manage Permissions Part XIII: Cloudformation
Tags: avp, authorization, cedar, cloudformation
Published: 2024-01-20 17:17:30

Welcome back to my blog post series dedicated to building authorization using Cedar and Amazon Verified Permissions. In a previous blogpost we've learned about usage [AVP with IaC Terraform](https://dev.to/aws-builders/authorization-and-amazon-verified-permissions-a-new-way-to-manage-permissions-part-xii-terraform-598d). Today, we will cover the topic of how to use AVP with Cloudformation. 

## Introduction

[Cloudformation (IaC)](https://aws.amazon.com/cloudformation/) does not need to be introduced to anyone, plus if you read the previous blogpost, the terraform provider (CC) we used is based on Cloudformation. Moreover, you will notice a lot of similarities, after all, we are implementing the same scenario, but with a different tool.

All the Cloudformation references you can check [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_VerifiedPermissions.html).

Let's assume that we would like to recreate one of the scenarios from avp-cli in Cloudformation, for example, the [documents scenario](https://github.com/Pigius/avp-cli/blob/main/scenarios/documentsScenario/documentsScenario.json). This is a basic scenario with a document management platform schema and two policies (`Allow all users to view all documents` and `Forbid user X from viewing any documents`).

To recreate this, we need:


![policy store](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7tx6zmpqeh8z5fvjq5j.png)


A **policy store**, which is a container for our policies. In such a container, we keep all policies, as well as the schema. It's important to remember that later when using AVP for authorization requests, we send requests against a specific policy store.

A **schema** is the structure of all available entities for a given policy store. In short, it defines what entities are available for our authorization system, including available actions, principals, and resources. The schema not only informs us about the available entities but also validates the correctness of authorization requests.

Finally, we will also need to add our **policies**.


## Let's build!

The code for today's blog post can be found [here](https://github.com/Pigius/awesome-amazon-verified-permissions-and-cedar/tree/main/cloudformation/scenarios/documentsScenario).

The example we will create is not intended for production use; it has been created for educational purposes. However, in the future, In the next weeks, I will try to recreate all scenarios from avp-cli to Cloudformation. It will be in this [repository](https://github.com/Pigius/awesome-amazon-verified-permissions-and-cedar/tree/main/cloudformation/), as well as a couple of more advanced, production-wise examples.

For our example, we will need two types of resources from the Cloudformation:

- [policy store](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-verifiedpermissions-policystore.html)
- [policy](https://docs.aws.amazon.com/verifiedpermissions/latest/userguide/policies.html)

Now that we know everything, we can start building the project with Cloudformation.

Let's start with empty `avp.yaml` for our Cloudformation configuration. 

```yaml
AWSTemplateFormatVersion: "2010-09-09"

Description: >
  IaC for AVP

Resources:
```
We've started from adding `AWSTemplateFormatVersion` to tell CloudFormation which template version we're adhering to. The `Description` part refers to the overall description of the template.

Now we can focus on the `Resources` section where we declare the AWS resources you want CloudFormation to create or manage.

## Create a policy store

Now our focus will be to add a configuration for a policy store. First, we need to define the `validation_settings`. It specifies the validation setting for this policy store. In our use-case, we would like to be `strict` as we will add schema and check the validation of our authorization requests.

```yaml
  PolicyStore:
    Type: AWS::VerifiedPermissions::PolicyStore
    Properties:
      Description: "Policy store for the documents scenario"
      ValidationSettings:
        Mode: STRICT
```
We're declaring a resource of type `AWS::VerifiedPermissions::PolicyStore`, which represents a container in AVP where we will store policies. The `Description` nested within Properties gives our policy store a description. Moreover, we've added validation settings setup as strict.

Now if we deploy it, we will have an empty policy store with a validation mode setup as strict. 

## Adding Schema
Now we want to add a schema, which you can find [here](https://github.com/Pigius/avp-cli/blob/main/scenarios/documentsScenario/schema.json). As you can see, it is a JSON file that we need to have. Let's update the configuration

```yaml
...
  PolicyStore:
    Type: AWS::VerifiedPermissions::PolicyStore
    Properties:
      Description: "Policy store for the documents scenario"
      ValidationSettings:
        Mode: STRICT
      Schema:
        CedarJson: |
          {
            "DocumentManagementPlatform": {
              "entityTypes": {
                "User": {
                  "shape": {
                    "type": "Record",
                    "attributes": {}
                  }
                },
                "Document": {
                  "shape": {
                    "type": "Record",
                    "attributes": {}
                  }
                }
              },
              "actions": {
                "View": {
                  "appliesTo": {
                    "principalTypes": ["User"],
                    "resourceTypes": ["Document"]
                  }
                },
                "Edit": {
                  "appliesTo": {
                    "principalTypes": ["User"],
                    "resourceTypes": ["Document"]
                  }
                }
              }
            }
          }
```
This way, we've added a schema to our policy store.

## Policy Store ID
Before we jump into the policies, we need to tackle one important thing in our template. We would like to know the ID of our policy store, so we can easily find it, and check our Policy Store, or pass it to the resources that will use it (for instance Lambda function which will make requests to AVP later on).

So to have it, we need to use Cloudformation `Outputs` which specifies the values that are returned whenever you view your stack's properties.

We can easily add it like:
```yaml
...
Outputs:
  PolicyStoreId:
    Description: "Identifier of the Amazon Verified Permissions policy store."
    Value: !Ref PolicyStore
```
The actual !Ref directive within Value is an [intrinsic function](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html) used to retrieve the ID of a resource defined in the same template. In our case, `!Ref PolicyStore` gets the ID of the PolicyStore resource created by the stack.


## Adding policies

Now, it's time for the final step, adding policies:

- [this allow policy](https://github.com/Pigius/avp-cli/blob/main/scenarios/documentsScenario/allow_policy.cedar) 
- [this deny policy](https://github.com/Pigius/avp-cli/blob/main/scenarios/documentsScenario/deny_policy.cedar) 

Now we can add:

```yaml
  AllowPolicy:
    Type: AWS::VerifiedPermissions::Policy
    Properties:
      PolicyStoreId: !Ref PolicyStore
      Definition:
        Static:
          Description: "Allow all users to view all documents"
          Statement: |
            permit (
                principal,
                action in [DocumentManagementPlatform::Action::"View"],
                resource
            );

  DenyPolicy:
    Type: AWS::VerifiedPermissions::Policy
    Properties:
      PolicyStoreId: !Ref PolicyStore
      Definition:
        Static:
          Description: "Forbid user X from viewing any documents"
          Statement: |
            forbid(
              principal == DocumentManagementPlatform::User::"xyz",
              action in [DocumentManagementPlatform::Action::"View"],
              resource
            );
```

- The `Type: AWS::VerifiedPermissions::Policy` is used for creating two policies.
- As any policy is part of that policy store, we needed to reference the Policy Store ID.
- For the `Definition`, we've used `static`, which indicates that the policy is static – it's not dynamically created from a template but is rather a fixed definition.
- For the `Statement` we just need to pass the definition in Cedar language.

The whole file will look like this:

```yaml
AWSTemplateFormatVersion: "2010-09-09"

Description: >
  IaC for AVP

Resources:
  PolicyStore:
    Type: AWS::VerifiedPermissions::PolicyStore
    Properties:
      Description: "Policy store for the documents scenario"
      ValidationSettings:
        Mode: STRICT
      Schema:
        CedarJson: |
          {
            "DocumentManagementPlatform": {
              "entityTypes": {
                "User": {
                  "shape": {
                    "type": "Record",
                    "attributes": {}
                  }
                },
                "Document": {
                  "shape": {
                    "type": "Record",
                    "attributes": {}
                  }
                }
              },
              "actions": {
                "View": {
                  "appliesTo": {
                    "principalTypes": ["User"],
                    "resourceTypes": ["Document"]
                  }
                },
                "Edit": {
                  "appliesTo": {
                    "principalTypes": ["User"],
                    "resourceTypes": ["Document"]
                  }
                }
              }
            }
          }

  AllowPolicy:
    Type: AWS::VerifiedPermissions::Policy
    Properties:
      PolicyStoreId: !Ref PolicyStore
      Definition:
        Static:
          Description: "Allow all users to view all documents"
          Statement: |
            permit (
                principal,
                action in [DocumentManagementPlatform::Action::"View"],
                resource
            );

  DenyPolicy:
    Type: AWS::VerifiedPermissions::Policy
    Properties:
      PolicyStoreId: !Ref PolicyStore
      Definition:
        Static:
          Description: "Forbid user X from viewing any documents"
          Statement: |
            forbid(
              principal == DocumentManagementPlatform::User::"xyz",
              action in [DocumentManagementPlatform::Action::"View"],
              resource
            );

Outputs:
  PolicyStoreId:
    Description: "Identifier of the Amazon Verified Permissions policy store."
    Value: !Ref PolicyStore
```

## Deployment

Now that we have everything, let's try to deploy our template.

Navigate to the AWS console, and open Cloudformation service. Inside the Cloudformation service in the AWS console, press `Create a new stack` with the option `With existing resources (import resources)`. Use the `Template is ready`.

Overall, we have 3 options for deploying our Cloudformation, either by storing it in S3 (if you use SAM or Serverless Framework you will be aware of that), you can upload it (which we will use), or you can deploy it from your repo. Here is a great article on [that](https://aws.amazon.com/blogs/devops/automate-safe-aws-cloudformation-deployments-from-github/).

This time, use the `Upload a template file` option.
- Attach the `avp.yaml` file and then for `Stack name` create a name, you can leave the rest of the configuration unchanged.



![upload](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vhv54htb8f9ic577q2l3.png)

If we check the `Designer` option it will look like this:


![Designer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d34d2kr1n5rnrvwzonom.png)


- Attach the `avp.yaml` file and then for `Stack name` create a name, you can leave the rest of the configuration unchanged.
- Review a stack, accept capabilities, and deploy.

- After a couple of minutes everything will be deployed. In the resources tab, you can see 3 resources (AVP policy store, and 2 access policies), and in the output section, you will AVP policy store ID, which you can grab and check the resources in the AVP service.


![Resources](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7lbdgcytyd18yd0373t.png)

![Outputs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o8jqvyi2ctw8a7kepcep.png)

Feel free to grab the Policy Store ID and check it in AVP.


![AVP](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v95agwgpqcnb9eajmpk6.png)



## Final words

As you can see, it's quite simple to create a basic project with AVP using Cloudformation.

If you're interested in more Cloudformation templates, I've recreated all of the AVP-CLI scenarios in CF, you can find them [here](https://github.com/Pigius/awesome-amazon-verified-permissions-and-cedar/tree/main/cloudformation/). In the next blogpost [we will tackle AVP getting started feature](https://dev.to/aws-heroes/authorization-and-amazon-verified-permissions-a-new-way-to-manage-permissions-part-xiv-avp-getting-started-478f).

That's all for today. I encourage you to experiment on your own.