From ce0399933c81a327dbf72ed4f3075daa30c992a9 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 12 Aug 2026 13:54:05 -0400 Subject: [PATCH 1/7] feat(auth): document fine-grained RBAC --- .../configuring-the-helm-chart.md | 64 +++++++- docs/_subsections/configuring-the-operator.md | 138 +++++++++++++++++- 2 files changed, 196 insertions(+), 6 deletions(-) diff --git a/docs/_subsections/configuring-the-helm-chart.md b/docs/_subsections/configuring-the-helm-chart.md index e26c3f73..bf90a834 100644 --- a/docs/_subsections/configuring-the-helm-chart.md +++ b/docs/_subsections/configuring-the-helm-chart.md @@ -237,13 +237,13 @@ When installed with authentication.openshift.enabled=false but authentication.openshift.enabled=false but :` key (for example `activerecordings:read`) and is mapped to a **Kubernetes** resource/verb pair (for example `pods/exec:create`). The built-in default mapping for every permission is `pods/exec:create`. Admins can override individual permissions via `core.config.extra.envVars` by setting environment variables of the form `CRYOSTAT_SECURITY_RBAC_PERMISSIONS____` to a value of the form `resource[/subresource]:verb`. + +**Cryostat** caches authorization decisions for up to one minute. Changes to user Roles or RoleBindings may take up to one minute to take effect. + +> **Note:** When Basic authentication (`authentication.basicAuth.enabled=true`) is also enabled, fine-grained RBAC checks are bypassed. Access becomes all-or-nothing for all authenticated users. + +##### Granting read-only access + +To give some users read-only access and others full access, remap every `read` permission to `pods:get` (a lower privilege granted by the built-in **OpenShift** `view` role) while leaving mutating permissions at the default `pods/exec:create` (granted by `admin` or `edit`). + +Pass the permission overrides via `core.config.extra.envVars` when installing or upgrading the chart. The example below shows `helm upgrade` syntax using `--set-json`: + +```bash +helm upgrade cryostat ./charts/cryostat -n \ + --set authentication.openshift.enabled=true \ + --set-json 'core.config.extra.envVars=[ + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__ACTIVERECORDINGS_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__ARCHIVEDRECORDINGS_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__ASYNCPROFILER_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__AUTOMATEDRULES_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__CREDENTIALS_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__DISCOVERYNODES_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__DISCOVERYPLUGINS_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__EVENTTEMPLATES_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__EVENTTYPES_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__HEAPDUMPS_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__MATCHEXPRESSIONS_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__PROBES_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__PROBETEMPLATES_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__RECORDINGMETADATA_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__REPORTS_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__TARGETS_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__THREADDUMPS_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__UNIFIEDLOGS_READ_","value":"pods:get"} + ]' +``` + +With this configuration, apply **OpenShift** RBAC as follows: + +- Grant `view` in the **Cryostat** installation namespace for read-only users: + ``` + oc adm policy add-role-to-user -n view + ``` +- Grant `admin` or `edit` in the **Cryostat** installation namespace for full-access users: + ``` + oc adm policy add-role-to-user -n admin + ``` + +A user who has neither role will be able to log in (they pass the `get pods` proxy access review), but all **Cryostat** API requests will be rejected with `403 Forbidden` until they are granted an appropriate role. + +For a full list of available permission keys and their defaults, see the [Full permission reference](#full-permission-reference) table in the Operator configuration section. + + ### Other Parameters | Name | Description | Value | diff --git a/docs/_subsections/configuring-the-operator.md b/docs/_subsections/configuring-the-operator.md index cac54a58..80cdf2a7 100644 --- a/docs/_subsections/configuring-the-operator.md +++ b/docs/_subsections/configuring-the-operator.md @@ -355,9 +355,10 @@ spec: ### Authorization Options -On **OpenShift**, the authentication/authorization proxy deployed in front of the **Cryostat** application requires all users to pass a `create pods/exec` access review in the **Cryostat** installation namespace +On **OpenShift**, the authentication/authorization proxy deployed in front of the **Cryostat** application requires all users to pass a `get pods` access review in the **Cryostat** installation namespace by default. This means that access to the **Cryostat** application is granted to exactly the set of **OpenShift** cluster user accounts and service accounts which have this Role. This can be configured using `spec.authorizationOptions.openShiftSSO.accessReview` as depicted below, but note that the `namespace` field should always be included and in most cases should match the **Cryostat** installation namespace. +This Role only grants basic access to the Cryostat API and UI - see [below](#fine-grained-rbac-on-openshift) for further details on how Cryostat handles RBAC checks for specific actions. The auth proxy may also be configured to allow Basic authentication by creating a **Secret** containing an `htpasswd` user file. An `htpasswd` file granting access to a user named `user` with the password `pass` can be generated like this: `htpasswd -cbB htpasswd.conf user pass`. The password should use `bcrypt` hashing, specified by the `-B` flag. @@ -377,15 +378,146 @@ spec: openShiftSSO: # only effective when running on OpenShift disable: false # set this to `true` to disable OpenShift SSO integration accessReview: # override this to change the required Role for users and service accounts to access the application - verb: create + verb: get resource: pods - subresource: exec namespace: cryostat-install-namespace basicAuth: secretName: my-secret # a Secret with this name must exist in the Cryostat installation namespace filename: htpasswd.conf # the name of the htpasswd user file within the Secret ``` +#### Fine-Grained RBAC on OpenShift + +When **OpenShift** SSO integration is enabled and Basic authentication is **not** enabled, **Cryostat** activates fine-grained RBAC mode. In this mode, **Cryostat** performs a `SelfSubjectAccessReview` +against the **OpenShift** cluster for every incoming API request, checking whether the authenticated user holds a sufficiently privileged **OpenShift** Role for the specific **Cryostat** resource and +operation being requested. This allows an admin to assign some users full access to **Cryostat** and others only read access, using standard **OpenShift** (Cluster)Role and (Cluster)RoleBinding objects. + +Each **Cryostat** API permission is expressed as a `:` key (for example `activerecordings:read`) and is mapped to a **Kubernetes** resource/verb pair (for example `pods/exec:create`). +When a user makes a request, **Cryostat** checks whether they are permitted to perform the mapped **Kubernetes** action, scoped to the **Cryostat** installation namespace. If the user passes the +check, the request proceeds; otherwise it is rejected with a `403 Forbidden` response. + +The built-in default mapping for every permission is `pods/exec:create`. This means that, out of the box, a user needs the (Cluster)Role which would grant them the ability to invoke shell commands +within Pods in the **Cryostat** installation namespace. Admins can override this default per-permission via `spec.authorizationOptions.rbacPermissions`, remapping individual permissions to +lower-privilege **Kubernetes** roles in order to extend read-only access to less-privileged users, or to create custom (Cluster)Roles and (Cluster)RoleBindings as needed. + +**Cryostat** caches authorization decisions for one minute by default. Changes to user Roles or RoleBindings may take up to one minute to take effect. + +> **Note:** When Basic authentication is enabled alongside **OpenShift** SSO, fine-grained RBAC checks are bypassed for all authenticated users. Access becomes all-or-nothing for any user who successfully authenticates. + +##### Granting read-only access + +The example below remaps every `read` permission to `pods:get`, while leaving all mutating operations mapped to the default `pods/exec:create`. A user who holds the built-in **OpenShift** `view` role +in the **Cryostat** installation namespace passes `pods:get` checks and therefore gets read-only access. A user who holds `admin` or `edit` passes `pods/exec:create` checks and therefore gets full access. + +```yaml +apiVersion: operator.cryostat.io/v1beta2 +kind: Cryostat +metadata: + name: cryostat-sample +spec: + authorizationOptions: + rbacPermissions: + activerecordings:read: pods:get + archivedrecordings:read: pods:get + asyncprofiler:read: pods:get + automatedrules:read: pods:get + credentials:read: pods:get + discoverynodes:read: pods:get + discoveryplugins:read: pods:get + eventtemplates:read: pods:get + eventtypes:read: pods:get + heapdumps:read: pods:get + matchexpressions:read: pods:get + probes:read: pods:get + probetemplates:read: pods:get + recordingmetadata:read: pods:get + reports:read: pods:get + targets:read: pods:get + threaddumps:read: pods:get + unifiedlogs:read: pods:get +``` + +With this configuration, apply **OpenShift** RBAC as follows: + +- Grant `view` in the **Cryostat** installation namespace for read-only users: + ``` + oc adm policy add-role-to-user -n view + ``` +- Grant `admin` or `edit` in the **Cryostat** installation namespace for full-access users: + ``` + oc adm policy add-role-to-user -n admin + ``` + +A user who has neither role may be able to log in (pass the `get pods` proxy access review), but all **Cryostat** API requests will be rejected with `403 Forbidden` until they are granted an appropriate role. + +##### Full permission reference + +The following table lists every available `:` key and its default **Kubernetes** permission mapping. + +| Key | Default mapping | +|---|---| +| `activerecordings:read` | `pods/exec:create` | +| `activerecordings:write` | `pods/exec:create` | +| `activerecordings:delete` | `pods/exec:create` | +| `archivedrecordings:read` | `pods/exec:create` | +| `archivedrecordings:write` | `pods/exec:create` | +| `archivedrecordings:delete` | `pods/exec:create` | +| `asyncprofiler:read` | `pods/exec:create` | +| `asyncprofiler:write` | `pods/exec:create` | +| `asyncprofiler:delete` | `pods/exec:create` | +| `automatedrules:read` | `pods/exec:create` | +| `automatedrules:write` | `pods/exec:create` | +| `automatedrules:delete` | `pods/exec:create` | +| `credentials:read` | `pods/exec:create` | +| `credentials:write` | `pods/exec:create` | +| `credentials:delete` | `pods/exec:create` | +| `discoverynodes:read` | `pods/exec:create` | +| `discoverynodes:write` | `pods/exec:create` | +| `discoveryplugins:read` | `pods/exec:create` | +| `discoveryplugins:write` | `pods/exec:create` | +| `discoveryplugins:delete` | `pods/exec:create` | +| `eventtemplates:read` | `pods/exec:create` | +| `eventtemplates:write` | `pods/exec:create` | +| `eventtemplates:delete` | `pods/exec:create` | +| `eventtypes:read` | `pods/exec:create` | +| `heapdumps:read` | `pods/exec:create` | +| `heapdumps:write` | `pods/exec:create` | +| `heapdumps:delete` | `pods/exec:create` | +| `matchexpressions:read` | `pods/exec:create` | +| `probes:read` | `pods/exec:create` | +| `probes:write` | `pods/exec:create` | +| `probes:delete` | `pods/exec:create` | +| `probetemplates:read` | `pods/exec:create` | +| `probetemplates:write` | `pods/exec:create` | +| `probetemplates:delete` | `pods/exec:create` | +| `recordingmetadata:read` | `pods/exec:create` | +| `recordingmetadata:write` | `pods/exec:create` | +| `reports:read` | `pods/exec:create` | +| `reports:write` | `pods/exec:create` | +| `targets:read` | `pods/exec:create` | +| `targets:write` | `pods/exec:create` | +| `targets:delete` | `pods/exec:create` | +| `threaddumps:read` | `pods/exec:create` | +| `threaddumps:write` | `pods/exec:create` | +| `threaddumps:delete` | `pods/exec:create` | +| `unifiedlogs:read` | `pods/exec:create` | +| `unifiedlogs:write` | `pods/exec:create` | +| `unifiedlogs:delete` | `pods/exec:create` | + +##### Cluster-scoped RBAC + +By default, access reviews are scoped to the **Cryostat** installation namespace, so users only need a `Role` and `RoleBinding` in that namespace. If you prefer cluster-scoped authorization (requiring a `ClusterRole` and `ClusterRoleBinding`), set `spec.authorizationOptions.namespacedRBACPermissions` to `false`: + +```yaml +apiVersion: operator.cryostat.io/v1beta2 +kind: Cryostat +metadata: + name: cryostat-sample +spec: + authorizationOptions: + namespacedRBACPermissions: false +``` + ### Security Context From 36cd58950cc195b978ec9971236c8f94fd11a446 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 12 Aug 2026 14:29:24 -0400 Subject: [PATCH 2/7] adjustment --- .../configuring-the-helm-chart.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/_subsections/configuring-the-helm-chart.md b/docs/_subsections/configuring-the-helm-chart.md index bf90a834..87703605 100644 --- a/docs/_subsections/configuring-the-helm-chart.md +++ b/docs/_subsections/configuring-the-helm-chart.md @@ -10,9 +10,9 @@ When installed on OpenShift with authentication.openshift.enabled=trueauthentication.openshift.enabled=false but oauth2Proxy.tls.selfSigned.enabled=true then a self-signed **TLS** certificate will be generated at installation time to serve similar purposes. These **TLS** certificates are not managed, will not automatically rotate, and will expire after 365 days. You will need to manually rotate the certificates, or reinstall the chart, or else apply your own customizations to the **Kubernetes** manifests to automate **TLS** certificate issuance and rotation. -## Configuration Options +### Configuration Options -### Cryostat Container +#### Cryostat Container | Name | Description | Value | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | @@ -65,7 +65,7 @@ When installed with authentication.openshift.enabled=false but authentication.openshift.enabled=false but authentication.openshift.enabled=false but authentication.openshift.enabled=false but authentication.openshift.enabled=false but authentication.openshift.enabled=false but authentication.openshift.enabled=false but authentication.openshift.enabled=false but authentication.openshift.enabled=false but :` key (for example `activerecordings:read`) and is mapped to a **Kubernetes** resource/verb pair (for example `pods/exec:create`). The built-in default mapping for every permission is `pods/exec:create`. Admins can override individual permissions via `core.config.extra.envVars` by setting environment variables of the form `CRYOSTAT_SECURITY_RBAC_PERMISSIONS____` to a value of the form `resource[/subresource]:verb`. -**Cryostat** caches authorization decisions for up to one minute. Changes to user Roles or RoleBindings may take up to one minute to take effect. +**Cryostat** caches authorization decisions for one minute. Changes to user Roles or RoleBindings may take up to one minute to take effect. > **Note:** When Basic authentication (`authentication.basicAuth.enabled=true`) is also enabled, fine-grained RBAC checks are bypassed. Access becomes all-or-nothing for all authenticated users. -##### Granting read-only access +###### Granting read-only access To give some users read-only access and others full access, remap every `read` permission to `pods:get` (a lower privilege granted by the built-in **OpenShift** `view` role) while leaving mutating permissions at the default `pods/exec:create` (granted by `admin` or `edit`). @@ -312,7 +312,7 @@ A user who has neither role will be able to log in (they pass the `get pods` pro For a full list of available permission keys and their defaults, see the [Full permission reference](#full-permission-reference) table in the Operator configuration section. -### Other Parameters +#### Other Parameters | Name | Description | Value | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | @@ -328,4 +328,4 @@ For a full list of available permission keys and their defaults, see the [Full p | `networkPolicy.ingress.enabled` | whether a NetworkPolicy for restricting Cryostat component Pods' traffic is installed. This prevents other Pods from sending unwanted traffic to Cryostat's Pods. Traffic should flow via the Service (or Route, or other Ingress) only, not by directly targeting Pods | `true` | | `nodeSelector` | default Node Selector for the various Pods. Any Pod which does not have an individual nodeSelector setting will default to this. See: [NodeSelector](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling) | `{}` | | `tolerations` | default Tolerations for the various Pods. See: [Tolerations](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling) | `[]` | -| `affinity` | default Affinity for the various Pods. See: [Affinity](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling) | `{}` | \ No newline at end of file +| `affinity` | default Affinity for the various Pods. See: [Affinity](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling) | `{}` | From 04181f72e2aa5aeaf03749ccdf15e8a6cab77820 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 20 Aug 2026 10:44:56 -0400 Subject: [PATCH 3/7] document per-verb config and cache config --- docs/_subsections/configuring-the-operator.md | 103 ++++++++---------- 1 file changed, 48 insertions(+), 55 deletions(-) diff --git a/docs/_subsections/configuring-the-operator.md b/docs/_subsections/configuring-the-operator.md index 80cdf2a7..9803040c 100644 --- a/docs/_subsections/configuring-the-operator.md +++ b/docs/_subsections/configuring-the-operator.md @@ -397,8 +397,12 @@ When a user makes a request, **Cryostat** checks whether they are permitted to p check, the request proceeds; otherwise it is rejected with a `403 Forbidden` response. The built-in default mapping for every permission is `pods/exec:create`. This means that, out of the box, a user needs the (Cluster)Role which would grant them the ability to invoke shell commands -within Pods in the **Cryostat** installation namespace. Admins can override this default per-permission via `spec.authorizationOptions.rbacPermissions`, remapping individual permissions to -lower-privilege **Kubernetes** roles in order to extend read-only access to less-privileged users, or to create custom (Cluster)Roles and (Cluster)RoleBindings as needed. +within Pods in the **Cryostat** installation namespace. Admins can override this default; **Cryostat** resolves the effective mapping for each permission in order from most to least specific: + +1. An explicit entry in `spec.authorizationOptions.rbacPermissions` for that exact `:` key. +2. A verb-class fallback in `spec.authorizationOptions.rbacDefaultPermissions` (`defaultReadPermission`, `defaultWritePermission`, or `defaultDeletePermission`). +3. The global catch-all `spec.authorizationOptions.rbacDefaultPermissions.defaultPermission`. +4. The built-in application default: `pods/exec:create` for all permissions. **Cryostat** caches authorization decisions for one minute by default. Changes to user Roles or RoleBindings may take up to one minute to take effect. @@ -409,6 +413,21 @@ lower-privilege **Kubernetes** roles in order to extend read-only access to less The example below remaps every `read` permission to `pods:get`, while leaving all mutating operations mapped to the default `pods/exec:create`. A user who holds the built-in **OpenShift** `view` role in the **Cryostat** installation namespace passes `pods:get` checks and therefore gets read-only access. A user who holds `admin` or `edit` passes `pods/exec:create` checks and therefore gets full access. +The most concise way to achieve this is via `spec.authorizationOptions.rbacDefaultPermissions`: + +```yaml +apiVersion: operator.cryostat.io/v1beta2 +kind: Cryostat +metadata: + name: cryostat-sample +spec: + authorizationOptions: + rbacDefaultPermissions: + defaultReadPermission: pods:get +``` + +Alternatively, individual permissions can be remapped explicitly via `spec.authorizationOptions.rbacPermissions`: + ```yaml apiVersion: operator.cryostat.io/v1beta2 kind: Cryostat @@ -420,7 +439,9 @@ spec: activerecordings:read: pods:get archivedrecordings:read: pods:get asyncprofiler:read: pods:get + audit:read: pods:get automatedrules:read: pods:get + certificates:read: pods:get credentials:read: pods:get discoverynodes:read: pods:get discoveryplugins:read: pods:get @@ -450,59 +471,31 @@ With this configuration, apply **OpenShift** RBAC as follows: A user who has neither role may be able to log in (pass the `get pods` proxy access review), but all **Cryostat** API requests will be rejected with `403 Forbidden` until they are granted an appropriate role. -##### Full permission reference - -The following table lists every available `:` key and its default **Kubernetes** permission mapping. - -| Key | Default mapping | -|---|---| -| `activerecordings:read` | `pods/exec:create` | -| `activerecordings:write` | `pods/exec:create` | -| `activerecordings:delete` | `pods/exec:create` | -| `archivedrecordings:read` | `pods/exec:create` | -| `archivedrecordings:write` | `pods/exec:create` | -| `archivedrecordings:delete` | `pods/exec:create` | -| `asyncprofiler:read` | `pods/exec:create` | -| `asyncprofiler:write` | `pods/exec:create` | -| `asyncprofiler:delete` | `pods/exec:create` | -| `automatedrules:read` | `pods/exec:create` | -| `automatedrules:write` | `pods/exec:create` | -| `automatedrules:delete` | `pods/exec:create` | -| `credentials:read` | `pods/exec:create` | -| `credentials:write` | `pods/exec:create` | -| `credentials:delete` | `pods/exec:create` | -| `discoverynodes:read` | `pods/exec:create` | -| `discoverynodes:write` | `pods/exec:create` | -| `discoveryplugins:read` | `pods/exec:create` | -| `discoveryplugins:write` | `pods/exec:create` | -| `discoveryplugins:delete` | `pods/exec:create` | -| `eventtemplates:read` | `pods/exec:create` | -| `eventtemplates:write` | `pods/exec:create` | -| `eventtemplates:delete` | `pods/exec:create` | -| `eventtypes:read` | `pods/exec:create` | -| `heapdumps:read` | `pods/exec:create` | -| `heapdumps:write` | `pods/exec:create` | -| `heapdumps:delete` | `pods/exec:create` | -| `matchexpressions:read` | `pods/exec:create` | -| `probes:read` | `pods/exec:create` | -| `probes:write` | `pods/exec:create` | -| `probes:delete` | `pods/exec:create` | -| `probetemplates:read` | `pods/exec:create` | -| `probetemplates:write` | `pods/exec:create` | -| `probetemplates:delete` | `pods/exec:create` | -| `recordingmetadata:read` | `pods/exec:create` | -| `recordingmetadata:write` | `pods/exec:create` | -| `reports:read` | `pods/exec:create` | -| `reports:write` | `pods/exec:create` | -| `targets:read` | `pods/exec:create` | -| `targets:write` | `pods/exec:create` | -| `targets:delete` | `pods/exec:create` | -| `threaddumps:read` | `pods/exec:create` | -| `threaddumps:write` | `pods/exec:create` | -| `threaddumps:delete` | `pods/exec:create` | -| `unifiedlogs:read` | `pods/exec:create` | -| `unifiedlogs:write` | `pods/exec:create` | -| `unifiedlogs:delete` | `pods/exec:create` | +##### RBAC cache options + +**Cryostat** maintains two in-process caches in fine-grained RBAC mode to reduce the number of **Kubernetes** API calls: + +- **Per-user client cache**: holds a per-user **Kubernetes** client. Defaults to a 5-minute idle TTL and a maximum of 1000 entries. +- **Decision cache**: caches `SelfSubjectAccessReview` results. Defaults to a 1-minute write TTL and a maximum of 10000 entries. + +Both caches can be tuned via `spec.authorizationOptions.rbacCacheOptions`: + +```yaml +apiVersion: operator.cryostat.io/v1beta2 +kind: Cryostat +metadata: + name: cryostat-sample +spec: + authorizationOptions: + rbacCacheOptions: + clientCacheExpireAfterAccess: "5m" # idle TTL for the per-user client cache (Go duration) + clientCacheMaximumSize: 1000 # max entries; set to 0 to disable + decisionCacheTTL: "1m" # write TTL for SSAR decision cache (Go duration) + decisionCacheMaximumSize: 10000 # max entries; set to 0 to disable +``` + +Setting `decisionCacheTTL: "0s"` or `decisionCacheMaximumSize: 0` disables the decision cache entirely so every request issues a fresh `SelfSubjectAccessReview`. Similarly, setting +`clientCacheExpireAfterAccess: "0s"` or `clientCacheMaximumSize: 0` disables the client cache. ##### Cluster-scoped RBAC From d54544f5247f9b408fd8b9457e78b218e017771c Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 20 Aug 2026 14:11:19 -0400 Subject: [PATCH 4/7] add audit:read and certificates:read examples for helm --- docs/_subsections/configuring-the-helm-chart.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/_subsections/configuring-the-helm-chart.md b/docs/_subsections/configuring-the-helm-chart.md index 87703605..3b87d6ed 100644 --- a/docs/_subsections/configuring-the-helm-chart.md +++ b/docs/_subsections/configuring-the-helm-chart.md @@ -278,7 +278,9 @@ helm upgrade cryostat ./charts/cryostat -n \ {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__ACTIVERECORDINGS_READ_","value":"pods:get"}, {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__ARCHIVEDRECORDINGS_READ_","value":"pods:get"}, {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__ASYNCPROFILER_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__AUDIT_READ_","value":"pods:get"}, {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__AUTOMATEDRULES_READ_","value":"pods:get"}, + {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__CERTIFICATES_READ_","value":"pods:get"}, {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__CREDENTIALS_READ_","value":"pods:get"}, {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__DISCOVERYNODES_READ_","value":"pods:get"}, {"name":"CRYOSTAT_SECURITY_RBAC_PERMISSIONS__DISCOVERYPLUGINS_READ_","value":"pods:get"}, From f15b1eb57750ec7f3fc82cf6d239f3fc3325cb78 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 20 Aug 2026 14:13:12 -0400 Subject: [PATCH 5/7] RBAC happens within installation namespace by default --- docs/_subsections/configuring-the-operator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_subsections/configuring-the-operator.md b/docs/_subsections/configuring-the-operator.md index 9803040c..53f67f48 100644 --- a/docs/_subsections/configuring-the-operator.md +++ b/docs/_subsections/configuring-the-operator.md @@ -393,7 +393,7 @@ against the **OpenShift** cluster for every incoming API request, checking wheth operation being requested. This allows an admin to assign some users full access to **Cryostat** and others only read access, using standard **OpenShift** (Cluster)Role and (Cluster)RoleBinding objects. Each **Cryostat** API permission is expressed as a `:` key (for example `activerecordings:read`) and is mapped to a **Kubernetes** resource/verb pair (for example `pods/exec:create`). -When a user makes a request, **Cryostat** checks whether they are permitted to perform the mapped **Kubernetes** action, scoped to the **Cryostat** installation namespace. If the user passes the +When a user makes a request, **Cryostat** checks whether they are permitted to perform the mapped **Kubernetes** action, scoped to the **Cryostat** installation namespace by default. If the user passes the check, the request proceeds; otherwise it is rejected with a `403 Forbidden` response. The built-in default mapping for every permission is `pods/exec:create`. This means that, out of the box, a user needs the (Cluster)Role which would grant them the ability to invoke shell commands From ac2c81f056b0e5ef74128a4f8f3fae2d8b5115e9 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 20 Aug 2026 14:20:21 -0400 Subject: [PATCH 6/7] namespaced checks by default --- docs/_subsections/configuring-the-operator.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/_subsections/configuring-the-operator.md b/docs/_subsections/configuring-the-operator.md index 53f67f48..6046174a 100644 --- a/docs/_subsections/configuring-the-operator.md +++ b/docs/_subsections/configuring-the-operator.md @@ -499,7 +499,7 @@ Setting `decisionCacheTTL: "0s"` or `decisionCacheMaximumSize: 0` disables the d ##### Cluster-scoped RBAC -By default, access reviews are scoped to the **Cryostat** installation namespace, so users only need a `Role` and `RoleBinding` in that namespace. If you prefer cluster-scoped authorization (requiring a `ClusterRole` and `ClusterRoleBinding`), set `spec.authorizationOptions.namespacedRBACPermissions` to `false`: +By default, access reviews are scoped to the **Cryostat** installation namespace (i.e. the namespace where the **CR** is created), so users only need a `Role` and `RoleBinding` within that namespace to pass the various permissions checks. If you prefer cluster-scoped authorization (requiring a `ClusterRole` and `ClusterRoleBinding`), set `spec.authorizationOptions.namespacedRBACPermissions` to `false`: ```yaml apiVersion: operator.cryostat.io/v1beta2 @@ -511,6 +511,7 @@ spec: namespacedRBACPermissions: false ``` +Setting `namespacedRBACPermissions: false` will require users to be able to pass the various permissions checks across all namespaces across the cluster, rather than only within the **CR**'s namespace. The `namespacedRBACPermissions` property is unset by default, which is equivalent to setting it to `true`: permissions are checked within the installation namespace only. ### Security Context From 6d62701a71823c8e2486e31524675836046e9542 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 20 Aug 2026 14:32:06 -0400 Subject: [PATCH 7/7] clarify --- docs/_subsections/configuring-the-helm-chart.md | 6 +++--- docs/_subsections/configuring-the-operator.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/_subsections/configuring-the-helm-chart.md b/docs/_subsections/configuring-the-helm-chart.md index 3b87d6ed..a51e216e 100644 --- a/docs/_subsections/configuring-the-helm-chart.md +++ b/docs/_subsections/configuring-the-helm-chart.md @@ -257,7 +257,7 @@ When installed with authentication.openshift.enabled=false but :` key (for example `activerecordings:read`) and is mapped to a **Kubernetes** resource/verb pair (for example `pods/exec:create`). The built-in default mapping for every permission is `pods/exec:create`. Admins can override individual permissions via `core.config.extra.envVars` by setting environment variables of the form `CRYOSTAT_SECURITY_RBAC_PERMISSIONS____` to a value of the form `resource[/subresource]:verb`. @@ -269,7 +269,7 @@ Each **Cryostat** API permission is expressed as a `:` key (for To give some users read-only access and others full access, remap every `read` permission to `pods:get` (a lower privilege granted by the built-in **OpenShift** `view` role) while leaving mutating permissions at the default `pods/exec:create` (granted by `admin` or `edit`). -Pass the permission overrides via `core.config.extra.envVars` when installing or upgrading the chart. The example below shows `helm upgrade` syntax using `--set-json`: +Pass the permission overrides via `core.config.extra.envVars` when installing or upgrading the chart. The example below shows `helm upgrade` syntax using `--set-json`. This replaces the entire `core.config.extra.envVars` array, so preserve and include any existing entries or provide a complete values file instead: ```bash helm upgrade cryostat ./charts/cryostat -n \ @@ -309,7 +309,7 @@ With this configuration, apply **OpenShift** RBAC as follows: oc adm policy add-role-to-user -n admin ``` -A user who has neither role will be able to log in (they pass the `get pods` proxy access review), but all **Cryostat** API requests will be rejected with `403 Forbidden` until they are granted an appropriate role. +A user who passes the `get pods` proxy access review can reach the **Cryostat** application, but individual **Cryostat** API requests may still be rejected with `403 Forbidden` until they are granted an appropriate role. For a full list of available permission keys and their defaults, see the [Full permission reference](#full-permission-reference) table in the Operator configuration section. diff --git a/docs/_subsections/configuring-the-operator.md b/docs/_subsections/configuring-the-operator.md index 6046174a..862a7376 100644 --- a/docs/_subsections/configuring-the-operator.md +++ b/docs/_subsections/configuring-the-operator.md @@ -356,9 +356,9 @@ spec: ### Authorization Options On **OpenShift**, the authentication/authorization proxy deployed in front of the **Cryostat** application requires all users to pass a `get pods` access review in the **Cryostat** installation namespace -by default. This means that access to the **Cryostat** application is granted to exactly the set of **OpenShift** cluster user accounts and service accounts which have this Role. This can be configured +by default. This means that any **OpenShift** cluster user account or service account that passes the configured access review can reach the **Cryostat** application. This can be configured using `spec.authorizationOptions.openShiftSSO.accessReview` as depicted below, but note that the `namespace` field should always be included and in most cases should match the **Cryostat** installation namespace. -This Role only grants basic access to the Cryostat API and UI - see [below](#fine-grained-rbac-on-openshift) for further details on how Cryostat handles RBAC checks for specific actions. +Passing this review only grants basic access to the **Cryostat** API and UI entry points - see [below](#fine-grained-rbac-on-openshift) for further details on how **Cryostat** handles RBAC checks for specific actions. The auth proxy may also be configured to allow Basic authentication by creating a **Secret** containing an `htpasswd` user file. An `htpasswd` file granting access to a user named `user` with the password `pass` can be generated like this: `htpasswd -cbB htpasswd.conf user pass`. The password should use `bcrypt` hashing, specified by the `-B` flag. @@ -388,8 +388,8 @@ spec: #### Fine-Grained RBAC on OpenShift -When **OpenShift** SSO integration is enabled and Basic authentication is **not** enabled, **Cryostat** activates fine-grained RBAC mode. In this mode, **Cryostat** performs a `SelfSubjectAccessReview` -against the **OpenShift** cluster for every incoming API request, checking whether the authenticated user holds a sufficiently privileged **OpenShift** Role for the specific **Cryostat** resource and +When **OpenShift** SSO integration is enabled and Basic authentication is **not** enabled, **Cryostat** activates fine-grained RBAC mode. In this mode, **Cryostat** checks the authorization cache for every incoming API request and performs a new `SelfSubjectAccessReview` +against the **OpenShift** cluster only on a cache miss, checking whether the authenticated user holds a sufficiently privileged **OpenShift** Role for the specific **Cryostat** resource and operation being requested. This allows an admin to assign some users full access to **Cryostat** and others only read access, using standard **OpenShift** (Cluster)Role and (Cluster)RoleBinding objects. Each **Cryostat** API permission is expressed as a `:` key (for example `activerecordings:read`) and is mapped to a **Kubernetes** resource/verb pair (for example `pods/exec:create`).