# Access Control

> Cordium documentation. Canonical page: <https://octelium.com/docs/cordium/latest/management/access-control>.

Cordium does not implement an identity or access control system of its own. Every Cordium endpoint is an Octelium *Service*, every Cordium *User* is an Octelium *User*, and every request is authorized by Octelium *Policies* (read more about *Policies* [here](https://octelium.com/docs/octelium/latest/management/core/policy.md)). On top of that, the Cordium API enforces the ownership of *Workspaces* and the roles of *Space* *Members* (read more [here](https://octelium.com/docs/cordium/latest/workspaces/spaces.md#members-and-roles)). This page explains how to authorize your *Users*, your administrators and your *Workspaces*.

## Cordium Services

Cordium is exposed via the following Octelium *Services*:

| Service                        | Description                                                                                                                                                                                                                                                | Default access                                                                                                                                                   |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `default.cordium`              | The web portal and the *Workspace* applications.                                                                                                                                                                                                           | Denied unless allowed by a *Policy*.                                                                                                                             |
| `default-cordium.octelium-api` | The Cordium gRPC API with its 3 gRPC services: `MainService` (*Spaces*, *Templates*, *Workspaces*, *Secrets*, *Volumes*, snapshots, etc.), `WorkspaceService` (terminals, `cordium exec` and log streaming) and `ManagementService` (the `ClusterConfig`). | `MainService` is allowed to all *Users* via the *Service*'s inline *Policy*. `WorkspaceService` and `ManagementService` are denied unless allowed by a *Policy*. |
| `default-ssh.cordium`          | The SSH endpoint used by `cordium ssh`, `cordium cp` and IDEs.                                                                                                                                                                                             | Allowed to all *Users*. The SSH server additionally checks that the *User* owns the target *Workspace*.                                                          |

*Services* of other *Regions* follow the same naming (e.g. `eu-west.cordium`, `eu-west-cordium.octelium-api` and `eu-west-ssh.cordium`).

## Authorizing Users

Here is an example of a *Policy* that grants the members of the `engineering` and `ml` *Groups* the full use of Cordium:

```yaml
kind: Policy
metadata:
  name: cordium-users
spec:
  rules:
    - effect: ALLOW
      condition:
        any:
          of:
            - match: ctx.service.metadata.name == "default.cordium"
            - match: >-
                ctx.service.metadata.name == "default-cordium.octelium-api" &&
                ctx.request.grpc.serviceFullName == "octelium.api.main.cordium.v1.WorkspaceService"
---
kind: Group
metadata:
  name: engineering
spec:
  authorization:
    policies: ["cordium-users"]
```

You can apply it via `octeliumctl apply` (read more [here](https://octelium.com/docs/octelium/latest/management/core/overview.md)). Workload *Users* (e.g. your CI or an orchestrator using the SDKs) are authorized in exactly the same way. A workload that only creates and stops *Workspaces* (e.g. with `autoStop`) only needs `MainService`, while a workload that runs commands inside them via `Exec` also needs `WorkspaceService`.

> **Note:**
>
> Remember that `ALLOW` rules are overridden by matching `DENY` rules. You can, for example, keep certain *Users* from using Cordium at all with a `DENY` rule on `ctx.service.metadata.name == "default-cordium.octelium-api"` and `ctx.service.metadata.name == "default.cordium"`.

## Cluster Administrators

The `ManagementService` of the Cordium API, which reads and updates the `ClusterConfig`, is only accessible to *Users* who are explicitly allowed by a *Policy*. Here is an example of a *Policy* that grants it to the `platform-admins` *Group*:

```yaml
kind: Policy
metadata:
  name: cordium-admins
spec:
  rules:
    - effect: ALLOW
      condition:
        match: >-
          ctx.service.metadata.name == "default-cordium.octelium-api" &&
          ctx.request.grpc.serviceFullName == "octelium.api.main.cordium.v1.ManagementService"
---
kind: Group
metadata:
  name: platform-admins
spec:
  authorization:
    policies: ["cordium-users", "cordium-admins"]
```

> **Note:**
>
> Since the `ManagementService` is protected exclusively by Octelium *Policies*, *Users* who are granted broad access to the *Cluster* (e.g. via the `allow-all` *Policy*) are effectively Cordium administrators as well. Only grant such access to the *Users* who actually need it.

## Restricting the Cordium API

Since every Cordium API call is an Octelium gRPC request, you can restrict specific operations via `ctx.request.grpc.service` and `ctx.request.grpc.method`, in addition to the *User*'s identity and context. Here is an example that keeps contractors from creating *Spaces* and snapshots, and from running commands in their *Workspaces* via `cordium exec`:

```yaml
kind: Policy
metadata:
  name: cordium-contractors
spec:
  rules:
    - effect: DENY
      condition:
        all:
          of:
            - match: ctx.service.metadata.name == "default-cordium.octelium-api"
            - match: ctx.request.grpc.method in ["CreateSpace", "CreateWorkspaceSnapshot", "Exec"]
---
kind: Group
metadata:
  name: contractors
spec:
  authorization:
    policies: ["cordium-users", "cordium-contractors"]
```

Every request is also logged by Octelium's access logs, including the gRPC method and the identity of the caller, which gives you a complete audit trail of all Cordium operations (read more [here](https://octelium.com/docs/octelium/latest/management/core/visibility.md)).

## Policies for Workspace Sessions

Every *Workspace* run is an Octelium *Session* of the *Workspace*'s owner. As a result, the processes running inside a *Workspace*, including the `cordium`, `octelium` and `octeliumctl` CLIs and any AI agent, act with the permissions of its owner. The *Session* of a *Workspace*, however, carries the identity of the *Workspace*, its *Space* and its *Template* in `ctx.session.status.ext.cordium` (read more [here](https://octelium.com/docs/cordium/latest/workspaces/secretless.md#policies-for-workspaces)), which lets you grant *Workspaces* less than their owners.

Here is an example of a global *Policy* that keeps all *Workspaces* from managing the *Cluster*, i.e. from using the Octelium administration API and the Cordium `ManagementService`, even when their owners are administrators. A compromised dependency or an AI agent running inside an administrator's *Workspace* therefore cannot escalate to the *Cluster*'s configuration. Note that the Octelium user API (i.e. `octelium.api.main.user.v1.MainService`) must remain accessible since it is used by `octelium connect` inside every *Workspace*:

```yaml
kind: Policy
metadata:
  name: deny-workspace-administration
spec:
  rules:
    - effect: DENY
      condition:
        all:
          of:
            - match: '"ext" in ctx.session.status && "cordium" in ctx.session.status.ext'
            - any:
                of:
                  - match: >-
                      ctx.service.metadata.name == "default.octelium-api" &&
                      ctx.request.grpc.serviceFullName != "octelium.api.main.user.v1.MainService"
                  - match: >-
                      ctx.service.metadata.name == "default-cordium.octelium-api" &&
                      ctx.request.grpc.serviceFullName == "octelium.api.main.cordium.v1.ManagementService"
```

You can make it effective for all the requests of the *Cluster* by adding it to the global *Policies* of the Octelium `ClusterConfig` (read more [here](https://octelium.com/docs/octelium/latest/management/core/policy.md#global-policies)):

```yaml
kind: ClusterConfig
spec:
  authorization:
    policies: ["deny-workspace-administration"]
```

Here is another example that only lets the *Workspaces* of the `agents` *Template* of the `payments` *Space* reach the *Services* of the `staging` *Namespace*, in addition to the APIs in the `octelium-api` *Namespace* that are needed by `octelium connect` and the `cordium` CLI:

```yaml
kind: Policy
metadata:
  name: agents-staging-only
spec:
  rules:
    - effect: DENY
      condition:
        all:
          of:
            - match: >-
                "ext" in ctx.session.status && "cordium" in ctx.session.status.ext &&
                ctx.session.status.ext.cordium.templateRef.name == "agents.payments.cordium"
            - match: '!(ctx.namespace.metadata.name in ["staging", "octelium-api"])'
```

> **Note:**
>
> Combine such restrictions with the `deny-workspace-administration` *Policy* above, so that the exception for the `octelium-api` *Namespace* does not include the administration APIs.
