# Authorization

> Concepts and configuration for authorization in Seldon Enterprise Platform

## User Identity

Once a user has been [authenticated](https://docs.seldon.ai/seldon-enterprise-platform/architecture/authentication), they have an identity in the form of an **OIDC ID token**. The token defines a **user ID** and **groups** to which the user belongs. This information can be used to authorize their actions.

The user ID is determined by a claim in the OIDC token. This defaults to `preferred_username`, but can be configured through the environment variable `USERID_CLAIM_KEY` in `install-values.yaml`. This can be used for both human users and service accounts. If it is not possible to share claim keys for both, it is possible to use the environment variable `SA_ID_CLAIM_KEY` for service accounts.

{% hint style="info" %}
**Note**: the `USERID_CLAIM_KEY` is always used if present. `SA_ID_CLAIM_KEY` is used only if `USERID_CLAIM_KEY` does not exist in the token.
{% endhint %}

```yaml
env:
  USERID_CLAIM_KEY: "preferred_username"
  SA_ID_CLAIM_KEY: "preferred_username"
```

The groups are determined by a claim in the OIDC token. This defaults to `groups`, but can be configured through the environment variable `GROUPS_CLAIM_KEY` in `install-values.yaml`.

```yaml
env:
  GROUPS_CLAIM_KEY: "groups"
```

{% hint style="info" %}
**Claim** is a [technical term](https://openid.net/specs/openid-connect-core-1_0.html#Terminology) in the OpenID specification. It means a piece of information about a user, or "entity". You can read more about claims [here](https://openid.net/specs/openid-connect-core-1_0.html#Claims).
{% endhint %}

## Permission Evaluation

Every call to the Seldon Enterprise Platform API is protected by authorization checks. This happens regardless of whether you are interacting with it via the UI, via the Python SDK, or directly.

Seldon Enterprise Platform operates on a **restrictive** model. This means that a user is assumed to have no permissions except those which they have been **explicitly** granted.

The effective list of permissions that a user has is the union of:

* Those that are granted to them by their user ID.
* Those that are granted to all users.
* Those that are granted to one or more of the groups to which the user belongs.

{% hint style="info" %}
**Note**: There is no way to define negative permissions, i.e. that a user is *denied* access to a particular resource.
{% endhint %}

## Resource Actions

There are three actions users can be granted for resources:

* **Read** for listing available resources and viewing particular ones.
* **Write** for creating, deleting, and modifying resources.
* **Grant** for granting other users permissions to specific resources.

These actions do **not** imply one another. For example, having `write` access does not also grant `read` access. Each action is handled completely independently in the OPA policies, and each grant must be specified explicitly.

## Resource Scopes

Resources in Enterprise Platform are grouped into three categories:

* [**Namespace-scoped resources**](#namespaces) that belong to a specific Kubernetes namespace. This includes ML deployments and artifact secrets.
* [**Project-scoped resources**](#projects), including models in the [Model Catalog](https://docs.seldon.ai/seldon-enterprise-platform/operations/model-metadata) and deployments using these. Projects are a Seldon Enterprise Platform concept, but not a Kubernetes one.
* [**System resources**](#system), such as authorization policies themselves!

Resources can sometimes be **both** project-scoped and namespace-scoped. For example, an ML deployment is namespace-scoped because it exists in Kubernetes, and is also project-scoped based on models it uses. When this happens, a user needs access to both the namespace and all projects associated with that deployment to be able to view or interact with it.

### Projects

A project defines a **logical grouping** of related resources.

At present, models are the only resources that are explicitly associated with projects. Any model that does not **explicitly declare** its associated project will be **implicitly assigned** to the `default` project.

A project can have multiple models associated with it, but each model is only associated with a single project. Projects-to-models is a one-to-many relationship.

As deployments reference models, they are also protected by project-based policies. Deployments can reference multiple models, so can be associated with multiple different projects. A user must have suitable permissions for **all projects** associated with a deployment in order to interact with it.

{% hint style="info" %}
**Note**: The term "model" here refers to inference models, explainers, and detectors.
{% endhint %}

{% hint style="info" %}
**Note**: It is advised to have a policy such that the `default` project is accessible to everyone.
{% endhint %}

### Namespaces

A namespace defines a **physical grouping** of resources.

Any resources related to deployments will exist in a namespace, including deployed models, explainers, detectors, canaries, shadows, and artifact storage secrets.

A namespace can have many resources associated with it, but each resource is only associated with a single namespace. Namespaces-to-resources is a one-to-many relationship.

### System

System resources are resources that relate to administrative concerns for the Seldon Enterprise Platform platform, instead of ML models and deployments.

Currently the only system-level resources that can be protected by access controls in the Seldon Enterprise Platform API are authorization policies themselves. As a result, the initial policies must be defined by someone with access to the Kubernetes cluster.

### Associating Scopes With Kubernetes Resources

As namespaces are a native Kubernetes concept, there is a natural association between namespaced resources and their associated namespace.

In contrast, projects are not a native Kubernetes concept and so Seldon Enterprise Platform must record the association between a resource and its project somewhere. It does this via Kubernetes metadata: annotations and labels.

When creating deployments through the Seldon Enterprise Platform UI, you can use the pre-filled value or specify the appropriate project for your model. When creating deployments via the Seldon Enterprise Platform API, whether directly or with the Seldon Enterprise Platform SDK, you must provide the appropriate project metadata in the resource definition. In either case, when no project is explicitly specified then the `default` project will be used.

For Seldon Core v1, projects are defined via annotations at the *predictor* level. Each predictor in a deployment's `spec.predictors` has an `annotations` field that can contain annotations of the following form:

```
"project.seldon.io/<name of component in graph>": "<name of project>"
```

<details>

<summary>Example - Seldon Core v1</summary>

Given below is a heavily simplified example of a Seldon Core v1 deployment with a single-model predictor.

The key things to note are the `annotations` field and the `metadata.namespace` field. The former corresponds to the `mock-example-container` model in the graph and associates it with `project-a`. The latter defines the namespace to be `seldon-deployments`.

```yaml
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
  name: mock-deployment
  namespace: seldon-deployments
spec:
  name: mock-example
  predictors:
  - name: default
    annotations:
      project.seldon.io/mock-example-container: project-a
    graph:
      name: mock-example-container
      type: MODEL
    componentSpecs:
    - spec:
        containers:
        - name: mock-example-container
          image: "seldonio/mock_classifier:1.5.0"
```

</details>

<details>

<summary>Example - Seldon Core v2</summary>

Given below is an example of a Seldon Core v2 `Model`.

The key things to note here are the `metadata.annotations` field, which associates the model with `project-a`, and the `metadata.namespace` field, which defines the namespace to be `seldon-deployments`.

```yaml
apiVersion: mlops.seldon.io/v1alpha1
kind: Model
metadata:
  name: iris
  namespace: seldon-deployments
  annotations:
    seldon.io/project: project-a
spec:
  storageUri: "gs://seldon-models/mlserver/iris"
  requirements:
  - sklearn
  memory: 100Ki
```

</details>

## Authorization Methods

Currently, there are two ways of defining permissions on the various resources served by Seldon Enterprise Platform:

* [OPA policies](#opa-policies) (recommended)
* [Namespace labels](#namespace-labels) (deprecated)

OPA policies are the recommended authorization method for Seldon Enterprise Platform as they can be used to authorize all [resource scopes](#resource-scopes).

## OPA Policies

Seldon Enterprise Platform can use Open Policy Agent (OPA) policies to determine if a user has access to a resource. [OPA](https://www.openpolicyagent.org/) is popular, flexible open-source technology for defining policies. You can enable it by following the [installation guide](https://docs.seldon.ai/seldon-enterprise-platform/production-environment/authorization).

### Enable OPA

To enable the use of OPA policies for the **namespace and system scopes**, set the following Helm variables in `install-values.yaml`:

```yaml
rbac:
  opa:
    enabled: true

  nsLabelsAuth:
    enabled: false
```

As OPA policies cannot be used with [label-based authorization](#namespace-labels), this should be disabled as shown above. However, Seldon Enterprise Platform still requires the `seldon.restricted` label to be present on namespaces in order to [detect them](https://docs.seldon.ai/seldon-enterprise-platform/namespace-setup#namespace-detection). When using OPA policies, it does not matter whether the value of this label is `true` or `false`.

If you also want to enable OPA for **projects**, set the following configuration in `install-values.yaml`:

```yaml
rbac:
  opa:
    projectAuthEnabled: true
```

### Policy Management APIs

Seldon Enterprise Platform has API endpoints for managing OPA policies. These can be found in the [API documentation](https://docs.seldon.ai/seldon-enterprise-platform/product-tour/api).

By default, these are enabled and access can be granted to them on a per-user or group basis. If you want to disable these, set the following Helm value:

```yaml
rbac:
  opa:
    permissionManagementAPIDisabled: true
```

### Policy Schema

OPA policies are defined in a JSON document as resource-action pairs for users or groups. This JSON document exists in a ConfigMap, as discussed in the [installation guide](https://docs.seldon.ai/seldon-enterprise-platform/production-environment/authorization).

The structure of policies is as follows:

```json
{
  "role_grants": {
    "<group name>": [
      {
        "resource": "<resource>",
        "action":   "<action>"
      },
      // More permissions for this group
    ],
    // More groups
  },
  "user_grants": {
    "<user name>": [
      {
        "resource": "<resource>",
        "action":   "<action>"
      },
      // More permissions for this user
    ],
    // More users
  }
}
```

The top-level fields of the document are the **grant types**: `role_grants` and `user_grants`. These are **mandatory** fields and correspond to groups and user IDs respectively.

Each grant type defines a map from user/group name to a list of permissions. These user or group names are called **grant targets**.

Each permission is defined as a map, describing an action on a resource scope. For example this permission allows read access to resources in a namespace called `seldon`:

```json
{
  "action": "read",
  "resource": "namespace/seldon"
}
```

{% hint style="info" %}
**Note**: When you do not want to specify either user or role grants, use an empty JSON object as the value.
{% endhint %}

```json
{
  "user_grants": {},
  "role_grants": {
    // ...
  }
}
```

```json
{
  "user_grants": {
    // ...
  },
  "role_grants": {}
}
```

### Granting Access For All Users

For user grants, there is a special grant target called the **star user** and denoted `*`. This special target can be used to provide policies for **all users**.

To see how this can be used, please check the [examples](#examples).

{% hint style="info" %}
**Note**: This special star user is **not** a glob but rather a well-known value that Seldon Enterprise Platform understands.

Seldon Enterprise Platform does **not support** globbing for user and group names. For example, a grant target like `abc*` would not be valid. If you need to define permissions for a collection of users, please use role grants.
{% endhint %}

### Resource Names

Resources are identified by the [scope](#resource-scopes) as a prefix and a name.

For the **system** scope, there is only one possible value:

* `system/iam`

For the **project** scope, aside from the `default` project the names are determined by you:

* `project/default`
* `project/foo`
* ...

For the **namespace** scope, the names should correspond to Kubernetes namespaces for deploying ML models:

* `namespace/seldon`
* `namespace/seldon-gitops`
* ...

Seldon Enterprise Platform supports globbing of resource names with an asterisk (`*`). It is valid to use globbing anywhere within the resource *name*, but not for the resource *scope*.

For example, these would all be **allowed**:

* `"resource": "namespace/*"`
* `"resource": "namespace/seldon-*"`
* `"resource": "namespace/*-gitops"`
* `"resource": "project/dev*"`

On the other hand, these would all be **prohibited**:

* `"resource": "*"`
* `"resource": "*/*"`
* `"resource": "*/seldon"`
* `"resource": "name*/seldon"`

{% hint style="info" %}
**Note**: For more details on the specifics of each resource type, please see the namespace and project sections.
{% endhint %}

### Examples

Giving **everyone** access to resources in a scope is possible using the special "star" user, `*`. An example of giving all users access to the `default` project looks like this:

```json
{
  "role_grants": {},
  "user_grants": {
    "*": [
      {
        "action": "read",
        "resource": "project/default"
      },
      {
        "action": "write",
        "resource": "project/default"
      }
    ]
  }
}
```

Giving a **group** access to resources in a scope is possible using `role_grants`. An example of giving the `data-scientist` group access to the `prod` namespace looks like this:

```json
{
  "role_grants": {
    "data-scientist": [
      {
        "action": "read",
        "resource": "namespace/prod"
      },
      {
        "action": "write",
        "resource": "namespace/prod"
      }
    ]
  },
  "user_grants": {}
}
```

Giving a **user** access to resources in a scope is possible using `user_grants`. An example of giving the `alice` user access to the `system/iam` scope looks like this:

```json
{
  "role_grants": {},
  "user_grants": {
    "alice": [
      {
        "action": "read",
        "resource": "system/iam"
      },
      {
        "action": "write",
        "resource": "system/iam"
      }
    ]
  }
}
```

An example of a complete policy file is given below.

In this example, users belonging to the `data_scientist` group will be able to create, modify, and read models from the `iris` project, as well as see deployments in the `seldon` namespace. However, they do not have permission to create or modify deployments in that namespace.

Members of the `ml_engineer` group can create deployments from the `iris` project in the `seldon` namespace. They are also allowed to interact with other resources in the `seldon` namespace, such as storage secrets.

Members of the `ops` group can view all resources in namespaces with the `seldon` prefix (including `seldon` itself), and can see the full Model Catalog.

The `*` user grant specifies that all users have read and write permissions for the `default` project and `default` namespace.

Only the user `alice` has access to the `alice` namespace.

```json
{
  "role_grants": {
    "data_scientist": [
      {
        "action": "read",
        "resource": "project/iris"
      },
      {
        "action": "write",
        "resource": "project/iris"
      },
      {
        "action": "read",
        "resource": "namespace/seldon"
      }
    ],
    "ml_engineer": [
      {
        "action": "read",
        "resource": "project/iris"
      },
      {
        "action": "write",
        "resource": "project/iris"
      },
      {
        "action": "read",
        "resource": "namespace/seldon"
      },
      {
        "action": "write",
        "resource": "namespace/seldon"
      }
    ],
    "ops": [
      {
        "action": "read",
        "resource": "namespace/seldon*"
      },
      {
        "action": "read",
        "resource": "project/*"
      }
    ]
  },
  "user_grants": {
    "*": [
      {
        "action": "read",
        "resource": "project/default"
      },
      {
        "action": "write",
        "resource": "project/default"
      },
      {
        "action": "read",
        "resource": "namespace/default"
      },
      {
        "action": "write",
        "resource": "namespace/default"
      }
    ],
    "alice": [
      {
        "action": "read",
        "resource": "namespace/alice"
      },
      {
        "action": "write",
        "resource": "namespace/alice"
      }
    ]
  }
}
```

## Policy Operations

### Interacting With Policies

The policy file can be modified in various ways:

* Using `kubectl`
* Using Seldon's `policy_client` command-line utility
* Using the permission management endpoints of the Seldon Enterprise Platform API

You can interact with the ConfigMap directly using `kubectl`. If your policies are defined in a JSON file called `policy.json`, you can turn it into a Kubernetes manifest called `policy.yaml` using the following command:

```bash
kubectl -n seldon-system create configmap seldon-deploy-policies --from-file=data=policy.json --dry-run=client -o yaml > policy.yaml
```

This resulting `policy.yaml` file can be tracked under version control if you wish. You can overwrite the existing policies by running the following:

```bash
  kubectl apply -f policy.yaml
```

The `policy_client` command-line utility can be downloaded from the [seldon-deploy-resources repository](https://github.com/SeldonIO/seldon-deploy-resources/releases/tag/policy_client_1.3.0). You will need to download the correct binary for your operating system and architecture, e.g. Linux AMD x64. For full documentation of the tool you can run `policy_client --help`.

The permission management API is provided via the `/iam/policy` endpoints discussed in the [API documentation](https://docs.seldon.ai/seldon-enterprise-platform/product-tour/api).

Seldon Enterprise Platform will automatically reload the policies if it detects that they have changed. This is dependent on how the file is mounted and how quickly Kubernetes propagates changes. If you want to ensure that Seldon Enterprise Platform definitely has the latest changes, you can restart it using:

```bash
kubectl -n seldon-system rollout restart deployment seldon-deploy
```

### Adding Policies

To add a new policy you can run:

```bash
policy_client addPolicy --resource=namespace/seldon --action=write --target_users=alice --target_groups=admin
```

The arguments `target_groups` and `target_users` can take multiple entries.

### Deleting Policies

Similarly to adding, to remove an existing policy you can run:

```bash
policy_client removePolicy --resource=namespace/seldon --action=write --target_users=alice --target_groups=admin
```

### Limitations

Using a ConfigMap for the policy file is flexible and easy to setup. However, it comes with a limitation which is true for all Kubernetes resources - it is [limited to 1MB size](https://kubernetes.io/docs/concepts/configuration/configmap/#motivation), since that is the underlying etcd value limit.

If your use case requires a bigger size for the policy file consider mounting the file from somewhere else like an object store (S3, minio), or a database.

We are working on improving support for such use cases, which should be available in a future release.

### Migrating From Namespace Labels

To migrate from using namespace labels for defining access requirements to a namespace you can use the following command:

```bash
policy_client sync
```

It will create a ConfigMap called `seldon-deploy-policies` in the `seldon-system` namespace if one is not present. The name and namespace are configurable by flags. It will then populate policies corresponding to the namespace labels in the cluster. The command will not delete any existing policies in the ConfigMap.

To confirm the change has been successful you can check the ConfigMap by running:

```bash
kubectl get cm -n seldon-system seldon-deploy-policies -o jsonpath='{.data.data}' | jq
```

## Namespace Labels

{% hint style="warning" %}
This approach has been deprecated and will be removed in the future. For a more complete and efficient authorization solution, please use [Open Policy Agent policies](#opa-policies).

Label-based and OPA policy-based authorization cannot be used at the same time. Attempts to do so will result in only OPA policies taking effect.
{% endhint %}

Seldon Enterprise Platform can use labels on Kubernetes namespaces to determine if a user has access to resources within that namespace. Note that namespace labels can **only** be used for authorizing access to namespaces, but not to projects.

To authorize a user or a group to access resources in a namespace, add labels in the following manner along with the associated permission.

```
seldon.user.<user name>: "write"
seldon.group.<group name>: "read"
```

As an example, setting permissions on a namespace for user `alice`, service account `sd_api` and group `ml_engineer` would involve adding labels as follows,

```
seldon.user.alice: "write"
seldon.user.sd_api: "read"
seldon.group.ml_engineer: "read"
```

{% hint style="info" %}
**Note**:Service accounts are also treated as users for authorization purposes and the service account user identity can be configured using a separate token claim if needed.
{% endhint %}

## Authorization Outside Seldon Enterprise Platform

Seldon Enterprise Platform runs in a Kubernetes platform which contains several other components. Access to these components should also be controlled. How this is done can vary based on your requirements and pre-existing access controls.

### Seldon Core

{% hint style="info" %}
**Note**:This feature is only supported in Core v1 as of now and requires Istio to be installed in the cluster.
{% endhint %}

One of the main features of Seldon Enterprise Platform is the ability to create Seldon Core deployments and allow users to make requests to these deployments. For requests to be made in a secure manner, these endpoints need to be authorized appropriately.

Seldon Enterprise Platform can automatically synchronize Istio authorization policies based on the permissions set in its OPA policies. Please refer to the [production installation guide](https://docs.seldon.ai/seldon-enterprise-platform/production-environment/authorization#seldon-core) for enabling this functionality.

This functionality is controlled by the `rbac.opa.istioPolicySyncInterval` Helm variable. You can enable synchronization by supplying a strictly positive value compatible with Go's `time.ParseDuration` [function](https://pkg.go.dev/time#ParseDuration). Using any other value, such as a negative duration or leaving the Helm parameter empty, will disable this functionality. To use the suggested interval of 5 minutes, please set the following in `install-values.yaml`:

```yaml
rbac:
  opa:
    istioPolicySyncInterval: "5m"
```

Alternatively, the access to these endpoints can be manually controlled via the Istio configuration as described in the [Seldon Core documentation](https://docs.seldon.io/projects/seldon-core/en/latest/ingress/istio.html#configuring-authentication-authorization).

{% hint style="info" %}
Seldon Enterprise Platform provides the ability to proxy requests to Seldon Core deployments via its API. If these requests go through Seldon Enterprise Platform, then they will already be authorized based on the user's permissions.

However, it is **not recommended** to do this except for testing and debugging purposes, as Seldon Enterprise Platform is not designed as a high-performance proxy and cannot be configured as one. For reasons of latency, scalability, and availability you should use dedicated networking infrastructure like an ingress controller or service mesh.
{% endhint %}

### Other Components

As part of the [production installation](https://docs.seldon.ai/seldon-enterprise-platform/production-environment), other components can be installed in your environment. Please refer to the official documentation for each such technology for details on how to configure authorization for them.

## Configuration Reference

The full list of Helm values for authorization is given below. These can be set in `install-values.yaml`. Please refer to the above documentation for how to configure each setting.

```yaml
rbac:
  opa:
    enabled: true
    projectAuthEnabled: true
    permissionManagementAPIDisabled: false
    istioPolicySyncInterval: ""
    configMap: seldon-deploy-policies
  nsLabelsAuth:
    enabled: false
```
