permissions operator
The component is deployed as stp-controller as part of the Helm chart shturval-backend. There is no separate Helm chart just for the rights operator: along with it, the chart includes backend API, Envoy, OPA and Swagger UI.
Contents
1. Review
Purpose: the operator coordinates objects Permission, Scope, UserRole, GroupRole, Tenant with actual rights: aggregating and client ClusterRole, subject bindings through plugins (RBAC, Argo CD, Kubeflow, tenant), child areas for tenants and in-memory model for policies. HTTP endpoint /bundle (typical port 8082) sends data to OPA; when webhook is enabled, UserRole, GroupRole, Tenant are validated.
Features
| Contour | Intended use |
|---|---|
Areas (Scope) | Calculation of paths, cluster annotations, list of names Permission, statuses of bound objects |
Custom roles (Permission) | Aggregating ClusterRole on the platform and ClusterRole in client clusters by granted_cluster_rules (via kubeconfig from Secret on the platform) |
Bindings (UserRole, GroupRole) | Plugins enforce subject rights in scope; idempotency is reflected in plugin statuses |
Tenant (Tenant) | Create/update child Scope, platform Role for rights tenant-get / tenant-edit |
| Admission | Validation UserRole, GroupRole, Tenant with webhook enabled |
| Bundle HTTP | Issue revision OPA-bundle; periodic reassembly by interval (default 60 s) |
Initiation scripts
| What’s going on | Who initiates | Observed effect |
|---|---|---|
| CR matching | Control plane Kubernetes (CR, relist, watch events) | Update related ClusterRole, statuses, annotations, model for bundle |
| Binding Validation | API server on Update UserRole / GroupRole | Reject invalid changes to key spec fields |
| Tenant Validation | API server when Create/Update Tenant along the path webhook | Reject Invalid Changes |
| Reassembly bundle | Timer and HTTP consumer requests /bundle | Current revision in response |
| Response to clusters | CAPI events Cluster (control plane ready, delete, pause) | Renegotiation of affected Permission, Scope, roles |
Limitations and implications
| Limit | Consequence for the administrator |
|---|---|
Once UserRole is created, spec.username cannot be changed | The change is blocked by the validating webhook during Update |
Removing Permission with UserRole / GroupRole attached | The finalizer is not removed until released; retry at about 1 minute intervals |
Removing Tenant if there are child tenants | Delete negotiation fails before hierarchy is resolved |
Field open_search_roles in Permission | Marked deprecated in the CRD; new configurations are better not to rely on it |
Place in the chain of command
High level chaining: Tenant objects spawn/update the associated Scope; reconciliation Scope updates the storage model and statuses (including cluster annotations); UserRole / GroupRole require the existence of a corresponding Scope and set the owner’s link; Permission updates platform and client ClusterRole, changes to Permission via watch affect further negotiation of Scope.
Control chain: CR, operator, client clusters and bundle

The user or CI applies CR via the Kubernetes API. The API server stores the Tenant, Scope, Permission, UserRole and GroupRole objects, after which the permissions operator reads them, performs negotiations and accesses the external loops: RBAC plugins, Argo CD, Kubeflow, client clusters and CAPI Cluster objects.
Key Features
| Characteristics | Description |
|---|---|
| API group | permissions.shturval.tech, version v1beta1 |
| Scope CRD | Namespaced |
| kubectl shortNames | In the generated CRDs for the listed species not specified |
| Namespace of a typical installation (Kustomize for development) | stp-system, resource prefix stp- |
Namespace of the process when setting chart to shturval-backend | Same as the namespace of the Helm release; namespace and serviceAccountName from the Pod fields are sent to the container |
Default value namespace for StorageEngine in binary | shturval-backend (overridden by environment variable) |
| Leader election ID | permissions.shturval.tech |
| Metrics | Parameters are set by deployment patches and process flags (see §6) |
| Webhook | Port 9443; shutdown via ENABLE_WEBHOOKS=false |
| HTTP/2 | Disabled by default for TLS metrics and webhook servers |
| Bundle | Default binding address :8082, path /bundle |
Components and controllers
| Name/Role | Purpose |
|---|---|
| CRD | permissions, scopes, userroles, grouproles, tenants (cluster-wide definitions) |
| Deployment Manager | One Pod with container manager |
| RBAC | ClusterRole/bindings for manager access to API |
PermissionReconciler | Sync Permission with ClusterRole, finalizer, watch Cluster |
ScopeReconciler | Area model, statuses, annotations; periodic requeue for cluster/namespace levels |
UserRoleReconciler / GroupRoleReconciler | Area Rights Plugins |
TenantReconciler | Scope and tenant roles; deletion restrictions |
2. Architecture
Workload Components
| Component | Kind | Scope | Description |
|---|---|---|---|
| CRD | CustomResourceDefinition | Cluster | API Group Resources permissions.shturval.tech |
| Manager | Deployment | Namespaced | One Pod with container manager |
| RBAC | ClusterRole, RoleBinding, etc. | Per manifests | Operator access to required API resources |
Runtime architecture: Deployment manager, RBAC, webhook, bundle
Illustration: Runtime architecture: Deployment manager, RBAC, webhook, bundle

Graph of connections between CR and the rights storage engine

The diagram describes how the CRs are related to each other and to the in-memory StorageEngine. Tenant owns the associated Scope and creates the roles tenant-get / tenant-edit; UserRole and GroupRole refer to Scope and the names Permission; Permission populates the rights model and generates ClusterRole on the platform and in client clusters.
Communication with external objects
| Object/system | Role |
|---|---|
CAPI Cluster | Defining Client Clusters and Ready/Delete Events |
| Secret kubeconfig | Name <cluster-name>-kubeconfig, key value - expected format for accessing the client clusters API |
| OPA Consumers | HTTP GET (and associated bundle update wait logic) to /bundle |
| Integration plugins | Argo CD, Kubeflow, RBAC Kubernetes, tenant - by type of rights in spec |
RBAC is detailed by delivery manifests; The list of exact rules is not duplicated in this document.
3. Installation / deployment
Platform and Helm
The component is included in chart shturval-backend: Deployment stp-controller, the service for the bundle and related objects are specified by chart templates. There is no separate Helm chart only for permissions-operator ** - a common chart backend is used.
A typical installation on a management cluster is performed through cluster-manager: ShturvalServiceConfig (SSC) objects are created and updated with the chart name shturval-backend, namespace shturval-backend, dependencies on shturval-backend-crds and shturval-ingress-controller, chart version from the platform release parameters and user values.
Local installation of CRD/RBAC via Kustomize for development uses namespace stp-system; in operation through the platform, the typical release namespace is shturval-backend - this must be consistent with the process environment variable namespace.
Dependencies for client rules
For Permission with granted_cluster_rules (policies in client clusters), kubeconfig Secrets must exist on the platform according to the name and key agreement (see §2). Separate SSH keys to nodes in the described getter chain are not used.
Configuration via SSP
Any Helm values.yaml changes to an operator component in production must be submitted via ShturvalServicePatch (SSP) on top of the corresponding SSC, without directly editing the release outside of platform practices.
If you need to fine-tune container arguments and resources, check the actual chart and the platform release documentation for the version you are using.
4. Diagnostics
Standard checks (namespace nleave the actual one for release, usually shturval-backend):
- state of Pods Deployment
stp-controller; - Pod events and container logs
managerfor negotiation or webhook errors; - availability of a health sample at the address specified by the arguments (typically
:8081); - for OPA policies - availability of the bundle service (port 8082 in the standard flag configuration).
Readiness and statuses (Ready, plugins)
| Resource | Field/structure | Interpretation |
|---|---|---|
Permission | status.ready | Summary status of application of rules |
Scope | status.ready, status.objectsStatus | Readiness of the area and condition of objects |
UserRole, GroupRole | status (RoleStatus) | Map permissions → clusters/platform → plugins → PluginObject |
Tenant | status.condition | See terms table below |
Plugin statuses (PluginApplyStatus)
| Value | Operational impact |
|---|---|
applied | Plugin successfully applied |
not applied | Not yet applied |
not required | Not required (for example, when deleting) |
undefined | Network or other error; records can be cleared on successful pass |
Tenant Conditions (status.condition)
| Condition | True | False / reasons |
|---|---|---|
TenantSynced | — | Error creating/patch Scope, receiving Scope (CreateOrPatchTenantFailed, GetScopeFailed) |
TenantCreated | Tenant and scope are synchronized (Created) | — |
TenantRoleCreated | — | Error creating Role tenant-get / tenant-edit |
TenantDeleted | — | Child tenants or error deleting Scope/Role/parent scope |
Typical symptoms
| Symptom | Possible reason | Action |
|---|---|---|
Permission stuck in deletion | There are UserRole/GroupRole referencing the name permission | Remove or change bindings; wait for re-negotiation (~1 min) |
| No rights in the client cluster | No/broken kubeconfig Secret, pause/remove CAPI Cluster | Check Secret <cluster>-kubeconfig, key value, status Cluster |
| Webhook not responding | No certificates / incorrect configuration when ENABLE_WEBHOOKS is enabled | Check TLS and webhook configuration; during emergency diagnostics - webhook disable mode (see §6) |
| Long update of cluster/namespace level scope | Scheduled intervalsre-entry ala reconcile | Consider frequency (up to ~30 min for cluster, ~5 min for namespace in the described logic) |
Error deleting Tenant | Child tenants | First delete/move child objects |
5. Life cycle
General circuit interaction diagram:
| Step | Actor | Action |
|---|---|---|
| 1 | Negotiation Tenant | Creates or updates Scope with the same name, owner reference to Tenant |
| 2 | Negotiation Scope | The storage model and statuses are updated; cluster annotations |
| 3 | Negotiation UserRole / GroupRole | Requires existing Scope; controller reference is set |
| 4 | Negotiation Permission | Cluster roles are updated; changes to Permission trigger further negotiation of Scope via watch |
Simplified end-to-end loop: Kubernetes API and resource negotiation
Illustration: Simplified end-to-end loop: Kubernetes API and resource negotiation

Permission
- Finalizer:
permissions.shturval.tech/finalizer. - Create/Update: clear
status.applied_to_clusters, write to model; for platform rules - updating the aggregatingClusterRole; for client - selecting clusters and updatingClusterRolein each; settingReadyon success. - Removal: if there are role bindings to this permission - reschedule the retry in about a minute; otherwise, removing platform and client
ClusterRole, removing the finalizer. - CAPI events: when the control plane is ready or a cluster is deleted - renegotiation of the affected
Permission(with a filter for applied clusters).
Scope
- Delete: if the model still has users/groups for the name scope - repeat after ~5 s; otherwise, removal from the model and removal of the finalizer.
- Update: recalculation of clusters, annotations, namespace statuses; for the namespace level - checking the existence of namespace in target clusters; periodic repetitions for cluster levels (up to ~30 min) and namespace (~5 min) atnecessary.
UserRole and GroupRole
- Delete: resetting plugin statuses, calling deletion for all plugins, calculating readiness, removing the finalizer and deleting from the model.
- Update: finalizer, binding to
Scope, model update, sequential application of plugins; in case of errors - immediate requeue; if successful, clear “undefined” plugin statuses. - Observation: changes to associated
Scope; forUserRole- also a change toCluster.Spec.Paused.
Tenant
- Creation / update: when adding a finalizer for the first time - immediate re-entry; generation and application of
Scope, creation of rolestenant-get/tenant-edit, registration in the model, conditionTenantCreated. - Removal: checking for the absence of child tenants; delete
Scope; whileScopeexists - repeat after ~5 s; then deleting roles and records in the model, clearing the parent scope, removing the finalizer.
Name prefixes for created ClusterRole: shturval: for user roles, platform: for platform ones.
Transition diagrams during coordination
Below are diagrams aimed at understanding the states of an object in a cluster: readiness, deletion and finalizers.
Negotiate Permission: create/update ClusterRole, delete

If there is no Permission object, reconcile ends with an empty result. For an existing object, the operator separates the deletion path and the update path: when deleting, waits until UserRole and GroupRole no longer refer to permission, then deletes the platform and client ClusterRole, the engine entry and the finalizer; when updating, it adds a finalizer, updates the model, synchronizes the necessary ClusterRole and sets successful readiness.
UserRole negotiation: binding to Scope, plugins, readiness

When updating UserRole, the operator adds a finalizer, gets Scope, sets the controller reference, updates the entry in the engine, and applies permissions through plugins. Then the readiness is recalculated: if the application is incomplete, the object is put into reprocessing, and if it is successful, the undefined results are cleared and the reconcile is completed without restarting; when removing plugins callare sent for cleaning, readiness for removal is recalculated and the finalizer is removed.
GroupRole negotiation: symmetrical to UserRole

The flow for GroupRole repeats the logic of UserRole, but the subject is the claim group. The operator adds the finalizer, reads the associated Scope, sets the owner reference, updates the model and applies permissions via plugins; when uninstalling, it removes plugin rights, checks the readiness of cleaning, and only after that removes the finalizer.
Scope negotiation: clusters, namespace, retries

For active Scope, the operator adds a finalizer, filters clusters by identifiers and annotations, updates paths in StorageEngine, writes permissions, paths and objectsStatus to the status, and then sets it to ready. If the platform or cluster/namespace level scope requires rechecking, reconcile is rescheduled at cluster and namespace intervals; when deleting, the operator waits until there are no users or groups left in the engine, then removes the scope from the model and removes the finalizer.
Tenant negotiation: child Scope, roles tenant-get/tenant-edit

When creating or updating Tenant, the operator adds a finalizer, generates and applies the associated Scope, creates the roles tenant-get and tenant-edit, adds a tenant to the engine, and sets the TenantCreated=True condition. When deleting, the absence of child tenants is first checked; then the operator removes Scope, waits for it to disappear, removes the roles, removes the tenant from the model, clears the reference in the parent scope and removes the finalizer.
6. Operator parameters
Process arguments are specified by flags at the entry point. In delivery, they are transmitted through the Deployment chart template and SSP; the administrator can override them only through mechanisms consistent with the platform, without improvising names:
| Flag | Default | Purpose |
|---|---|---|
--metrics-bind-address | 0 (disabled in code; patch may have 127.0.0.1:8080) | Metrics address |
--health-probe-bind-address | :8081 | Health test |
--leader-elect | false | Leader-election (in a typical deployment, aarguments) |
--metrics-secure | true | TLS for metrics |
--enable-http2 | false | HTTP/2 |
--bundle-bind-address | :8082 | Listening bundle |
--bundle-path | /bundle | HTTP path bundle |
--bundle-rebuild-duration | 60 (seconds) | Reassembly period bundle |
--max-reconciles | 5 | Concurrency of approvals |
--webhook-port | 9443 | webhook port |
--webhook-cert-dir | /tmp/k8s-webhook-server/serving-certs/ | webhook certificate directory |
zap logger options are also passed through the standard flags mechanism.
Environment Variables
| Variable | Purpose |
|---|---|
ENABLE_WEBHOOKS | Setting false disables webhook logging (useful for debugging without matched certificates) |
namespace | Model storage area namespace (override default shturval-backend) |
serviceAccountName | Account for tenant webhook (default stp-tenant-webhook) |
oidcClientRef | Link to auth client (default shturval-backend/backend) |
DEBUG | With true — development log mode |
Note: with ENABLE_WEBHOOKS=false admission validation for the listed types is not performed on the operator side; Use only in controlled diagnostic scenarios.
7. CRD/API reference
General information: group permissions.shturval.tech, version v1beta1, scope of all CRs - Namespaced.
Permission
spec
| Path | Type | Required | Description |
|---|---|---|---|
scope_level | string (ScopeLevel) | Yes | Scope level to define Permission |
granted_cluster_rules | []PolicyRule | No | RBAC rules for ClusterRole in client clusters |
granted_platform_permissions | []string | No | Platform permission names for aggregation ClusterRole |
argo_cd_roles | []string | No | Role of Argo CD |
kubeflow_roles | []string | No | Kubeflow access role |
open_search_roles | []string | No | Deprecated |
status
| Path | Type | Description |
|---|---|---|
applied_to_clusters | []string | Clusters where client ClusterRole is set |
ready | Ready | Summary status (ready, status, reason, message) |
There are no separate conditions like metav1.Condition ** - only the ready field.
Scope
spec
| Path | Required | Description |
|---|---|---|
spec.objects | Yes | Level map → names (ScopeObjects): platform, clustergroups, clusters, namespaces, tenants, custom |
status
| Path | Description |
|---|---|
paths | Paths scope |
permissions | Names Permission available at scope level |
ready | Ready |
objectsStatus | Status of clusters and namespace |
UserRole
spec
| Path | Required |
|---|---|
spec.scope | Yes |
status: RoleStatus - ready, nested map permissions → clusters/platform → plugins → PluginObject.
GroupRole
spec
| Path | Required |
|---|---|
spec.scope | Yes |
status: same as UserRole.
Tenant
spec
| Path | Description |
|---|---|
spec.objects | ScopeObjects tenant boundaries |
spec.parentRef | Parent tenant (name, namespace) |
spec.immutable | Prohibition of spec changes |
status
| Path | Description |
|---|---|
objectsStatus | Copy from linked Scope on successful reconciliation |
condition | One metav1.Condition (see §4) |