# Cedar RBAC Plugin for MCP Gateway

> Author: Shriti Priya
> Version: 0.1.0

A plugin that evaluates Cedar policies and user‑friendly custom-DSL policies on incoming requests, and then allows or denies those requests using RBAC-based decisions which are enforced in cedar language and using library `cedarpy`.

## Cedar Language

Cedar is an open-source language and specification for defining and evaluating permission policies. It allows you to specify who is authorized to perform which actions within your application.
For more details: https://www.cedarpolicy.com/en

## RBAC

Role-based access control (RBAC) is an authorization model where permissions are attached to roles (like admin, manager, viewer), and users are assigned to those roles instead of getting permissions directly. This makes access control easier to manage and reason about in larger systems.

## CedarPolicyPlugin

This plugin supports two ways of defining policies in the configuration file, controlled by the `policy_lang` parameter.

### Cedar Mode

`plugins/external/cedar/resources/config.yaml`

When `policy_lang` is set to cedar, policies are written in the Cedar language under the policy key, using the following structure:

```yaml
      - id: allow-employee-basic-access
        effect: Permit
        principal: Role::"employee"
        action:
        - Action::"get_leave_balance" #tool name
        - Action::"request_certificate"
        resource:
        - Server::"askHR" # mcp-server name
        - Agent::"employee_agent" # agent name
```
1. **id** is a unique string identifier for the policy.
2. **effect** can be either Permit or Forbid and determines whether matching requests are allowed or denied.
3. **principal** specifies who the policy applies to; here it targets the employee role.
4. **action** lists one or more tools that the principal is attempting to invoke. It could also be actions controlling the visibility of output, either to see full output or redacted output based on user role.
5. **resource** lists the servers, agents, prompts and resources that the actions can target.

### Custom DSL mode

`plugins/external/cedar/examples/config-dsl.yaml`

When `policy_lang` is set to `custom_dsl`, policies are written in a compact, human-readable mini-language as a YAML multiline string. This allows non-experts to define role, resource, and action in a single, easy-to-scan block.
following syntax:


## Syntax

Policies use the following basic pattern:

```
[role:<role_name>:<resource>/<resource_name>]
<action_1>
<action_2>
```

For example:

```yaml
    [role:hr:server/hr_tool]
    update_payroll
```

In this example, role is hr, resource is server, and action is hr_tool. The line update_payroll represents the specific operation being authorized for that role–resource–action tuple.


## Configuration

1. **policy_lang**: Specifies the policy language used, `cedar` or `custom_dsl`.
2. **policy_output_keywords**: Defines keywords for output views such as `view_full_output` and `view_redacted_output` which can be used in policies or applications to control the output visibility.
3. **policy_redaction_spec**: Contains a regex pattern for redaction; in this case, the pattern matches currency-like strings (e.g., "$123,456") for potential redaction in the policy output, protecting sensitive information.
4. **policy**: Defines the RBAC policy

## Installation

1. In the folder `plugins/external/cedar`,  copy `.env.example` to `.env` file.
2. If you are using `policy_lang` to be `cedar`, add the plugin configuration to `plugins/external/cedar/resources/plugins/config.yaml`:

```yaml
plugins:
  - name: "CedarPolicyPlugin"
    kind: "cedarpolicyplugin.plugin.CedarPolicyPlugin"
    description: "A plugin that does policy decision and enforcement using cedar"
    version: "0.1.0"
    author: "Shriti Priya"
    hooks: ["prompt_pre_fetch", "prompt_post_fetch", "tool_pre_invoke", "tool_post_invoke", "resource_pre_fetch", "resource_post_fetch"]
    tags: ["plugin"]
    mode: "enforce"  # enforce | permissive | disabled
    priority: 150
    conditions:
      # Apply to specific tools/servers
      - server_ids: []  # Apply to all servers
        tenant_ids: []  # Apply to all tenants
    config:
      policy_lang: cedar
      policy_output_keywords:
        view_full: "view_full_output"
        view_redacted: "view_redacted_output"
      policy_redaction_spec:
        pattern: '"\$\d{1,}(,\d{1,})*"' # provide regex, if none, then replace all
      policy:
        ### Tool invocation policies ###
        - id: allow-employee-basic-access
          effect: Permit
          principal: Role::"employee"
          action:
            - Action::"get_leave_balance" #tool name
            - Action::"request_certificate"
          resource:
            - Server::"askHR" # mcp-server name
            - Agent::"employee_agent" # agent name

        - id: allow-manager-full-access
          effect: Permit
          principal: Role::"manager"
          action:
            - Action::"get_leave_balance"
            - Action::"approve_leave"
            - Action::"promote_employee"
            - Action::"view_performance"
            - Action::"view_full_output"
          resource:
            - Agent::"manager_agent"
            - Server::"payroll_tool"

        - id: allow-hr-hr_tool
          effect: Permit
          principal: Role::"hr"
          action:
            - Action::"update_payroll"
            - Action::"view_performance"
            - Action::"view_full_output"
          resource: Server::"hr_tool"

        - id: redact-non-manager-views
          effect: Permit
          principal: Role::"employee"
          action: Action::"view_redacted_output"
          resource:
            - Server::"payroll_tool"
            - Agent::"manager_agent"
            - Server::"askHR"

        ### Resource invocation policies ###
        - id: allow-admin-resources # policy for resources
          effect: Permit
          principal: Role::"admin"
          action:
            - Action::"view_full_output"
          resource: Resource::""https://example.com/data"" #Resource::<resource_uri>

        - id: allow-employee-redacted-resources # policy for resources
          effect: Permit
          principal: Role::"employee"
          action:
            - Action::"view_redacted_output"
          resource: Resource::""https://example.com/data"" #Resource::<resource_uri>

        ### Prompt invocation policies ###
        - id: allow-admin-prompts # policy for resources
          effect: Permit
          principal: Role::"admin"
          action:
            - Action::"view_full_output"
          resource: Prompt::"judge_prompts" #Prompt::<prompt_name>


        - id: allow-employee-redacted-prompts # policy for resources
          effect: Permit
          principal: Role::"employee"
          action:
            - Action::"view_redacted_output"
          resource: Prompt::"judge_prompts" #Prompt::<prompt_name>

```

#### Tool Invocation Policies

For the RBAC policy related to `tool_pre_invoke` and `tool_post_invoke`
Example:
```yaml
        - id: allow-employee-basic-access
          effect: Permit
          principal: Role::"employee"
          action:
            - Action::"get_leave_balance" #tool name
            - Action::"request_certificate"
          resource:
            - Server::"askHR" # mcp-server name
            - Agent::"employee_agent" # agent name
```

Here, user with role `employee` (**Role**) is only allowed to invoke tool `get_leave_balance` (**Action**) belonging to the MCP server or (**Server**).

In another policy defined for tools

```yaml

        - id: allow-hr-hr_tool
          effect: Permit
          principal: Role::"hr"
          action:
            - Action::"update_payroll"
            - Action::"view_performance"
            - Action::"view_full_output"
          resource: Server::"hr_tool"

        - id: redact-non-manager-views
          effect: Permit
          principal: Role::"employee"
          action: Action::"view_redacted_output"
          resource:
            - Server::"payroll_tool"
            - Agent::"manager_agent"
            - Server::"askHR"
```


The actions like `view_full_output` and `view_redacted_output` has been used. This basically controls the
level of output visibile to the user. In the above policy, user with role `hr` is only allowed to view the output of `update_payroll`. Similary for the second policy, user with role `employee` is only allowed to view redacted output of the tool.


#### Prompt Invocation Policies


```yaml

        ### Prompt invocation policies ###
        - id: allow-admin-prompts # policy for resources
          effect: Permit
          principal: Role::"admin"
          action:
            - Action::"view_full_output"
          resource: Prompt::"judge_prompts" #Prompt::<prompt_name>


        - id: allow-employee-redacted-prompts # policy for resources
          effect: Permit
          principal: Role::"employee"
          action:
            - Action::"view_redacted_output"
          resource: Prompt::"judge_prompts" #Prompt::<prompt_name>
```

Here, in the above polcicy, given a prompt template `judge_prompts`, user of role `admin` is only allowed to view full prompt. However, if a user is of role `employee`, then it could only see redacted version of the prompt.


#### Resource Invocation Policies

**NOTE:** Please don't be confused with the word resource in cedar to the word resource in MCP ContextForge.

```yaml

        - id: allow-admin-resources # policy for resources
          effect: Permit
          principal: Role::"admin"
          action:
            - Action::"view_full_output"
          resource: Resource::"https://example.com/data" #Resource::<resource_uri>

        - id: allow-employee-redacted-resources # policy for resources
          effect: Permit
          principal: Role::"employee"
          action:
            - Action::"view_redacted_output"
          resource: Resource::"https://example.com/data" #Resource::<resource_uri>
```

Here, `Resource` word used in policy, is if resource hooks are invoked. So, in the above policy,
user with role `admin` is only allowed to view full output of uri `https://example.com/data`. Where, the user is of `employee` role, it can only see the redacted versionaaaaa of the resource output.


#### policy_output_keywords

```
        view_full: "view_full_output"
        view_redacted: "view_redacted_output"
```

has been provided, so everytime a user defines a policy, if it wants to control the output visibility of
any of the tool, prompt, resource or agent in MCP gateway, it can provide the keyword, it's supposed to use in the policy in `policy_output_keywords`. CedarPolicyPlugin will internally use this mapping to redact or fully display the tool, prompt or resource response in post hooks.




3. Now, the policy and plugin configurations are defined in `resources/config.yaml` file, next step is build this as an external MCP server.

* `make venv`: This will create a virtual environment to develop or build your plugin.
* `make install && make install-dev`: To install all the required libraries in the environment.
* `make build`:  This will build a docker image named `mcpgateway/cedarpolicyplugin`
* `make start`: This will start the cedarpolicyplugin container.

This confirms that your container is running fine:
```
WARNING:mcpgateway.observability:OpenTelemetry not installed. Proceeding with graceful fallbacks.
INFO:     Started server process [9]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:55196 - "GET /health HTTP/1.1" 200 OK

```

4. Now, you can add this external plugin configuration, in `plugins/config.yaml`:
3. The next step is to enable the opa plugin which you can do by adding `PLUGINS_ENABLED=true` and the following blob in `plugins/config.yaml` file. This will indicate that OPA Plugin is running as an external MCP server.

  ```yaml
  - name: "CedarPolicyPlugin"
    kind: "external"
    priority: 10 # adjust the priority
    mcp:
      proto: STREAMABLEHTTP
      url: http://127.0.0.1:8000/mcp
  ```

## Testing

There are set of test cases in the `cedar/tests` folder. The file named `test_cedarpolicyplugin.py` file which contains detailed test cases for RBAC policies enforced on tools, prompts and resources.
run `make test` to run all the test cases.

## End to End Testing from the UI for ContextForge MCP Gateway

Run the following from the project root folder:

```bash
# Generate JWT bearer token
python3 -m mcpgateway.utils.create_jwt_token --username admin@example.com --exp 10080 --secret my-test-key

# Export for API calls
export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token --username admin@example.com --exp 0 --secret my-test-key)
```


1. Add server fast-time that exposes git tools in the mcp gateway

Run the mcp server

```bash
python3 -m mcpgateway.translate \
     --stdio "uvx mcp-server-git" \
     --expose-sse \
     --expose-streamable-http \
     --port 9000
```

Add it to the gateway
```bash
curl -s -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"name":"fast-time","url":"http://localhost:9000/sse"}' \
     http://localhost:4444/gateways
```

2. This adds server to the gateway and exposes all the tools for git. You would see `fast-time-git-status` as the tool appearing in the tools tab of mcp gateway.

3. The next step is to run CedarPolicyPlugin as an external MCP server
Go into `plugins/external/cedar` and run the following:
- `make venv`, and enter the environment
- `make install && make install-dev` installs all the packages
- `make build`
- `make start`

This will start CedarPolicyPlugin on port `8000`.

4. Next step, is to add CedarPolicyPlugin in plugins by `PLUGINS_ENABLED=true` and the following blob in `plugins/config.yaml` file. This will indicate that CedarPolicyPlugin is running as an external MCP server.

  ```yaml
  - name: "CedarPolicyPlugin"
    kind: "external"
    priority: 10 # adjust the priority
    mcp:
      proto: STREAMABLEHTTP
      url: http://127.0.0.1:8000/mcp
  ```

2. To test cedar plugin from the UI
Invoke the `fast-time-git-status` from the UI, with `admin` as role.
The request will be allowed by the CedarPolicyPlugin.

If you switch to a another tool which is not in the policy `fast-time-git-show` expect denial by the CedarPolicyPlugin. For more details into policy, `plugins/external/cedar/resources/config.yaml`
You get the following as output in the UI:
```json
  {
    "message": "Plugin Violation: tool_pre_invoke not allowed",
    "data": {
      "description": "tool_pre_invoke not allowed",
      "plugin_error_code": "DENY",
      "plugin_name": "CedarPolicyPlugin"
    }
  }
```


## Difference from OPAPlugin

The OPA plugin runs an OPA server to enforce policies, whereas the Cedar plugin uses the `cedarpy` library and performs policy enforcement locally without requiring an external service.
OPA plugin requires to know `rego` to define policies by user while the `Cedar` plugin can be defined either in `cedar` or user friendly `custom_dsl` language.
Right now, the cedar plugin enforces RBAC policies and it could be extended to enforce ABAC policies using the same plugin.
