From 67dcaeb89a74ba81219292e22345a91ae3f50d11 Mon Sep 17 00:00:00 2001 From: Major Hayden Date: Thu, 22 Jan 2026 17:16:34 -0600 Subject: [PATCH] docs: split auth.md into modular documentation structure Reorganize authentication documentation from single 368-line file into dedicated docs/auth/ directory with focused files for each auth module. Expanded rh-identity documentation with User/System identity types, entitlement validation, request examples, and troubleshooting guide. Enhanced examples/lightspeed-stack-rh-identity.yaml with comprehensive comments and testing instructions. Signed-off-by: Major Hayden --- docs/auth.md | 372 +-------------------- docs/auth/api-key-token.md | 85 +++++ docs/auth/authorization.md | 234 +++++++++++++ docs/auth/index.md | 68 ++++ docs/auth/jwk-token.md | 178 ++++++++++ docs/auth/kubernetes.md | 131 ++++++++ docs/auth/noop.md | 80 +++++ docs/auth/rh-identity.md | 323 ++++++++++++++++++ examples/lightspeed-stack-rh-identity.yaml | 96 +++++- 9 files changed, 1198 insertions(+), 369 deletions(-) create mode 100644 docs/auth/api-key-token.md create mode 100644 docs/auth/authorization.md create mode 100644 docs/auth/index.md create mode 100644 docs/auth/jwk-token.md create mode 100644 docs/auth/kubernetes.md create mode 100644 docs/auth/noop.md create mode 100644 docs/auth/rh-identity.md diff --git a/docs/auth.md b/docs/auth.md index 0c7905b9a..7cbbac8e7 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -1,367 +1,15 @@ # Authentication and Authorization -Lightspeed Core Stack implements a modular authentication and authorization -system with multiple authentication methods. Authorization is configurable -through role-based access control. +This documentation has been reorganized into separate files for better navigation. -## Authentication configuration +**See [auth/index.md](auth/index.md) for the complete authentication and authorization documentation.** -The authentication system is configured via the `authentication` section in -the configuration file. +## Quick Links -## Authentication Modules - -Authentication is handled through selectable modules configured via the -`module` field in the authentication configuration. - -### No-op (`noop`) - -Development-only authentication that bypasses security checks. - -**Configuration:** -```yaml -authentication: - module: noop -``` - -**Behavior:** -- Accepts any request without token validation -- Extracts `user_id` from query parameters (defaults to `00000000-0000-0000-0000-000`) -- Uses fixed username `lightspeed-user` - -### No-op with Token (`noop-with-token`) - -Development authentication that requires tokens but doesn't validate them. - -**Configuration:** -```yaml -authentication: - module: noop-with-token -``` - -**Behavior:** -- Extracts bearer token from the `Authorization` header -- Same user ID and username handling as `noop` -- Token is passed through unvalidated for downstream use - -### Kubernetes (`k8s`) - -K8s based authentication is suitable for running the Lightspeed Stack in -Kubernetes environments. The user accessing the service must have a valid -Kubernetes token and the appropriate RBAC permissions to access the service. -The user must have the `get` permission on the Kubernetes RBAC non-resource URL -`/ls-access`. Here is an example of granting `get` on `/ls-access` via a -ClusterRole’s nonResourceURLs rule: - -```yaml -# Allow GET on non-resource URL /ls-access -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: lightspeed-access -rules: - - nonResourceURLs: ["/ls-access"] - verbs: ["get"] ---- -# Bind to a user, group, or service account -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: lightspeed-access-binding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: lightspeed-access -subjects: - - kind: User # or ServiceAccount, Group - name: SOME_USER_OR_SA - apiGroup: rbac.authorization.k8s.io -``` - -**Configuration:** - -When deploying Lightspeed Stack in a Kubernetes cluster, it is not required to -specify cluster connection details, it automatically picks up the in-cluster -configuration or through a kubeconfig file. - -When running outside a kubernetes cluster or connecting to external Kubernetes -clusters, Lightspeed Stack requires the cluster connection details in the -configuration file: - -- `k8s_cluster_api` Kubernetes Cluster API URL. The URL of the k8s/OCP API server where tokens are validated. -- `k8s_ca_cert_path` Path to the CA certificate file for clusters with self-signed certificates. -- `skip_tls_verification` Whether to skip TLS verification. - -For example: - -```yaml -authentication: - module: k8s - k8s_cluster_api: https://kubernetes.default.svc # optional, will be auto-detected - k8s_ca_cert_path: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt # optional, will be auto-detected - skip_tls_verification: false # optional, insecure -``` - -**Behavior:** -- Validates bearer tokens via the Kubernetes TokenReview API -- Performs authorization checks using SubjectAccessReview (SAR) -- Checks access to configured virtual path (default: `/ls-access`) with `get` verb -- Extracts user ID and username from token claims -- Special handling for the `kube:admin` user (uses cluster ID as user ID) - -**Requirements:** -- Valid Kubernetes service account token in the `Authorization` header -- RBAC rules granting access to the virtual path -- Cluster access or kubeconfig file - -### JWK Token (`jwk-token`) - -JWK (JSON Web Keyset) based authentication is suitable for scenarios where you -need to authenticate users based on tokens. This method is commonly used in web -applications and APIs. - -Users provide a JWT (JSON Web Token) in the `Authorization` header of their -requests. This JWT is validated against the JWK set fetched from the configured -URL. - -**Configuration:** -```yaml -authentication: - module: jwk-token - jwk_config: - url: https://auth.example.com/.well-known/jwks.json - jwt_configuration: - user_id_claim: sub # optional, defaults to 'sub' - username_claim: name # optional, defaults to 'preferred_username' - role_rules: [] # optional role extraction rules. See Authorization section for details. -``` - -**Behavior:** -- Fetches JWK set from configured URL (cached for 1 hour) -- Validates JWT signature against JWK set -- Extracts user ID and username from configurable JWT claims -- Returns default credentials (guest-like) if no `Authorization` header present (guest access) - -### API Key Token (`api-key-token`) - -Authentication that checks a given API Key token is present as a Bearer token - -**Configuration:** -```yaml - module: "api-key-token" - api_key_config: - api_key: "some-api-key" -``` - -**Behavior:** -- Extracts bearer token from the `Authorization` header -- Same user ID and username handling as `noop` -- Token is passed through and validated against the API Key given from configuration, for downstream use - -### Red Hat Identity (`rh-identity`) - -Red Hat Identity header authentication is suitable for deployments behind Red Hat -Hybrid Cloud Console infrastructure (e.g., console.redhat.com, Insights). This -method validates the `x-rh-identity` header provided by Red Hat's authentication -proxy, supporting both User (console users) and System (RHEL systems) identity types. - -**Configuration:** -```yaml -authentication: - module: rh-identity - rh_identity_config: - required_entitlements: ["rhel"] # optional, validates service entitlements -``` - -The `required_entitlements` field accepts a list of service names. When configured, -ALL listed entitlements must be present in the identity header. Omit this field -to disable entitlement validation entirely. - -**Identity Types:** - -- **User**: Console users authenticated via SSO. Identified by `user_id` and `username` - from the `identity.user` object. -- **System**: Certificate-authenticated RHEL systems. Identified by `cn` (Common Name) - from the `identity.system` object, with `account_number` used as username. - -**Header Format:** - -The `x-rh-identity` header contains a base64-encoded JSON payload. Below are -examples of the decoded JSON structure for each identity type. - -User identity: -```json -{ - "identity": { - "account_number": "123456", - "org_id": "654321", - "type": "User", - "user": { - "user_id": "abc123", - "username": "user@example.com", - "is_org_admin": false - } - }, - "entitlements": { - "rhel": {"is_entitled": true, "is_trial": false} - } -} -``` - -System identity: -```json -{ - "identity": { - "account_number": "123456", - "org_id": "654321", - "type": "System", - "system": { - "cn": "c87dcb4c-8af1-40dd-878e-60c744edddd0" - } - }, - "entitlements": { - "rhel": {"is_entitled": true, "is_trial": false} - } -} -``` - -**Behavior:** -- Extracts `x-rh-identity` header from request -- Base64 decodes and parses as JSON -- Validates structure based on identity type (User or System) -- Validates service entitlements if `required_entitlements` is configured -- Extracts user_id (or cn for System) and username (or account_number for System) - -**Requirements:** -- Valid `x-rh-identity` header with base64-encoded JSON -- Proper JSON structure for the identity type -- Required service entitlements (if configured) - -**Error Responses:** - -| Status | Condition | -|--------|-----------| -| 401 | Missing `x-rh-identity` header | -| 400 | Invalid base64 encoding, invalid JSON, or missing required fields | -| 403 | Missing required service entitlements | - -## Authorization System - -Authorization is controlled through role-based access control using two resolver types. - -### Role Resolution - -Determines user roles based on authentication method: - -**No-op/K8s Authentication:** -- Uses a no-op role resolver -- All users get the special `*` (everyone) role only -- To be expanded in the future - -**JWK Token Authentication:** -- Uses JWT claims to determine user roles through JSONPath expressions -- Falls back to a no-op resolver if no role rules are configured - -#### JWT Role Rules - -Extract roles from JWT claims using JSONPath expressions, for example: - -```yaml -authentication: - module: jwk-token - jwk_config: - jwt_configuration: - role_rules: - - jsonpath: "$.realm_access.roles[*]" - operator: contains - value: "manager" - roles: ["manager"] - - jsonpath: "$.org_id" - operator: "equals" - value: [["dummy_corp"]] - roles: ["dummy_employee"] - - jsonpath: "$.groups[*]" - operator: in - value: ["developers", "qa"] - roles: ["developer"] - negate: false -``` - -**Fields:** -- `jsonpath`: JSONPath expression to extract values from JWT claims. -- `operator`: Comparison operator (see below) -- `value`: Value(s) to evaluate the extracted values and operator against -- `roles`: List of roles to assign if the rule matches -- `negate`: If true, inverts the rule match result (optional, defaults to false) - -Note that the JSONPath expression always yields a list of values, even for -single-value expressions, so comparisons should be done accordingly. - -**Operators:** -- `equals`: Exact match -- `contains`: Value contains the specified string -- `in`: Value is in the specified list -- `match`: Regex pattern match (uses pre-compiled patterns) - -### Access Resolution - -Various operations inside lightspeed require authorization checks. Those -operations are associated with actions (e.g. `query`, `info`, `admin`). - -Once user roles are determined, checking whether a user is allowed to perform -an action is done through access resolvers. - -**No-op resolver:** - -A resolver which uses a no-op access resolver that grants all users access to -all actions, used when no access rules are configured no-op authentication is -configured, or at-least currently when k8s authentication is configured. - -**Rule-based Access:** - -A resolver which does the obvious thing of checking whether any of the user's -roles is allowed to perform the requested action based on the access rules in -the authorization configuration. It also grants all users which have the `admin` -action unrestricted access to all other actions. - -#### Access Rules - -Define which roles can perform which actions: - -```yaml -authorization: - access_rules: - # `*` is a special role that is given to all users - - role: "*" - actions: ["query", "info"] - - role: "manager" - # admin is a special *action* that grants unrestricted access to all actions. - # Note that only the `admin` *action* is special, there is no special `admin` role. - actions: ["admin"] - - role: "dummy_employee" - actions: ["list_conversations"] - - role: "developer" - actions: ["query", "get_config", "list_conversations"] -``` - -**Available Actions:** -- `admin` - If a user has this action, they automatically can perform all other actions -- `query` - Access query endpoints -- `query_other_conversations` - Query conversations not owned by the user -- `streaming_query` - Access streaming query endpoints -- `info` - Access the `/` endpoint, `/info` endpoint, `/readiness` endpoint, and `/liveness` endpoint -- `get_config` - Access the `/config` endpoint -- `get_models` - Access the `/models` endpoint -- `get_tools` - Access the `/tools` and `/mcp-auth/client-options` endpoints -- `get_shields` - Access the `/shields` endpoint -- `list_providers` - Access the `/providers` endpoint -- `get_provider` - Access the `/providers/{provider_id}` endpoint -- `get_metrics` - Access the `/metrics` endpoint -- `list_conversations` - Access the `/conversations` endpoint -- `list_other_conversations` - Access conversations not owned by the user -- `get_conversation` - `GET` conversations from `/conversations/{conversation_id}` endpoint -- `read_other_conversations` - Read conversations not owned by the user -- `delete_conversation` - `DELETE` conversations from `/conversations/{conversation_id}` endpoint -- `delete_other_conversations` - Delete conversations not owned by the user -- `feedback` - Access the `/feedback` endpoint -- `model_override` - Allow user to choose the model when querying +- [Overview & Configuration](auth/index.md) +- [No-op Modules](auth/noop.md) - Development authentication +- [Kubernetes](auth/kubernetes.md) - K8s/OCP authentication +- [JWK Token](auth/jwk-token.md) - JWT/OAuth2 authentication +- [API Key Token](auth/api-key-token.md) - Static API key authentication +- [Red Hat Identity](auth/rh-identity.md) - Red Hat Hybrid Cloud Console authentication +- [Authorization](auth/authorization.md) - Role-based access control diff --git a/docs/auth/api-key-token.md b/docs/auth/api-key-token.md new file mode 100644 index 000000000..99b32b0ce --- /dev/null +++ b/docs/auth/api-key-token.md @@ -0,0 +1,85 @@ +# API Key Token Authentication (`api-key-token`) + +Simple API key authentication for service-to-service communication or +controlled access scenarios. + +## Overview + +The `api-key-token` module validates requests by comparing the bearer token +against a pre-configured API key. + +## Configuration + +```yaml +authentication: + module: api-key-token + api_key_config: + api_key: "your-secret-api-key" +``` + +### Configuration Options + +| Option | Required | Description | +|--------|----------|-------------| +| `api_key` | Yes | The API key that clients must provide | + +## Behavior + +1. Extracts bearer token from the `Authorization` header +2. Compares token against configured `api_key` +3. Rejects request if token doesn't match +4. Uses same user ID and username handling as `noop` module + +### User Identity + +Since API key authentication doesn't carry user identity information: +- `user_id`: Defaults to `00000000-0000-0000-0000-000` or from query parameter +- `username`: Fixed as `lightspeed-user` + +## Request Example + +```bash +curl http://localhost:8080/v1/query \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-secret-api-key" \ + -d '{"query": "Hello"}' +``` + +## Security Considerations + +### Best Practices + +1. **Use strong, random API keys**: Generate keys with sufficient entropy + ```bash + openssl rand -hex 32 + ``` + +2. **Store keys securely**: Use environment variables or secret management + ```yaml + authentication: + module: api-key-token + api_key_config: + api_key: ${API_KEY} # From environment variable + ``` + +3. **Rotate keys regularly**: Implement key rotation procedures + +4. **Use HTTPS**: Always use TLS in production to protect keys in transit + +### Limitations + +- No user identity information (shared credential) +- Single key for all clients (no granular access control) +- No automatic key rotation + +### When to Use + +- Internal service-to-service communication +- Simple integrations with trusted clients +- Scenarios where OAuth2/OIDC is overkill + +### When NOT to Use + +- User-facing applications (use `jwk-token` instead) +- Multi-tenant environments (no user distinction) +- Scenarios requiring audit trails per user diff --git a/docs/auth/authorization.md b/docs/auth/authorization.md new file mode 100644 index 000000000..8283172eb --- /dev/null +++ b/docs/auth/authorization.md @@ -0,0 +1,234 @@ +# Authorization System + +Authorization in Lightspeed Core Stack is controlled through role-based access +control (RBAC). Once a user is authenticated, the authorization system +determines what actions they can perform. + +## Overview + +``` +Authentication → Role Resolution → Access Resolution → Action Allowed/Denied +``` + +1. **Authentication**: User identity extracted (user_id, username) +2. **Role Resolution**: Determine user's roles based on auth method +3. **Access Resolution**: Check if any role grants the requested action + +## Role Resolution + +Determines user roles based on the authentication method used. + +### No-op/K8s Authentication + +Uses a no-op role resolver: +- All users get the special `*` (everyone) role only +- No additional roles assigned +- Future versions may expand K8s role resolution + +### JWK Token Authentication + +Uses JWT claims to determine user roles through JSONPath expressions. +See [JWK Token - Role Extraction](jwk-token.md#role-extraction) for configuration. + +If no role rules are configured, falls back to the no-op resolver. + +### Red Hat Identity Authentication + +Currently uses no-op role resolution: +- All users get the `*` (everyone) role +- Future versions may support org-based or entitlement-based roles + +## Access Resolution + +Once roles are determined, access resolvers check whether any role grants the +requested action. + +### No-op Resolver + +Grants all users access to all actions. Used when: +- No access rules are configured +- No-op authentication is configured +- K8s authentication is configured (currently) + +### Rule-based Resolver + +Checks user's roles against configured access rules. Also grants admin users +unrestricted access - any user with the `admin` action can perform all other +actions. + +## Configuring Access Rules + +Define which roles can perform which actions in the `authorization` section: + +```yaml +authorization: + access_rules: + # Everyone can query and get info + - role: "*" + actions: ["query", "info"] + + # Managers have full admin access + - role: "manager" + actions: ["admin"] + + # Employees can list their conversations + - role: "employee" + actions: ["list_conversations"] + + # Developers have extended access + - role: "developer" + actions: ["query", "get_config", "list_conversations"] +``` + +### Special Roles + +| Role | Description | +|------|-------------| +| `*` | Everyone - matches all authenticated users | + +### Special Actions + +| Action | Description | +|--------|-------------| +| `admin` | Grants unrestricted access to ALL other actions | + +## Available Actions + +| Action | Description | Endpoints | +|--------|-------------|-----------| +| `admin` | Full administrative access | All endpoints | +| `query` | Submit queries | `/v1/query` | +| `streaming_query` | Submit streaming queries | `/v1/streaming_query` | +| `info` | Access service info | `/`, `/info`, `/readiness`, `/liveness` | +| `get_config` | View configuration | `/config` | +| `get_models` | List available models | `/models` | +| `get_tools` | List available tools | `/tools`, `/mcp-auth/client-options` | +| `get_shields` | List safety shields | `/shields` | +| `list_providers` | List providers | `/providers` | +| `get_provider` | Get provider details | `/providers/{provider_id}` | +| `get_metrics` | Access metrics | `/metrics` | +| `feedback` | Submit feedback | `/feedback` | +| `model_override` | Override model in queries | N/A (permission flag) | + +### Conversation Actions + +| Action | Description | +|--------|-------------| +| `list_conversations` | List own conversations | +| `list_other_conversations` | List other users' conversations | +| `get_conversation` | Get own conversation details | +| `read_other_conversations` | Read other users' conversations | +| `delete_conversation` | Delete own conversations | +| `delete_other_conversations` | Delete other users' conversations | +| `query_other_conversations` | Query other users' conversations | + +## Example Configurations + +### Minimal (Everyone Can Query) + +```yaml +authorization: + access_rules: + - role: "*" + actions: ["query", "streaming_query", "info"] +``` + +### Admin + Regular Users + +```yaml +authorization: + access_rules: + - role: "*" + actions: ["query", "info"] + - role: "admin" + actions: ["admin"] +``` + +### Team-based Access + +```yaml +authorization: + access_rules: + # Everyone gets basic access + - role: "*" + actions: ["info"] + + # Developers can query and see config + - role: "developer" + actions: ["query", "streaming_query", "get_config", "list_conversations"] + + # SRE team gets metrics access + - role: "sre" + actions: ["get_metrics", "info"] + + # Team leads get admin access + - role: "team_lead" + actions: ["admin"] +``` + +### Read-only Access Pattern + +```yaml +authorization: + access_rules: + - role: "*" + actions: ["info", "get_models", "get_tools"] + - role: "user" + actions: ["query"] + - role: "viewer" + actions: ["list_conversations", "get_conversation"] +``` + +## Authorization Flow + +``` +1. Request arrives + ↓ +2. Authentication extracts user identity + ↓ +3. Role resolver determines user's roles + (e.g., ["*", "developer", "team_lead"]) + ↓ +4. Endpoint requires specific action (e.g., "query") + ↓ +5. Access resolver checks: + - Does any user role have "admin" action? → Allow all + - Does any user role have "query" action? → Allow + - No matching rules? → Deny (403) + ↓ +6. Request proceeds or is rejected +``` + +## Controlling Model Overrides + +By default, clients may specify `model` and `provider` in query requests. +This can be restricted using the `model_override` action: + +```yaml +authorization: + access_rules: + - role: "*" + actions: ["query", "info"] + - role: "power_user" + actions: ["model_override"] +``` + +Requests that include `model` or `provider` without the `model_override` +permission are rejected with HTTP 403. + +## Troubleshooting + +### Common Issues + +| Issue | Cause | Solution | +|-------|-------|----------| +| 403 on all requests | No access rules configured | Add `access_rules` section | +| 403 for specific action | Role doesn't have action | Add action to role's actions list | +| Admin can't access endpoint | Using wrong role name | Use `admin` action, not role | + +### Debugging + +Check which roles a user has: +1. Enable debug logging +2. Look for role resolution logs +3. Verify JWT claims (for jwk-token) or identity header (for rh-identity) diff --git a/docs/auth/index.md b/docs/auth/index.md new file mode 100644 index 000000000..6cb840111 --- /dev/null +++ b/docs/auth/index.md @@ -0,0 +1,68 @@ +# Authentication and Authorization + +Lightspeed Core Stack implements a modular authentication and authorization +system with multiple authentication methods. Authorization is configurable +through role-based access control. + +## Configuration + +The authentication system is configured via the `authentication` section in +the Lightspeed Core Stack configuration file (`lightspeed-stack.yaml`). + +```yaml +authentication: + module: + # Module-specific configuration options +``` + +## Available Modules + +| Module | Use Case | Documentation | +|--------|----------|---------------| +| `noop` | Development only - no security | [No-op Modules](noop.md) | +| `noop-with-token` | Development with token passthrough | [No-op Modules](noop.md) | +| `k8s` | Kubernetes/OpenShift deployments | [Kubernetes](kubernetes.md) | +| `jwk-token` | JWT/OAuth2 authentication | [JWK Token](jwk-token.md) | +| `api-key-token` | Static API key authentication | [API Key Token](api-key-token.md) | +| `rh-identity` | Red Hat Hybrid Cloud Console | [Red Hat Identity](rh-identity.md) | + +## Choosing a Module + +### Production Deployments + +- **Kubernetes/OpenShift**: Use `k8s` for native cluster authentication +- **Red Hat Console**: Use `rh-identity` when behind console.redhat.com +- **OAuth2/OIDC**: Use `jwk-token` with your identity provider +- **Simple API Access**: Use `api-key-token` for service-to-service auth + +### Development + +- **Local Testing**: Use `noop` for quick iteration without auth overhead +- **Token Testing**: Use `noop-with-token` to test token passthrough behavior + +## Authentication Flow + +``` +Request → Authentication Module → User Identity → Role Resolution → Access Check → Endpoint +``` + +Each authentication module extracts user identity (user_id, username) from the +request. The authorization system then determines what actions the user can +perform based on their assigned roles. + +## Authentication Tuple + +All authentication modules return a consistent tuple containing: + +| Field | Type | Description | +|-------|------|-------------| +| `user_id` | `str` | Unique identifier for the user | +| `username` | `str` | Human-readable username | +| `skip_userid_check` | `bool` | Whether to skip user ID validation | +| `token` | `str` | Authentication token (if applicable) | + +This tuple is used by downstream components for authorization and audit logging. + +## Authorization + +For role-based access control configuration, see [Authorization](authorization.md). diff --git a/docs/auth/jwk-token.md b/docs/auth/jwk-token.md new file mode 100644 index 000000000..a8da65f61 --- /dev/null +++ b/docs/auth/jwk-token.md @@ -0,0 +1,178 @@ +# JWK Token Authentication (`jwk-token`) + +JSON Web Key Set (JWK) based authentication for scenarios requiring JWT token +validation. Commonly used with OAuth2/OIDC identity providers. + +## Overview + +The `jwk-token` module: +1. Fetches the JWK set from a configured URL +2. Validates JWT signatures against the JWK set +3. Extracts user identity from configurable JWT claims +4. Supports role extraction for authorization + +## Configuration + +### Basic Configuration + +```yaml +authentication: + module: jwk-token + jwk_config: + url: https://auth.example.com/.well-known/jwks.json +``` + +### Full Configuration + +```yaml +authentication: + module: jwk-token + jwk_config: + url: https://auth.example.com/.well-known/jwks.json + jwt_configuration: + user_id_claim: sub # optional, defaults to 'sub' + username_claim: preferred_username # optional, defaults to 'preferred_username' + role_rules: [] # optional, see Role Extraction below +``` + +### Configuration Options + +| Option | Required | Default | Description | +|--------|----------|---------|-------------| +| `url` | Yes | - | URL to fetch JWK set | +| `user_id_claim` | No | `sub` | JWT claim for user ID | +| `username_claim` | No | `preferred_username` | JWT claim for username | +| `role_rules` | No | `[]` | Rules for extracting roles from JWT | + +## Behavior + +### JWK Set Caching + +- JWK set is fetched from the configured URL +- Cached for 1 hour to reduce network overhead +- Automatically refreshed when cache expires + +### Token Validation + +1. Extract bearer token from `Authorization` header +2. Decode JWT and extract key ID (`kid`) +3. Find matching key in JWK set +4. Validate signature using the key +5. Extract user identity from claims + +### Guest Access + +If no `Authorization` header is present, the module returns default guest-like +credentials instead of rejecting the request. + +## Role Extraction + +Extract roles from JWT claims using JSONPath expressions for use with the +authorization system. + +### Role Rules Configuration + +```yaml +authentication: + module: jwk-token + jwk_config: + url: https://auth.example.com/.well-known/jwks.json + jwt_configuration: + role_rules: + # Match if realm_access.roles contains "manager" + - jsonpath: "$.realm_access.roles[*]" + operator: contains + value: "manager" + roles: ["manager"] + + # Match if org_id equals specific values + - jsonpath: "$.org_id" + operator: equals + value: [["dummy_corp"]] + roles: ["dummy_employee"] + + # Match if user is in specific groups + - jsonpath: "$.groups[*]" + operator: in + value: ["developers", "qa"] + roles: ["developer"] + negate: false +``` + +### Rule Fields + +| Field | Required | Description | +|-------|----------|-------------| +| `jsonpath` | Yes | JSONPath expression to extract values from JWT | +| `operator` | Yes | Comparison operator (see below) | +| `value` | Yes | Value(s) to compare against | +| `roles` | Yes | Roles to assign if rule matches | +| `negate` | No | Invert match result (default: false) | + +### Operators + +| Operator | Description | +|----------|-------------| +| `equals` | Exact match between extracted and configured values | +| `contains` | Extracted value contains the configured string | +| `in` | Extracted value is in the configured list | +| `match` | Regex pattern match (uses pre-compiled patterns) | + +> **Note**: JSONPath expressions always yield a list of values, even for +> single-value expressions. Comparisons should account for this. + +## Request Example + +```bash +# Get token from your identity provider +TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." + +# Make authenticated request +curl http://localhost:8080/v1/query \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"query": "Hello"}' +``` + +## Common Identity Providers + +### Keycloak + +```yaml +authentication: + module: jwk-token + jwk_config: + url: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs + jwt_configuration: + user_id_claim: sub + username_claim: preferred_username + role_rules: + - jsonpath: "$.realm_access.roles[*]" + operator: contains + value: "admin" + roles: ["admin"] +``` + +### Auth0 + +```yaml +authentication: + module: jwk-token + jwk_config: + url: https://your-tenant.auth0.com/.well-known/jwks.json + jwt_configuration: + user_id_claim: sub + username_claim: email +``` + +### Azure AD + +```yaml +authentication: + module: jwk-token + jwk_config: + url: https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys + jwt_configuration: + user_id_claim: oid + username_claim: preferred_username +``` diff --git a/docs/auth/kubernetes.md b/docs/auth/kubernetes.md new file mode 100644 index 000000000..2e8c0af20 --- /dev/null +++ b/docs/auth/kubernetes.md @@ -0,0 +1,131 @@ +# Kubernetes Authentication (`k8s`) + +Kubernetes-based authentication for running Lightspeed Stack in Kubernetes or +OpenShift environments. Users must have a valid Kubernetes token and appropriate +RBAC permissions. + +## Overview + +The `k8s` authentication module: +1. Validates bearer tokens via the Kubernetes TokenReview API +2. Performs authorization checks using SubjectAccessReview (SAR) +3. Checks access to a configured virtual path (default: `/ls-access`) with `get` verb +4. Extracts user ID and username from token claims + +## RBAC Requirements + +Users must have the `get` permission on the Kubernetes RBAC non-resource URL +`/ls-access`. Create a ClusterRole and ClusterRoleBinding: + +```yaml +# Allow GET on non-resource URL /ls-access +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: lightspeed-access +rules: + - nonResourceURLs: ["/ls-access"] + verbs: ["get"] +--- +# Bind to a user, group, or service account +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: lightspeed-access-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: lightspeed-access +subjects: + - kind: User # or ServiceAccount, Group + name: SOME_USER_OR_SA + apiGroup: rbac.authorization.k8s.io +``` + +## Configuration + +### In-Cluster Deployment + +When deploying inside a Kubernetes cluster, connection details are automatically +detected: + +```yaml +authentication: + module: k8s +``` + +### External Deployment + +When running outside the cluster or connecting to external clusters, specify +connection details: + +```yaml +authentication: + module: k8s + k8s_cluster_api: https://kubernetes.default.svc + k8s_ca_cert_path: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + skip_tls_verification: false # optional, insecure +``` + +### Configuration Options + +| Option | Required | Description | +|--------|----------|-------------| +| `k8s_cluster_api` | No | Kubernetes API server URL (auto-detected in-cluster) | +| `k8s_ca_cert_path` | No | Path to CA certificate for TLS verification | +| `skip_tls_verification` | No | Skip TLS verification (insecure, not recommended) | + +## Behavior + +### Token Validation + +1. Extract bearer token from `Authorization` header +2. Submit token to Kubernetes TokenReview API +3. Verify authentication success +4. Extract user identity from token claims + +### Authorization Check + +1. Create SubjectAccessReview for the virtual path +2. Check if user has `get` permission on `/ls-access` +3. Reject request if access is denied + +### Special Cases + +- **kube:admin user**: Uses cluster ID as user ID for consistent identification + +## Request Example + +```bash +# Get your Kubernetes token +TOKEN=$(kubectl create token my-service-account) + +# Make authenticated request +curl http://localhost:8080/v1/query \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"query": "Hello"}' +``` + +## Troubleshooting + +### Common Issues + +| Error | Cause | Solution | +|-------|-------|----------| +| 401 Unauthorized | Invalid or expired token | Refresh token or check service account | +| 403 Forbidden | Missing RBAC permissions | Add ClusterRoleBinding for user/SA | +| Connection refused | Wrong API server URL | Check `k8s_cluster_api` setting | +| Certificate error | TLS verification failed | Check `k8s_ca_cert_path` or use valid certs | + +### Debugging + +Check token validity: +```bash +kubectl auth can-i get /ls-access --as=system:serviceaccount:namespace:sa-name +``` + +Check service account permissions: +```bash +kubectl auth can-i --list --as=system:serviceaccount:namespace:sa-name +``` diff --git a/docs/auth/noop.md b/docs/auth/noop.md new file mode 100644 index 000000000..c8893ca50 --- /dev/null +++ b/docs/auth/noop.md @@ -0,0 +1,80 @@ +# No-op Authentication Modules + +Development-only authentication modules that bypass or minimize security checks. +These modules are intended for local development and testing only. + +> **Warning**: Never use these modules in production environments. + +## No-op (`noop`) + +Development authentication that completely bypasses security checks. + +### Configuration + +```yaml +authentication: + module: noop +``` + +### Behavior + +- Accepts any request without token validation +- Extracts `user_id` from query parameters (defaults to `00000000-0000-0000-0000-000`) +- Uses fixed username `lightspeed-user` +- No authorization checks performed + +### Use Cases + +- Local development without authentication overhead +- Quick prototyping and testing +- CI/CD pipeline testing + +## No-op with Token (`noop-with-token`) + +Development authentication that requires tokens but doesn't validate them. + +### Configuration + +```yaml +authentication: + module: noop-with-token +``` + +### Behavior + +- Extracts bearer token from the `Authorization` header +- Same user ID and username handling as `noop` +- Token is passed through unvalidated for downstream use +- Useful for testing token passthrough behavior + +### Use Cases + +- Testing downstream components that need tokens +- Verifying token extraction logic +- Development environments with partial authentication + +## Request Examples + +### No-op Module + +```bash +# Basic request (no auth required) +curl http://localhost:8080/v1/query \ + -H "Content-Type: application/json" \ + -d '{"query": "Hello"}' + +# With custom user_id +curl "http://localhost:8080/v1/query?user_id=test-user-123" \ + -H "Content-Type: application/json" \ + -d '{"query": "Hello"}' +``` + +### No-op with Token Module + +```bash +# Token required but not validated +curl http://localhost:8080/v1/query \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer any-token-value" \ + -d '{"query": "Hello"}' +``` diff --git a/docs/auth/rh-identity.md b/docs/auth/rh-identity.md new file mode 100644 index 000000000..0c7a316ad --- /dev/null +++ b/docs/auth/rh-identity.md @@ -0,0 +1,323 @@ +# Red Hat Identity Authentication (`rh-identity`) + +Red Hat Identity header authentication for deployments behind Red Hat Hybrid +Cloud Console infrastructure (e.g., console.redhat.com, Insights). This module +validates the `x-rh-identity` header provided by Red Hat's authentication proxy. + +## Overview + +The `rh-identity` module: +1. Extracts the `x-rh-identity` header from incoming requests +2. Base64 decodes and parses the JSON payload +3. Validates the identity structure based on type (User or System) +4. Optionally validates service entitlements +5. Extracts user identity for downstream use + +## Architecture + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐ +│ Client/RHEL │──────▶ Red Hat Auth │──────▶ Lightspeed Stack │ +│ System │ │ Proxy │ │ (rh-identity) │ +└─────────────────┘ └──────────────────┘ └─────────────────────┘ + │ │ + │ Adds x-rh-identity │ Validates header + │ header to request │ Extracts identity + ▼ ▼ +``` + +The authentication proxy (part of Red Hat's infrastructure) authenticates users +via SSO or systems via certificate authentication, then injects the +`x-rh-identity` header containing the verified identity information. + +## Configuration + +### Basic Configuration + +```yaml +authentication: + module: rh-identity +``` + +### With Entitlement Validation + +```yaml +authentication: + module: rh-identity + rh_identity_config: + required_entitlements: + - rhel + - insights +``` + +### Configuration Options + +| Option | Required | Default | Description | +|--------|----------|---------|-------------| +| `required_entitlements` | No | `[]` | List of service entitlements to require | + +When `required_entitlements` is configured, **ALL** listed entitlements must be +present and entitled in the identity header. Omit this field to disable +entitlement validation entirely. + +## Identity Types + +The `x-rh-identity` header supports two identity types, each with different +structure and use cases. + +### User Identity + +Console users authenticated via Red Hat SSO. Used when humans access services +through the Hybrid Cloud Console. + +**Identity extraction:** +- `user_id`: From `identity.user.user_id` +- `username`: From `identity.user.username` + +**Header structure:** +```json +{ + "identity": { + "account_number": "123456", + "org_id": "654321", + "type": "User", + "user": { + "user_id": "abc123", + "username": "user@example.com", + "is_org_admin": false, + "is_internal": false, + "locale": "en_US" + } + }, + "entitlements": { + "rhel": {"is_entitled": true, "is_trial": false}, + "insights": {"is_entitled": true, "is_trial": false}, + "ansible": {"is_entitled": false, "is_trial": false} + } +} +``` + +**Available User fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `user_id` | string | Unique user identifier | +| `username` | string | User's email or username | +| `is_org_admin` | boolean | Whether user is an organization admin | +| `is_internal` | boolean | Whether user is a Red Hat internal user | +| `locale` | string | User's locale preference | + +### System Identity + +Certificate-authenticated RHEL systems. Used when RHEL hosts access services +directly (e.g., Insights client, subscription-manager). + +**Identity extraction:** +- `user_id`: From `identity.system.cn` (certificate Common Name) +- `username`: From `identity.account_number` + +**Header structure:** +```json +{ + "identity": { + "account_number": "123456", + "org_id": "654321", + "type": "System", + "system": { + "cn": "c87dcb4c-8af1-40dd-878e-60c744edddd0", + "cert_type": "system" + } + }, + "entitlements": { + "rhel": {"is_entitled": true, "is_trial": false} + } +} +``` + +**Available System fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `cn` | string | Certificate Common Name (system UUID) | +| `cert_type` | string | Certificate type (usually "system") | + +## Common Identity Fields + +Both identity types share these top-level fields: + +| Field | Type | Description | +|-------|------|-------------| +| `account_number` | string | Red Hat account number | +| `org_id` | string | Organization ID | +| `type` | string | Identity type: "User" or "System" | + +## Entitlements + +Entitlements represent service subscriptions. Each entitlement has: + +| Field | Type | Description | +|-------|------|-------------| +| `is_entitled` | boolean | Whether the account has this entitlement | +| `is_trial` | boolean | Whether this is a trial entitlement | + +**Common entitlement names:** +- `rhel` - Red Hat Enterprise Linux +- `insights` - Red Hat Insights +- `ansible` - Ansible Automation Platform +- `openshift` - OpenShift Container Platform + +## Accessing Identity Data in Application + +The module stores the parsed identity data in `request.state.rh_identity_data` +for downstream access: + +```python +from fastapi import Request + +async def my_endpoint(request: Request): + rh_identity = request.state.rh_identity_data + + # Get organization ID + org_id = rh_identity.get_org_id() + + # Check specific entitlement + has_insights = rh_identity.has_entitlement("insights") + + # Get user info + user_id = rh_identity.get_user_id() + username = rh_identity.get_username() +``` + +### Available Methods + +| Method | Returns | Description | +|--------|---------|-------------| +| `get_user_id()` | `str` | User ID or system CN | +| `get_username()` | `str` | Username or account number | +| `get_org_id()` | `str` | Organization ID | +| `has_entitlement(service)` | `bool` | Check single entitlement | +| `has_entitlements(services)` | `bool` | Check ALL entitlements in list | + +## Request Examples + +### Creating a Test Header + +```bash +# User identity +IDENTITY='{ + "identity": { + "account_number": "123456", + "org_id": "654321", + "type": "User", + "user": { + "user_id": "test-user-id", + "username": "testuser@example.com" + } + }, + "entitlements": { + "rhel": {"is_entitled": true, "is_trial": false} + } +}' + +# Base64 encode +HEADER=$(echo -n "$IDENTITY" | base64) + +# Make request +curl http://localhost:8080/v1/query \ + -H "Content-Type: application/json" \ + -H "x-rh-identity: $HEADER" \ + -d '{"query": "Hello"}' +``` + +### System Identity Example + +```bash +# System identity +IDENTITY='{ + "identity": { + "account_number": "123456", + "org_id": "654321", + "type": "System", + "system": { + "cn": "c87dcb4c-8af1-40dd-878e-60c744edddd0" + } + }, + "entitlements": { + "rhel": {"is_entitled": true, "is_trial": false} + } +}' + +HEADER=$(echo -n "$IDENTITY" | base64) + +curl http://localhost:8080/v1/query \ + -H "Content-Type: application/json" \ + -H "x-rh-identity: $HEADER" \ + -d '{"query": "Hello"}' +``` + +## Error Responses + +| Status | Condition | Response | +|--------|-----------|----------| +| 401 | Missing `x-rh-identity` header | `{"detail": "Missing x-rh-identity header"}` | +| 400 | Invalid base64 encoding | `{"detail": "Invalid base64 encoding in x-rh-identity header"}` | +| 400 | Invalid JSON | `{"detail": "Invalid JSON in x-rh-identity header"}` | +| 400 | Missing `identity` field | `{"detail": "Missing 'identity' field"}` | +| 400 | Missing identity `type` | `{"detail": "Missing identity 'type' field"}` | +| 400 | Missing `user` for User type | `{"detail": "Missing 'user' field for User type"}` | +| 400 | Missing `user_id` in user | `{"detail": "Missing 'user_id' in user data"}` | +| 400 | Missing `username` in user | `{"detail": "Missing 'username' in user data"}` | +| 400 | Missing `system` for System type | `{"detail": "Missing 'system' field for System type"}` | +| 400 | Missing `cn` in system | `{"detail": "Missing 'cn' in system data"}` | +| 400 | Missing `account_number` for System | `{"detail": "Missing 'account_number' for System type"}` | +| 400 | Unsupported identity type | `{"detail": "Unsupported identity type: X"}` | +| 403 | Missing required entitlements | `{"detail": "Missing required entitlement: rhel"}` | + +## Deployment Considerations + +### Behind the Hybrid Cloud Console + +When deployed behind console.redhat.com or similar Red Hat infrastructure: +1. The authentication proxy handles all authentication +2. The `x-rh-identity` header is automatically injected +3. Configure Lightspeed Stack to trust this header + +### Security Notes + +- **Never expose directly to the internet** - The `x-rh-identity` header can be + forged. Always deploy behind Red Hat's authentication proxy. +- **Validate entitlements** - Use `required_entitlements` to ensure only + authorized accounts can access the service. +- **Log audit events** - The extracted user/system identity should be logged + for audit purposes. + +### Local Development + +For local testing without the authentication proxy, you can manually create +headers using the examples above. Consider using `noop` module instead for +simpler local development. + +## Troubleshooting + +### Common Issues + +| Issue | Cause | Solution | +|-------|-------|----------| +| 401 errors | Header not being forwarded | Check proxy/ingress configuration | +| 400 errors | Malformed header | Validate JSON structure, check base64 encoding | +| 403 errors | Missing entitlements | Verify account has required subscriptions | +| Wrong user ID | Using System identity | Check identity type in header | + +### Debugging + +Enable debug logging to see identity extraction: +```yaml +service: + color_log: true + log_level: DEBUG +``` + +Look for log entries like: +``` +RH Identity authenticated: user_id=abc123, username=user@example.com +``` diff --git a/examples/lightspeed-stack-rh-identity.yaml b/examples/lightspeed-stack-rh-identity.yaml index 8c6e9edac..c67d684ac 100644 --- a/examples/lightspeed-stack-rh-identity.yaml +++ b/examples/lightspeed-stack-rh-identity.yaml @@ -1,3 +1,18 @@ +# Red Hat Identity Authentication Example +# +# This configuration is for deployments behind Red Hat Hybrid Cloud Console +# infrastructure (console.redhat.com, Insights, etc.) where an authentication +# proxy injects the x-rh-identity header. +# +# For detailed documentation, see: docs/auth/rh-identity.md +# +# Identity Types Supported: +# - User: Console users authenticated via Red Hat SSO +# - System: Certificate-authenticated RHEL systems +# +# Security Note: This module trusts the x-rh-identity header. Only use +# behind Red Hat's authentication proxy - never expose directly to the internet. + name: Red Hat Identity Authentication Example service: host: localhost @@ -10,14 +25,81 @@ llama_stack: use_as_library_client: false url: http://localhost:8321 authentication: - module: "rh-identity" + module: rh-identity + rh_identity_config: - # Option 1: Single entitlement validation - required_entitlements: ["rhel"] + # Entitlement Validation + # ---------------------- + # When configured, ALL listed entitlements must be present and is_entitled=true. + # Requests without required entitlements receive HTTP 403. + # + # Common entitlement names: + # - rhel: Red Hat Enterprise Linux + # - insights: Red Hat Insights + # - ansible: Ansible Automation Platform + # - openshift: OpenShift Container Platform + + # Option 1: Single entitlement - require RHEL subscription + required_entitlements: + - rhel - # Option 2: Multiple entitlements validation - ALL required (uncomment to use, comment out option 1) + # Option 2: Multiple entitlements - require BOTH rhel AND insights # required_entitlements: - # - "rhel" - # - "ansible" + # - rhel + # - insights + + # Option 3: No entitlement validation - allow any authenticated identity + # Omit the required_entitlements field entirely or leave it empty: + # required_entitlements: [] + +# Authorization (optional) +# ------------------------ +# Configure role-based access control for fine-grained permissions. +# By default, all authenticated users can access all endpoints. +# +# authorization: +# access_rules: +# # All authenticated users can query and check service info +# - role: "*" +# actions: +# - query +# - streaming_query +# - info +# +# # Users with admin entitlements get full access (configure via role rules) +# # Note: rh-identity currently uses no-op role resolution, so custom roles +# # would need to be assigned via a different mechanism or future enhancement. + +# User Data Collection (optional) +# ------------------------------- +# user_data_collection: +# feedback_enabled: true +# feedback_storage: /var/data/lightspeed/feedback +# transcripts_enabled: true +# transcripts_storage: /var/data/lightspeed/transcripts - # Option 3: No validation - omit required_entitlements field (comment out both options above) +# Testing the Configuration +# ------------------------- +# Create a test identity header: +# +# IDENTITY='{ +# "identity": { +# "account_number": "123456", +# "org_id": "654321", +# "type": "User", +# "user": { +# "user_id": "test-user-id", +# "username": "testuser@example.com" +# } +# }, +# "entitlements": { +# "rhel": {"is_entitled": true, "is_trial": false} +# } +# }' +# +# HEADER=$(echo -n "$IDENTITY" | base64) +# +# curl http://localhost:8080/v1/query \ +# -H "Content-Type: application/json" \ +# -H "x-rh-identity: $HEADER" \ +# -d '{"query": "Hello"}'