|
| 1 | +# Authentication and Authorization |
| 2 | + |
| 3 | +Lightspeed Core Stack implements a modular authentication and authorization |
| 4 | +system with multiple authentication methods. Authorization is configurable |
| 5 | +through role-based access control. |
| 6 | + |
| 7 | +## Authentication configuration |
| 8 | + |
| 9 | +The authentication system is configured via the `authentication` section in |
| 10 | +the configuration file. |
| 11 | + |
| 12 | +## Authentication Modules |
| 13 | + |
| 14 | +Authentication is handled through selectable modules configured via the |
| 15 | +`module` field in the authentication configuration. |
| 16 | + |
| 17 | +### No-op (`noop`) |
| 18 | + |
| 19 | +Development-only authentication that bypasses security checks. |
| 20 | + |
| 21 | +**Configuration:** |
| 22 | +```yaml |
| 23 | +authentication: |
| 24 | + module: noop |
| 25 | +``` |
| 26 | +
|
| 27 | +**Behavior:** |
| 28 | +- Accepts any request without token validation |
| 29 | +- Extracts `user_id` from query parameters (defaults to `00000000-0000-0000-0000-000`) |
| 30 | +- Uses fixed username `lightspeed-user` |
| 31 | + |
| 32 | +### No-op with Token (`noop-with-token`) |
| 33 | + |
| 34 | +Development authentication that requires tokens but doesn't validate them. |
| 35 | + |
| 36 | +**Configuration:** |
| 37 | +```yaml |
| 38 | +authentication: |
| 39 | + module: noop-with-token |
| 40 | +``` |
| 41 | + |
| 42 | +**Behavior:** |
| 43 | +- Extracts bearer token from the `Authorization` header |
| 44 | +- Same user ID and username handling as `noop` |
| 45 | +- Token is passed through unvalidated for downstream use |
| 46 | + |
| 47 | +### Kubernetes (`k8s`) |
| 48 | + |
| 49 | +K8s based authentication is suitable for running the Lightspeed Stack in |
| 50 | +Kubernetes environments. The user accessing the service must have a valid |
| 51 | +Kubernetes token and the appropriate RBAC permissions to access the service. |
| 52 | +The user must have the `get` permission on the Kubernetes RBAC non-resource URL |
| 53 | +`/ls-access`. Here is an example of granting `get` on `/ls-access` via a |
| 54 | +ClusterRole’s nonResourceURLs rule: |
| 55 | + |
| 56 | +```yaml |
| 57 | +# Allow GET on non-resource URL /ls-access |
| 58 | +apiVersion: rbac.authorization.k8s.io/v1 |
| 59 | +kind: ClusterRole |
| 60 | +metadata: |
| 61 | + name: lightspeed-access |
| 62 | +rules: |
| 63 | + - nonResourceURLs: ["/ls-access"] |
| 64 | + verbs: ["get"] |
| 65 | +--- |
| 66 | +# Bind to a user, group, or service account |
| 67 | +apiVersion: rbac.authorization.k8s.io/v1 |
| 68 | +kind: ClusterRoleBinding |
| 69 | +metadata: |
| 70 | + name: lightspeed-access-binding |
| 71 | +roleRef: |
| 72 | + apiGroup: rbac.authorization.k8s.io |
| 73 | + kind: ClusterRole |
| 74 | + name: lightspeed-access |
| 75 | +subjects: |
| 76 | + - kind: User # or ServiceAccount, Group |
| 77 | + name: SOME_USER_OR_SA |
| 78 | + apiGroup: rbac.authorization.k8s.io |
| 79 | +``` |
| 80 | + |
| 81 | +**Configuration:** |
| 82 | + |
| 83 | +When deploying Lightspeed Stack in a Kubernetes cluster, it is not required to |
| 84 | +specify cluster connection details, it automatically picks up the in-cluster |
| 85 | +configuration or through a kubeconfig file. |
| 86 | + |
| 87 | +When running outside a kubernetes cluster or connecting to external Kubernetes |
| 88 | +clusters, Lightspeed Stack requires the cluster connection details in the |
| 89 | +configuration file: |
| 90 | + |
| 91 | +- `k8s_cluster_api` Kubernetes Cluster API URL. The URL of the k8s/OCP API server where tokens are validated. |
| 92 | +- `k8s_ca_cert_path` Path to the CA certificate file for clusters with self-signed certificates. |
| 93 | +- `skip_tls_verification` Whether to skip TLS verification. |
| 94 | + |
| 95 | +For example: |
| 96 | + |
| 97 | +```yaml |
| 98 | +authentication: |
| 99 | + module: k8s |
| 100 | + k8s_cluster_api: https://kubernetes.default.svc # optional, will be auto-detected |
| 101 | + k8s_ca_cert_path: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt # optional, will be auto-detected |
| 102 | + skip_tls_verification: false # optional, insecure |
| 103 | +``` |
| 104 | + |
| 105 | +**Behavior:** |
| 106 | +- Validates bearer tokens via the Kubernetes TokenReview API |
| 107 | +- Performs authorization checks using SubjectAccessReview (SAR) |
| 108 | +- Checks access to configured virtual path (default: `/ls-access`) with `get` verb |
| 109 | +- Extracts user ID and username from token claims |
| 110 | +- Special handling for the `kube:admin` user (uses cluster ID as user ID) |
| 111 | + |
| 112 | +**Requirements:** |
| 113 | +- Valid Kubernetes service account token in the `Authorization` header |
| 114 | +- RBAC rules granting access to the virtual path |
| 115 | +- Cluster access or kubeconfig file |
| 116 | + |
| 117 | +### JWK Token (`jwk-token`) |
| 118 | + |
| 119 | +JWK (JSON Web Keyset) based authentication is suitable for scenarios where you |
| 120 | +need to authenticate users based on tokens. This method is commonly used in web |
| 121 | +applications and APIs. |
| 122 | + |
| 123 | +Users provide a JWT (JSON Web Token) in the `Authorization` header of their |
| 124 | +requests. This JWT is validated against the JWK set fetched from the configured |
| 125 | +URL. |
| 126 | + |
| 127 | +**Configuration:** |
| 128 | +```yaml |
| 129 | +authentication: |
| 130 | + module: jwk-token |
| 131 | + jwk_config: |
| 132 | + url: https://auth.example.com/.well-known/jwks.json |
| 133 | + jwt_configuration: |
| 134 | + user_id_claim: sub # optional, defaults to 'sub' |
| 135 | + username_claim: name # optional, defaults to 'preferred_username' |
| 136 | + role_rules: [] # optional role extraction rules. See Authorization section for details. |
| 137 | +``` |
| 138 | + |
| 139 | +**Behavior:** |
| 140 | +- Fetches JWK set from configured URL (cached for 1 hour) |
| 141 | +- Validates JWT signature against JWK set |
| 142 | +- Extracts user ID and username from configurable JWT claims |
| 143 | +- Returns default credentials (guest-like) if no `Authorization` header present (guest access) |
| 144 | + |
| 145 | +## Authorization System |
| 146 | + |
| 147 | +Authorization is controlled through role-based access control using two resolver types. |
| 148 | + |
| 149 | +### Role Resolution |
| 150 | + |
| 151 | +Determines user roles based on authentication method: |
| 152 | + |
| 153 | +**No-op/K8s Authentication:** |
| 154 | +- Uses a no-op role resolver |
| 155 | +- All users get the special `*` (everyone) role only |
| 156 | +- To be expanded in the future |
| 157 | + |
| 158 | +**JWK Token Authentication:** |
| 159 | +- Uses JWT claims to determine user roles through JSONPath expressions |
| 160 | +- Falls back to a no-op resolver if no role rules are configured |
| 161 | + |
| 162 | +#### JWT Role Rules |
| 163 | + |
| 164 | +Extract roles from JWT claims using JSONPath expressions, for example: |
| 165 | + |
| 166 | +```yaml |
| 167 | +authentication: |
| 168 | + module: jwk-token |
| 169 | + jwk_config: |
| 170 | + jwt_configuration: |
| 171 | + role_rules: |
| 172 | + - jsonpath: "$.realm_access.roles[*]" |
| 173 | + operator: contains |
| 174 | + value: "manager" |
| 175 | + roles: ["manager"] |
| 176 | + - jsonpath: "$.org_id" |
| 177 | + operator: "equals" |
| 178 | + value: [["dummy_corp"]] |
| 179 | + roles: ["dummy_employee"] |
| 180 | + - jsonpath: "$.groups[*]" |
| 181 | + operator: in |
| 182 | + value: ["developers", "qa"] |
| 183 | + roles: ["developer"] |
| 184 | + negate: false |
| 185 | +``` |
| 186 | + |
| 187 | +**Fields:** |
| 188 | +- `jsonpath`: JSONPath expression to extract values from JWT claims. |
| 189 | +- `operator`: Comparison operator (see below) |
| 190 | +- `value`: Value(s) to evaluate the extracted values and operator against |
| 191 | +- `roles`: List of roles to assign if the rule matches |
| 192 | +- `negate`: If true, inverts the rule match result (optional, defaults to false) |
| 193 | + |
| 194 | +Note that the JSONPath expression always yields a list of values, even for |
| 195 | +single-value expressions, so comparisons should be done accordingly. |
| 196 | + |
| 197 | +**Operators:** |
| 198 | +- `equals`: Exact match |
| 199 | +- `contains`: Value contains the specified string |
| 200 | +- `in`: Value is in the specified list |
| 201 | +- `match`: Regex pattern match (uses pre-compiled patterns) |
| 202 | + |
| 203 | +### Access Resolution |
| 204 | + |
| 205 | +Various operations inside lightspeed require authorization checks. Those |
| 206 | +operations are associated with actions (e.g. `query`, `info`, `admin`). |
| 207 | + |
| 208 | +Once user roles are determined, checking whether a user is allowed to perform |
| 209 | +an action is done through access resolvers. |
| 210 | + |
| 211 | +**No-op resolver:** |
| 212 | + |
| 213 | +A resolver which uses a no-op access resolver that grants all users access to |
| 214 | +all actions, used when no access rules are configured no-op authentication is |
| 215 | +configured, or at-least currently when k8s authentication is configured. |
| 216 | + |
| 217 | +**Rule-based Access:** |
| 218 | + |
| 219 | +A resolver which does the obvious thing of checking whether any of the user's |
| 220 | +roles is allowed to perform the requested action based on the access rules in |
| 221 | +the authorization configuration. It also grants all users which have the `admin` |
| 222 | +action unrestricted access to all other actions. |
| 223 | + |
| 224 | +#### Access Rules |
| 225 | + |
| 226 | +Define which roles can perform which actions: |
| 227 | + |
| 228 | +```yaml |
| 229 | +authorization: |
| 230 | + access_rules: |
| 231 | + # `*` is a special role that is given to all users |
| 232 | + - role: "*" |
| 233 | + actions: ["query", "info"] |
| 234 | + - role: "manager" |
| 235 | + # admin is a special *action* that grants unrestricted access to all actions. |
| 236 | + # Note that only the `admin` *action* is special, there is no special `admin` role. |
| 237 | + actions: ["admin"] |
| 238 | + - role: "dummy_employee" |
| 239 | + actions: ["list_conversations"] |
| 240 | + - role: "developer" |
| 241 | + actions: ["query", "get_config", "list_conversations"] |
| 242 | +``` |
| 243 | +
|
| 244 | +**Available Actions:** |
| 245 | +- `admin` - If a user has this action, they automatically can perform all other actions |
| 246 | +- `query` - Access query endpoints |
| 247 | +- `query_other_conversations` - Query conversations not owned by the user |
| 248 | +- `streaming_query` - Access streaming query endpoints |
| 249 | +- `info` - Access the `/` endpoint, `/info` endpoint, `/readiness` endpoint, and `/liveness` endpoint |
| 250 | +- `get_config` - Access the `/config` endpoint |
| 251 | +- `get_models` - Access the `/models` endpoint |
| 252 | +- `get_metrics` - Access the `/metrics` endpoint |
| 253 | +- `list_conversations` - Access the `/conversations` endpoint |
| 254 | +- `list_other_conversations` - Access conversations not owned by the user |
| 255 | +- `get_conversation` - `GET` conversations from `/conversations/{conversation_id}` endpoint |
| 256 | +- `read_other_conversations` - Read conversations not owned by the user |
| 257 | +- `delete_conversation` - `DELETE` conversations from `/conversations/{conversation_id}` endpoint |
| 258 | +- `delete_other_conversations` - Delete conversations not owned by the user |
| 259 | +- `feedback` - Access the `/feedback` endpoint |
| 260 | +- `model_override` - Allow user to choose the model when querying |
0 commit comments