# Request Routing

Request routing allows you to limit and direct just-in-time access requests, based on *who* is making the request and *which* resource is requested.

This page describes how to use and configure request routing rules.

* [Configuring Request Routing](#configuring-request-routing)
* [Request Routing Format](#request-routing-format)
* [Examples](#examples)
* [Evaluation of Routing Rules](#evaluation-of-routing-rules)

{% hint style="info" %}
Request Routing requires a pro-tier P0 subscription.
{% endhint %}

## Configuring Request Routing

To use request routing, go to p0.app and navigate to **Policy Studio**. Saving a routing configuration here configures your organization to use routed approvals, rather than default approvals.

<figure><img src="https://3783273641-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSQNwGQz62W737pY0FzVb%2Fuploads%2Fgit-blob-35397c393ee4c3531cfb79cdf2f535a25255eb56%2Fimage.png?alt=media" alt="" width="563"><figcaption></figcaption></figure>

## Request Routing Format

When working with Request Routing directly, you should use this reference page for specific definitions of how to format your request properly. This feature of p0 is powerful and customizable, and that's why we've made it accessible via a full API for those who want to use it that way.

### Structure of a "Workflow"

We call a collection of routing rules a Workflow, and when you want to make changes to the way your requests are routed in p0 (such as, sending engineers' request to their managers, or restricting access to a particular prod resource to a small group), you do so by submitting a new Workflow.

Keep in mind that you only have one Workflow active at any given time, but that one Workflow can have as many individual routing rules as you want. You do not need to separate out different rule types or anything like that.

Here's an example Workflow that you can copy into your app as a starting point:

```yaml
- requestor:
    type: group
    id: engineering@yourorg.com
    label: Engineering
    directory: workspace
  resource:
    type: integration
    service: snowflake
  approval:
    - type: group
      id: dataops@yourorg.com
      label: Data Ops Team
      directory: workspace
      options: {allowOneParty: false}
```

### Structure of a "Rule"

The core of workflows are the rules, so let's break them down.

### Requestor

The first part of a rule is a "requestor", which matches who makes the request. There are three types of requestors you can choose from:

#### "Any"

```yaml
requestor:
  type: any
```

This will match all requestors. Useful for global rules like fall-back restrictions of sensitive resources.

#### "Group"

```yaml
requestor:
  type: group
  id: <group identifier>
  label: <human-readable name>
  directory: azure-ad|okta|workspace
```

This rule will match any requestor that is a member of a group in your IdP. Currently, this supports Google Workspace, Microsoft Entra ID, and Okta.

**id**: For Google Workspace, this is the group email address (ie, <engineering@yourco.com>). For Entra, this is the Entra ID group's UUID. For Okta, this is the group ID found in the URL of the group's page in the admin console. See [Okta docs](https://support.okta.com/help/s/article/how-to-find-group-ids-through-the-okta-user-interface?language=en_US).

**label**: This is any friendly human-readable name you like, although P0 suggests using the same name as displayed in your directory.

**directory**: For Google Workspace this is "workspace", for Okta this is "okta", and for Entra ID, this is "azure-ad".

#### "User"

```yaml
requestor:
  type: user
  uid: <user's email address>
```

This rule will match only a specific user, as identified by their email address.

### Resource

The second part of a routing rule is the resource this rule should apply to.

#### "Any"

```yaml
resource:
  type: any
```

This will match all resources. This is useful, for instance, when you want to route access for a particular user, regardless of what resource or service they are looking to access.

#### "Integration"

```yaml
resource:
  type: integration
  service: <target service>
  accessType <target access type>
```

This rule will match a specific type of access request. For instance, maybe you want to create a rule that routes all AWS requests to your DevOps team. Or you want to restrict access to GCP to only a select group.

**service**: For GCloud, "gcloud", for AWS, "aws", for Microsoft Azure "azure", for Snowflake, "snowflake", and for SSH, "ssh".

**accessType** (Optional): The access type within the service that the rule should apply to, or "any" meaning the rule will match requests of any access type. Defaults to "any" if omitted.

**filters** (Optional):\
Filters allow you to apply the rule only to the service components that match the filtering condition.

```yaml
resource:
  type: integration
  service: aws|azure|azure-ad|gcloud|k8s|okta|snowflake|ssh
  filters:
    <access-type>:
      effect: keep|remove|removeAll
      key: <property>
      pattern: <regex pattern>
```

Each filter has an `filter-name` that refers to the type of the requested object. For instance, when "service" is "aws", the filter with the name "policy" will only allow requesting policies whose ARNs match the `pattern`.

Note that filters are independent when the access type is omitted or set to "any." For example, if you specify an `filter-name=permission` filter for `service=gcloud` requests, but omit a `filter-name=role` filter, then roles will be unaffected by the filter, i.e. all roles will remain requestable.

Each filter has three potential properties:

* **effect** - Describes how this filter is applied
  * **keep** - This rule will retain objects that match the pattern as requestable
  * **remove** - This rule will only retain objects that do not match the pattern
  * **removeAll** - This rule disables the object type entirely.
  * If you want to allow all objects of a type as requestable, omit the filter altogether
* **key** - Describes which property of the object the filter must match; only applies if "effect" is one of "keep" or "remove". For instance, if `service=aws` and `access-type=permission-set` then the key can be "name" or "arn". If "name", the regex pattern will be matched against the name of permission sets. If "arn", the entire arn is matched.
* **pattern** - A regex pattern used to match against the specified property; only applies if "effect" is one of "keep" or "remove"

{% hint style="info" %}
Patterns are unanchored. Use line-start (`^`) and line-end (`$`) markers to anchor patterns.

P0 uses the [JavaScript regex dialect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions).
{% endhint %}

*Filter behavior*

The behavior of filters depends on the requested access type.

For example, the "role" filter in the Gcloud integration has a slightly different meaning depending on which access type is being used used in the request:

* When making an GCP request with "role" as the access type (e.g. `p0 request gcloud role`), the filter limits the roles the user can request access to
* When making an GCP request with "resource" as the access type (e.g. `p0 request gcloud resource`), the filter limits the roles that the user can request on the resource they are requesting

By setting the rule's access type to either "role" or "resource", you can explicitly specify which of these requests is allowable.

{% hint style="warning" %}
To ensure predictable filtering behavior, P0 recommends setting an `accessType` constraint when using filters.
{% endhint %}

Valid filter filter-name / key combinations are summarized in the below table. For more detailed information on the filters, see the documentation for [AWS](https://docs.p0.dev/orchestration/just-in-time-access/request-routing/aws-filtering) and [Google Cloud](https://docs.p0.dev/orchestration/just-in-time-access/request-routing/google-cloud-filtering).

For more detailed information on the filters, see the documentation for [AWS](https://docs.p0.dev/orchestration/just-in-time-access/request-routing/aws-filtering), [Microsoft Azure](https://docs.p0.dev/orchestration/just-in-time-access/request-routing/microsoft-azure-filtering), [Google Cloud](https://docs.p0.dev/orchestration/just-in-time-access/request-routing/google-cloud-filtering), and [SSH](https://docs.p0.dev/orchestration/just-in-time-access/request-routing/ssh-filtering).

<table><thead><tr><th width="155">service</th><th width="147">access-type</th><th width="136">key</th><th>Notes</th></tr></thead><tbody><tr><td>aws</td><td>tag</td><td>&#x3C;tag key></td><td>Only filters policies and permission sets. AWS managed policies can't be tagged.</td></tr><tr><td></td><td>group</td><td>name</td><td>Filters IAM group names</td></tr><tr><td></td><td>permission-set</td><td>arn | name</td><td>Filters Identity Center permission sets</td></tr><tr><td></td><td>policy</td><td>arn</td><td>Filters policies with matching ARNs</td></tr><tr><td></td><td>resource</td><td>arn | name | service</td><td>Filters resource requests based on the resource ARN, the resource name, or the service the resource belongs to.</td></tr><tr><td>azure</td><td>subscription</td><td>id</td><td>Filters requests at a subscription level</td></tr><tr><td></td><td>resource</td><td>id | name</td><td>Filters requests based on what resource is requested</td></tr><tr><td></td><td>role</td><td>id | name</td><td>Filters requests based on the role requested</td></tr><tr><td>azure-ad|okta</td><td>group</td><td>id | label</td><td>Filters directory groups</td></tr><tr><td>gcloud</td><td>permission</td><td>id</td><td>Filters IAM permissions</td></tr><tr><td></td><td>role</td><td>id</td><td>Filters IAM roles based on full ID (for example, the ID for "Owner" is <code>roles/owner</code>)</td></tr><tr><td></td><td>resource</td><td>name | type | full-resource-name</td><td>Filters resource requests based on the resource name, resource type, or the full resource name.</td></tr><tr><td>snowflake</td><td>role</td><td>name</td><td>Filters roles</td></tr><tr><td>k8s</td><td>resource</td><td>kind | name | namespace</td><td>Filters resources</td></tr><tr><td></td><td>role</td><td>name</td><td>Filters roles and cluster roles</td></tr><tr><td></td><td>cluster</td><td>name</td><td>Filters clusters based on the cluster name specified when installed in P0</td></tr><tr><td>ssh</td><td>provider</td><td>id</td><td>Filters by cloud provider (<code>aws</code>, <code>gcloud</code>, <code>azure</code>, or <code>self-hosted</code>)</td></tr><tr><td></td><td>destination</td><td>arn | name | full-resource-name</td><td>Filters by instance identifier</td></tr><tr><td></td><td>group</td><td>name</td><td>Filters by SSH group name</td></tr><tr><td></td><td>parent</td><td>id</td><td>Filters by parent resource ID (AWS account ID, GCP project ID, or Azure subscription ID)</td></tr><tr><td></td><td>region</td><td>id</td><td>Filters by AWS region, GCP zone, or Azure region</td></tr><tr><td></td><td>sudo</td><td><em>(boolean)</em></td><td>Filters by whether sudo privileges are requested</td></tr></tbody></table>

Note that filters are not currently available for AWS or GCP resource-level grants, nor for Snowflake SQL grants.

*Example that covers each possible access type in the AWS service:*

1. Exclude all permission sets and policies containing "FullAccess"
2. Only allow AWS policies and permission sets where the tag "P0Grantable" is equal to the string "true"
3. Do not allow requesting any AWS groups

<pre><code><strong>- resource
</strong>    type: integration
    service: aws
    accessType: permission-set
    filters:
      permission-set:
        effect: remove
        key: name
        pattern: FullAccess
      tag:
        effect: keep
        key: P0Grantable
        pattern: ^true$
  requestor:
    type: any
  approval:
    - type: any

- resource
    type: integration
    service: aws
    accessType: policy
    filters:
      policy:
        effect: remove
        key: name
        pattern: FullAccess
      tag:
        effect: keep
        key: P0Grantable
        pattern: ^true$
  requestor:
    type: any
  approval:
    - type: any
    
- resource
    type: integration
    service: aws
    accessType: group
  requestor:
    type: any
  approval:
    - type: deny
</code></pre>

### Approval

The final part of a routing rule is the "approval". Unlike the requestor and resource parts, the approval part is an array of multiple rules, referring to the people/groups/services that can approve (or deny) access requests.

#### "p0"

```json
approval:
  - type: p0
    options: { allowOneParty: true|false, requireReason: true|false, breakGlassApprover: true|false }
```

This approval type is analogous to the default approval behavior. This rule routes approvals to the people designated as "Security Reviewers" on the Settings page of your app. The "options" key is optional. You can use it to:

* allow the requestor to approve their own requests for this flow with the `allowOneParty: true` setting. Defaults to `false`.
* require the requestor to specify a reason when submitting requests with the `requireReason: true` setting. Defaults to `false`.
* designate this approver as a break glass approver for SSH "all" access requests with the `breakGlassApprover: true` setting. Defaults to `false`. See [SSH Filtering](https://docs.p0.dev/orchestration/just-in-time-access/ssh-filtering#break-glass-access) for details.

#### "Group"

```yaml
approval:
  - type: group
    id: <group identifier>
    label: <human readable name>
    directory: azure-ad|okta|workspace
    options: { allowOneParty: true|false,  requireReason: true|false, breakGlassApprover: true|false }
```

This rule will match any requestor who is a member of a group in your Identity Provider. Currently, p0 supports Google Workspace, Microsoft Entra ID, and Okta.

**id**: For Google Workspace, this is the group email address (ie, <engineering@yourco.com>). For Entra ID, this is the Entra ID group's UUID. For Okta, this is the group ID found in the URL of the group's page in the admin console. See [Okta docs](https://support.okta.com/help/s/article/how-to-find-group-ids-through-the-okta-user-interface?language=en_US).

**label**: This is any friendly human-readable name you like, although P0 suggests using the same name as displayed in your directory. This label will be printed in approval notifications.

**directory**: For Google Workspace this is "workspace", for Okta this is "okta", and for Entra ID this is "azure-ad".

**options**: See ["p0" section](#p0)

#### "Auto"

<pre class="language-yaml"><code class="lang-yaml"><strong>approval:
</strong>  - type: auto
    integration: pagerduty|incidentio
    options: { requireReason: true|false }
</code></pre>

This rule automatically approves matching access requests for one hour.

**integration**: `"pagerduty"` or `"incidentio"`. If the requestor is currently on-call, their requests are automatically approved. For PagerDuty, P0 checks the selected escalation policies. For Incident.io, P0 checks all schedules in the account.

**options**: The "options" key is optional. You can use it to require the requestor to specify a reason when submitting requests with the `requireReason: true` setting. Defaults to `false`.

#### "Escalation"

<pre><code><strong>approval:
</strong>  - type: escalation
    integration: pagerduty|incidentio
    options: { allowOneParty: true|false,  requireReason: true|false, breakGlassApprover: true|false }
    services: [&#x3C;Service or Schedule ID>]
</code></pre>

This rule routes approval to users who are now on-call for the specified services or schedules. When combined with other approval rules, it enables on-call users to approve escalated requests.

**integration**: `"pagerduty"` or `"incidentio"`.

* For PagerDuty, escalation policies for the configured services determine on-call status. The system creates an incident against the specified services when it escalates the request.
* For Incident.io, on-call status is determined by the specified schedule IDs. Users who are on-call for any of the listed schedules can approve the request.

**options**: See ["p0" section](#p0).

**services**: List of PagerDuty service IDs or Incident.io schedule IDs, depending on the configured integration.

#### "Always allowed"

```yaml
approval:
  - type: persistent
```

Access is always granted automatically.

**options**: The "options" key is optional. You can use it to require the requestor to specify a reason when submitting requests with the `requireReason: true` setting. Defaults to `false`.

#### "Deny"

```yaml
approval:
  - type: deny
```

This rule will deny all requests that match. This supersedes other matching rules: if at least one "deny" rule matches, the request will be denied.

## Examples

Routing rules can be used for a variety of use cases.

#### Different access depending on organizational role

Configure different approvers and different resources for developers and customer-success engineers:

```yaml
- requestor:
    type: group
    id: devs@you.co
    label: Developers
    directory: workspace
  resource:
    type: any
  approval:
    - type: group
      id: sre@you.co
      label: SREs
      directory: workspace
      options: { allowOneParty: true }
- requestor:
    type: group
    id: customer-success@you.co
    label: Customer Success
    directory: workspace
  resource:
    type: integration
    service: snowflake
  approval:
    - type: group
      id: data-ops@you.co
      label: Data Ops
      directory: workspace
      options: { allowOneParty: false }
```

#### Different rules by AWS account

Development AWS account (123456789) allows one-party approvals. Staging AWS account (234567891) allows peer approvals but no one-party approvals. Production AWS account (345678912) requires a manager approver represented by an Okta group. Assumes an AWS Identity Center setup where resource filters allow requesting customer-managed policies in specific AWS accounts, enforced by an ARN match that includes the AWS account ID. Permission sets are implicitly allowed because the `permission-set` access type filter is not defined.

<pre class="language-yaml"><code class="lang-yaml"><strong>- requestor:
</strong>    type: group
    id: 00g5j4jojlGZMzfhM69
    label: Engineers
    directory: okta
  resource:
    type: integration
    service: aws
    accessType: resource
    filters:
      policy: { effect: keep, key: arn, pattern: ^arn:aws:iam::123456789:policy/ }
      group: { effect: removeAll }
  approval:
    - type: group
      id: 00g5j4jojlGZMzfhM69
      label: Engineers
      directory: okta
      options: { allowOneParty: true }
- requestor:
    type: group
    id: 00g5j4jojlGZMzfhM69
    label: Engineers
    directory: okta
  resource:
    type: integration
    service: aws
    accessType: resource
    filters:
      policy: { effect: keep, key: arn, pattern: ^arn:aws:iam::234567891:policy/ }
      group: { effect: removeAll }
  approval:
    - type: group
      id: 00g5j4jojlGZMzfhM69
      label: Engineers
      directory: okta
      options: { allowOneParty: false, requireReason: true }
- requestor:
    type: group
    id: 00g5j4jojlGZMzfhM69
    label: Engineers
    directory: okta
  resource:
    type: integration
    service: aws
    accessType: resource
    filters:
      policy: { effect: keep, key: arn, pattern: ^arn:aws:iam::345678912:policy/ }
      group: { effect: removeAll }
  approval:
    - type: group
      id: 01f5j4jfjlGZMzfhN99
      label: Managers
      directory: okta
      options: { allowOneParty: false, requireReason: true }
</code></pre>

#### Filter access to AWS policies and permission sets by tag

Restrict which AWS policies and permission sets can be requested to those for which a tag matches a specific value:

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: aws
    accessType: policy
    filters:
      tag: { effect: keep, key: P0Grantable, pattern: ^true$ }
  approval:
    - type: p0
- requestor:
    type: any
  resource:
    type: integration
    service: aws
    accessType: permission-set
    filters:
      tag: { effect: keep, key: P0Grantable, pattern: ^true$ }
  approval:
    - type: p0
```

#### Org- and resource-specific routing

Allow either the requestor's manager or the service owner to approve requests:

```
- requestor:
    type: group
    group: devs@you.co
    directory: workspace
  resource:
    type: any
  approval:
    - type: group
      id: eng-managers@you.c
      label: Eng managers
      directory: workspace
- requestor:
    type: group
    group: devs@you.co
    directory: workspace
  resource:
    type: integration
    service: gcloud
  approval:
    - type: group
      id: gcloud-owners@you.co
      label: GCloud owners
      directory: workspace
```

#### Mandatory reason for a specific resource type

Make reason mandatory for AWS requests

<pre class="language-yaml"><code class="lang-yaml"><strong>- resource:
</strong>    type: any
  approval:
    - options: {requireReason: false, allowOneParty: true}
      type: p0
  requestor:
    type: any
- resource:
    service: aws
    type: integration
  approval:
    - options: {requireReason: true}
      integration: pagerduty
      type: auto
  requestor:
    type: any
</code></pre>

#### Exclude cluster-admin role and only allow default namespace in Kubernetes

```yaml
- resource:
    type: integration
    service: k8s
    accessType: resource
    filters:
      resource:
        effect: keep
        key: namespace
        pattern: ^default$
      role:
        effect: remove
        key: name
        pattern: ClusterRole/cluster-admin
  approval:
    - type: p0
  requestor:
    type: any
```

#### Escalate request using PagerDuty for priority approval when requesting google cloud requests

```
- resource:
    service: gcloud
    type: integration
  approval:
    - type: p0
      options: {requireReason: true, allowOneParty: false}  
    - type: escalation
      integration: pagerduty
      options: {requireReason: true, allowOneParty: false}
      services: [PSJXXXG]
  requestor:
    type: any
```

#### Allow users to request access to Google Cloud roles, but not permissions

```
- resource:
    service: gcloud
    type: integration
    accessType: role
  approval:
    - type: p0
  requestor:
    type: any
- resource:
    service: gcloud
    type: integration
    accessType: permission
  approval:
    - type: deny
  requestor:
    type: any
```

#### Deny access to specific roles, but allow users to request others

```
- resource:
    service: gcloud
    type: integration
    accessType: role
    filters:
      role:
        effect: keep
        key: name
        pattern: roles/owner
  approval:
    - type: deny
  requestor:
    type: any
- resource:
    service: gcloud
    type: integration
    accessType: role
  approval:
    - type: p0
  requestor:
    type: any
```

## Evaluation of Routing Rules

1. Match Rules to the Request

   Identify rules that match the request. A rule matches if both the requestor and the resource criteria in the rule align with the request details.
2. Handle Missing Matches

   If no rules match, the access request is not created, and further evaluation stops. The requestor will see the following message:

   *This resource doesn't exist, or your organization doesn't allow this principal to access this resource*
3. Evaluate Access Control of Matching Rules

   For matching rules, review the Access Control field in the following order:

   * Deny all access\
     If *any* matching rule specifies "Deny all access", the request is denied immediately, and further evaluation stops.
   * Always allowed\
     If any rule specifies "Always allowed," the request is automatically approved and provisioned, and further evaluation stops.
   * Third-party auto-approval\
     If a rule specifies a third-party auto-approval, such as a PagerDuty or Incident.io on-call rule, the request is evaluated for automatic approval. If it cannot be auto-approved, proceed to the next step.
   * Manual approval\
     For all other Access Control values, the request requires manual approval by a designated person or group. When multiple rules with different manual approvers match, approval from any one approver is sufficient to provision the request. All approvers are notified.

### Key Points to Remember

* **Deny all access:** Always takes precedence and stops further evaluation immediately.
* **Always allowed:** Overrides other Access Control values and is prioritized in the evaluation process.
* **Multiple matching rules:** When multiple rules match the requestor and resource, approvers are combined using an **ANY** relationship, meaning approval from any one approver is sufficient.

### Examples

1. **Always allowed rule takes precedence over other allow rules**

<figure><img src="https://3783273641-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSQNwGQz62W737pY0FzVb%2Fuploads%2Fgit-blob-c014287654c65bf9b33cc62020af6ddcb888560a%2FScreenshot%202025-01-25%20at%205.55.14%E2%80%AFPM.png?alt=media" alt=""><figcaption><p>Standing access and selective allow rule applying to the same resource</p></figcaption></figure>

Consider a scenario where two rules apply to the same resource, **`node1`** in the **devstack** group:

* An "Always allow" rule grants access to **`node1`**.
* Another "Allow" rule requires approval before granting access.

In this case, the **"Always allow" rule** takes precedence. As a result:

* The user is immediately granted access to **`node1`** without requiring additional approval.

2. **Deny Rules are evaluated first, before ANY allow rules**

<figure><img src="https://3783273641-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSQNwGQz62W737pY0FzVb%2Fuploads%2Fgit-blob-5d2e2fd24e4f4b589e9bc346ed6b4f0883d9eade%2FScreenshot%202025-01-25%20at%206.14.47%E2%80%AFPM.png?alt=media" alt=""><figcaption></figcaption></figure>

Using the same example as before, if there is an additional rule that **denies access** to any instance containing **`node`** in its name, the system will evaluate the "Deny" rule first.

In this case:

* The "Deny" rule matches because **`node1`** contains the string **`node`** in its name.
* The user's request is immediately denied, and is not evaluated further.

This highlights the priority of "Deny" rules, ensuring they are enforced before any other rules are considered.
