# API Authorization using Aserto and Zuplo

Sep 25th, 2024

Omri Gazitt

## API Authorization

Consistent API management is emerging as one of the most important functions of a Platform Engineering team. With projects like Backstage, these teams have a way to expose and document their API estate.

[Authorizing access to APIs](/content/blog/an-easy-button-for-api-authorization/index.html) is the next step in this journey. With Aserto’s API Authorization solution, onboarding new services is as simple as importing their OpenAPI definition and assigning users or groups various levels of access to those services, or to individual endpoints.

## Enforcing authorization using an API gateway

Since Platform Engineering teams don’t control the code to every API, they often look for common control points. An API gateway is the perfect enforcement point for an authorization policy, since it can filter requests before they are forwarded to the service, thereby requiring zero changes to the API code.

[Zuplo](https://www.zuplo.com/) is a modern API gateway company, and we now have a first-class integration between Aserto and Zuplo. Check out this webinar, where Josh Swift and I discuss the partnership, and demonstrate how easy it is to add fine-grained authorization to your APIs.

[Adding "entitled" API Authorization with Aserto and Zuplo](https://www.youtube.com/watch?v=oW4QRTke-Oc)

Or if you'd like to try it out for yourself, follow this guide to use Aserto and Zuplo together to secure your APIs.

## A step-by-step guide

For expediency, this walkthrough uses the Aserto hosted authorizer as the policy decision point that is called by the API gateway. In a production environment, you’d change the authorizer service endpoint to a Topaz instance that is running in your cluster, and connect that Topaz instance to the Aserto control plane. Everything else is basically the same.

With that said, let’s jump in!

## Instantiate the API Authorization template

Go to the [Aserto Console](https://console.aserto.com/), and if you don’t have an account yet, sign up for a free account. Once you’ve verified it, pick an account name, and select the API Authorization template.

If you already have an Aserto account, you can install the API Authorization template [here](https://console.aserto.com/ui/policies/createpolicy/fromtemplateschema).

This results in the creation of a new policy instance named _api-auth_.

Your tenant now has the _API Authorization_ model, which includes the _user_, _group_, _service_, and _endpoint_ object types, and the relations and permissions on these types.

Finally, the template also loaded some sample users and groups, modeled after the Rick and Morty cartoon, as well as three sample services - _Petstore API_, _Rick and Morty API_, and _Todo API_.

## Import an OpenAPI definition

Click on the _api-auth_ policy instance, and select the [Quickstart](https://console.aserto.com/ui/policies/api-auth/quickstart) tab. The Citadel identity provider that contains the _Rick and Morty_ users is already connected. In the next step, you can optionally import your own OpenAPI spec. If you have one ready, try it out… otherwise feel free to skip this step and rely on the existing three services that we’re using as sample data.

## The OPA policy

Click on the [Modules](https://console.aserto.com/ui/policies/api-auth/modules) tab, and observe a _single_ Rego module - the boilerplate _policy-rebac.check_ module.

Compare that with the complexity of having to manage a custom policy for every service! As mentioned, we are transforming a policy problem into a data modeling problem, which we will explore next.

But it’s important to note that any additional attribute-based access control or environment-oriented access restrictions can be easily added to this boilerplate policy. For example, you can easily extend the policy to ensure that users who are “contractors” are only allowed to invoke endpoints on weekdays.

Since every OPA policy is a Topaz policy, you can bring the full power of OPA and Rego to bear on your custom authorization policies.

## The API Authorization model

Click the [Directory](https://console.aserto.com/ui/directory/model) tab, and the _Edit manifest_ button. This will show you the API Authorization model definition. The important type definitions are for _service_ and _endpoint_.

```yaml
methods:
  # user represents a user that can be granted role(s)
  user:
    relations:
      manager: user

permissions:
      ### display_name: user#in_management_chain ###
      in_management_chain: manager | manager->in_management_chain

# group represents a collection of users and/or (nested) groups
  group:
    relations:
      member: user | group#member

# identity represents a collection of identities for users
  identity:
    relations:
      identifier: user

# service represents a set of endpoints
  service:
    relations:
      owner: user
      deleter: user | group#member
      creator: user | group#member
      writer: user | group#member
      reader: user | group#member

permissions:
      can_get: reader | can_put
      can_put: writer | can_post
      can_patch: writer | can_post
      can_post: creator | can_delete
      can_delete: deleter | owner

# endpoint represents a specific API endpoint
  endpoint:
    relations:
      # each endpoint picks the reader/writer/creator/deleter relation to the service
      # based on the method (GET -> reader, PUT/PATCH -> writer, etc)
      service-reader: service
      service-writer: service
      service-creator: service
      service-deleter: service
      # invoker allows a user or group to get access to invoke this specific endpoint
      invoker: user | group#member
    permissions:
      can_invoke: invoker | service-reader->can_get | service-writer->can_put |
        service-creator->can_post | service-deleter->can_delete
```

Let’s start at the bottom. An _endpoint_ has a _can_invoke_ permission, which is directly assignable by creating an _invoker_ relationship to a user or a group. This allows an API administrator to entitle a user or a group directly on a discrete endpoint.

The _endpoint_ also has relations called _service-reader_, _service-writer_, _service-creator_, and _service-deleter_ which ladder up to the enclosing _service_. The default transformation that occurs when importing an OpenAPI definition is to set the relationship of the endpoint based on its HTTP method - a _GET_ creates the _service-reader_ relation, a _PUT_ or _PATCH_ creates the _service-writer_ relation, a _POST_ uses the _service-creator_ relation, and a _DELETE_ uses the _service-deleter_ relation. This allows the _can_invoke_ permission to ALSO be assignable via relationships that a user or group has to the _service_. You can of course customize this default transform if you have different conventions or needs.

Now, let’s look at the _service_ type. A service has discrete permissions called _can_get_, _can_put_, _can_patch_, _can_post_, and _can_delete_ which are assignable through the _reader_, _writer_, _creator_, _deleter_, and _owner_ relations on the _service_. These permissions are additive, in the sense that a _deleter_ can invoke _DELETE_ endpoints, and can also do anything that a _creator_ can do. A _creator_ can _POST_, and can also do anything that a _writer_ can do… and so on.

To put it all together, users (or groups) can be entitled at the level of an entire service, at the level of a class of endpoints on a service (e.g. all GET endpoints), or at the level of a discrete endpoint. This provides a lot of flexibility in API entitlement, while keeping things simple and consistent.

Next, let’s look at the _user_, _group_, _service_, and _endpoint_ instance data.

## Authorization data

Click on the _Objects_ tab, and within that the [Service](https://console.aserto.com/ui/directory/objects/service) type. You should see the three services that were automatically added by the template.

Let’s follow the trail of entitlements from users and groups to the services and endpoints. Click on the [User](https://console.aserto.com/ui/directory/objects/user) type, which should show the five Citadel users - _Beth_, _Jerry_, _Morty_, _Rick_, and _Summer_.

Let’s click on [Rick](https://console.aserto.com/ui/directory/objects/user/rick%40the-citadel.com/graph). As you can see, Rick is a member of the _Global Deleters_ group.

Next, click on the [Global Deleters](https://console.aserto.com/ui/directory/objects/group/global-deleters/graph) group.

This group aggregates the _deleters_ group for each service. This pattern makes _Rick_ a super-user - he can invoke any endpoint in the system, since the members of the _Petstore API Deleters, Rick and Morty API Deleters_, and _Todo API Deleters_ groups can invoke any endpoint on the respective services, and being a member of the _Global Deleters_ group means that Rick is transitively a member of these groups.

Next, let’s look at _Morty_ - click the User type and then click [Morty](https://console.aserto.com/ui/directory/objects/user/morty%40the-citadel.com/graph). Morty is a member of the _Petstore API Creators_ group, which means he can invoke any _Petstore_ endpoint that is not a _DELETE_. This demonstrates the pattern of how to entitle a user on a set of methods within a service.

Finally, let’s go back to the _Service_ type and click the [Todo List API](https://console.aserto.com/ui/directory/objects/service/todo/graph). This shows the six endpoints that are part of this API.

The group called _Todo List API Readers_ is a reader of the service, meaning every member is entitled to invoke all the _GET_ endpoints on this service. Click on the [Todo List API Readers](https://console.aserto.com/ui/directory/objects/group/todo-readers/graph), and follow the trail of nested groups. As you can see, it includes the _viewer-group_, which comes from the Citadel IDP.

Clicking the [viewer-group](https://console.aserto.com/ui/directory/objects/group/viewer/graph) reveals its members - _Beth_ and _Jerry_, as well as the members of the _editor-group_. Clicking the [editor-group](https://console.aserto.com/ui/directory/objects/group/editor/graph) reveals its members -  _Morty_ and _Summer_, as well as members of the _admin-group_. And the [admin-group](https://console.aserto.com/ui/directory/objects/group/admin/graph) includes Rick. So, transitively, every one of our protagonists are entitled to invoke any _GET_ endpoint on the _Todo API Service_.

This pattern shows how to use nested groups in the IDP to control entitlements to classes of endpoints (in this case, the _GET_ endpoints) in a service.

## Integrating authorization with an API gateway

Let’s use [Zuplo](https://www.zuplo.com/) as an example of a modern API gateway. Create a free account, and use their Todo sample template. This should result in something like this:

### Add inbound policies to the routes

Next, click the _routes.oas.json_ file on the left navbar, and click the _GET /v1/todos_ endpoint. Open the _Policies_ chevron.

Add a new policy called _API Key Authentication_:

Add another below it using the policy type _Aserto Authorization_.

You can call the _Aserto Authorization_ policy _aserto-authz-inbound_, and configure it using the values shown below.

```json
{
  "export": "AsertoAuthZInboundPolicy",
  "module": "$import(@zuplo/runtime)",
  "options": {
    "tenantId": "tenant-id",
    "authorizerApiKey": "authorizer-api-key",
    "policyName": "api-auth",
    "serviceName": "todo"
    "userSubPropertyPath": ".data.email"
  }
}
```

Note that the values for _tenantId_ and _authorizerApiKey_ should come from the [Settings](https://console.aserto.com/ui/policies/api-auth/settings) tab of the _api-auth_ policy instance in the Aserto Console.

You can repeat this process for all six routes, but after you’ve done it once, you can just select the policies you’ve already selected from the top of the policy selector (in other words, you only have to create and configure the _aserto-authz-inbound_ once).

Now you can save your work by pressing _command-S_.

### Set up API keys

Lastly, you’ll need to set up API Key consumers. Click the _Services_ tab and the _API Key Service_.

Create a consumer for each of Rick and Morty:

Ensure that the _metadata_ for Rick is the following:

```json
{
  "sub": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs",
  "email": "rick@the-citadel.com"
}
```

The metadata for Morty has a slightly different _sub_ claim and Morty's email:

```json
{
  "sub": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs",
  "email": "morty@the-citadel.com"
}
```

Now we have the two users set up, which we can use in the _Authorization_ header of the requests that we send the gateway. The _API key inbound_ policy will check that the user has a valid API key, and make it available in the _aserto-authz-inbound_ policy. This will be the _subject_ that the Aserto policy will pass to the Aserto hosted authorization service, along with the _tenant ID_, _API key_, _policy name_, and _service name_ that we configured above. The subject passed into the Aserto authorizer will come from the `.data.email` field that we placed in the API key metadata.

Make sure you copy the API keys associated with Rick and with Morty by using the "copy handles" next to each one - we’ll use them in the next section.

### Testing the APIs

We can finally test our APIs using Zuplo’s Test modal. Go back to the _routes.oas.json_ file in the left navbar, click the _DELETE /v1/todos/{todoId}_ route, and click the _Test_ button.

Now, fill in an arbitrary value for the todo ID, and enter the Authorization header and its value:

In the screenshot you’ll see that we also put in some dummy headers - _AuthorizationRick_ and _AuthorizationMorty_, just so we can copy-paste these headers (and their Bearer tokens) into the _Authorization_ header value. This is a convenient way to flip between invoking the service as _Rick_ and as _Morty_.

First, let’s try as _Rick_. Copy and paste the value of the _AuthorizationRick_ header into the _Authorization_ header value. Then click the _Test_ button. You should see an HTTP _200 OK_ status, and the logs should show that Aserto returned the resulting _allowed_ decision as _true_. This is as expected, since _Rick_ is a superuser and can invoke all endpoints by virtue of being a _Global Deleter_.

Next, paste Morty’s bearer token into the _Authorization_ header, and click _Test_. You should see an HTTP _403 Forbidden_ status, as expected, since Morty is a member of the _viewer-group_, which can only invoke the GET APIs on the _Todo API_ service.

### Break the glass

Finally, let’s simulate a “break the glass” scenario where Morty needs access to the _DELETE /v1/todos/{todoId}_ endpoint.

Go to the Aserto Console, click the _Directory_ tab, and the [Endpoint](https://console.aserto.com/ui/directory/objects/endpoint) type. Type “delete” in the _Find input_ and click the _todo:DELETE:/v1/todos/{todoId}_ [endpoint](https://console.aserto.com/ui/directory/objects/endpoint/todo%3ADELETE%3A%2Fv1%2Ftodos%2F%7BtodoId%7D/graph).

Click the Outgoing relations tab, and the [invoker](https://console.aserto.com/ui/directory/objects/endpoint/todo%3ADELETE%3A%2Fv1%2Ftodos%2F%7BtodoId%7D/outgoing-relations/endpoint/invoker) relation:

Click the _Add a relation_ button and select _User_ for the type and _Morty_ for the instance. Click _Add relation_ and you should see _Morty_ as a direct assignee of the _invoker_ relation.

Go back to the Zuplo console, and click the _Test_ button again. You should now see an HTTP _200 OK_ status code, indicating that Morty is now able to invoke the _DELETE /v1/todos/{todoId}_ endpoint. If you go back to the Aserto Console and delete the relation you just added, Morty will once again receive an HTTP _403 Forbidden_ status.

## Governance

Before we wrap up, it’s worth noting that with the ReBAC model, we can now trivially find out which users are able to invoke which endpoints.

Go back to the Aserto Console, click on the _Directory_ tab, and the [Evaluator](https://console.aserto.com/ui/directory/evaluator). Select the request called _“Find objects that a user can access”_, and select _Beth_, the _Endpoint_ type, and the _can_invoke_ permission.

Clicking the “ _Play”_ button invokes the query, and shows that _Beth_ can only invoke the three _GET_ endpoints on the _Todo API_ service.

This makes sense because she’s a member of the _viewer-group_, and doesn’t have any additional entitlements. Play around with other users such as Morty and Rick to get different results, and feel free to copy the request as a cURL and execute it from a terminal to see how to invoke this type of request as a REST call.

```bash
curl 'https://directory.prod.aserto.com/api/v3/directory/graph/endpoint/can_invoke/user?object_id=&subject_id=beth%40the-smiths.com' \
          -H 'aserto-tenant-id: <your-tenant-id>' \
          -H 'authorization: basic <your-dir-api-key>' \
          -H 'content-type: application/json'
```

Lastly, we can ask the question in the opposite direction - which users can invoke an endpoint? Select the _“Find users that can access an object”_ request, select the first endpoint ( _DELETE /v1/todos/{todoId}_), and select the _can_invoke_ permission. You should see only a single user - _Rick_ - that is entitled to invoke that endpoint.

## Summary

This tutorial covered a lot of ground - the API Authorization model, importing an OpenAPI spec, entitling users on services and endpoints, calling Aserto / Topaz from an API gateway, and answering governance questions.

This is only the tip of the iceberg, but should hopefully show the potential of setting up a scalable way to perform fine-grained API authorization for all your services, enforced at the API gateway.

Aserto has a CLI toolchain that enables organizations to easily onboard a new service from its OpenAPI spec, creating the possibility of a CI/CD pipeline that easily adds new services (or handles adding new endpoints) in an automated fashion.

Entitling users to endpoints can be done by adding users to IDP groups (which are imported into Aserto), and break-the-glass scenarios can be achieved in the Aserto Console, through the _aserto_/ _topaz_ CLI, through the REST or gRPC APIs, or one of our [language SDKs](https://docs.aserto.com/docs/software-development-kits/overview).

We hope you enjoyed this tutorial. We have a longer-form video version [here](https://youtu.be/qn6c-XNLdqw?si=iADFtx8jCva44yH1), and if you have any questions, feel free to [contact us](/content/contact/index.html) or join our [community slack](/content/slack/index.html).

### Happy hacking!
