RBAC¶
Every table below is rendered from the Helm chart itself, so it is the permission set an install actually creates. The chart creates one cluster-scoped ClusterRole (pacto-operator-manager) bound to the controller's ServiceAccount, and one namespaced Role (pacto-operator-leader-election) for leader election.
Note
The repository also contains config/rbac/role.yaml, a kubebuilder-generated manager-role. It is a different object with a different name and is not what helm install creates. It belongs to the config/ kustomize scaffolding, which is not published with a release and is deployed by no test or CI job -- Helm is the only supported install path.
Always granted (pacto-operator-manager)¶
Present in every install, including one with every managed component disabled. Workloads and their wiring are read-only; the writes are on Pacto's own resources, on events, and on the specific named objects a previous install may have created (so the operator can clean them up after you disable a component).
| API groups | Resources | Verbs | Limited to |
|---|---|---|---|
"" (core) |
configmaps |
get |
not name-restricted |
"" (core) |
events |
create, patch |
not name-restricted |
"" (core) |
secrets |
get, list, watch |
not name-restricted |
"" (core) |
secrets |
delete, get |
pacto-dashboard-oci-creds |
"" (core) |
serviceaccounts, services |
delete, get |
pacto-dashboard |
"" (core) |
services |
get, list, watch |
not name-restricted |
"" (core) |
services |
delete, get |
pacto-evidence |
apps |
deployments |
delete, get |
pacto-dashboard |
apps |
deployments |
delete, get |
pacto-evidence |
apps |
deployments, replicasets, statefulsets |
get, list, watch |
not name-restricted |
batch |
cronjobs, jobs |
get, list, watch |
not name-restricted |
discovery.k8s.io |
endpointslices |
get, list, watch |
not name-restricted |
pacto.trianalab.io |
pactorevisions |
create, get, list, watch |
not name-restricted |
pacto.trianalab.io |
pactorevisions/status |
get, patch, update |
not name-restricted |
pacto.trianalab.io |
pactos |
create, delete, get, list, patch, update, watch |
not name-restricted |
pacto.trianalab.io |
pactos/finalizers |
update |
not name-restricted |
pacto.trianalab.io |
pactos/status |
get, patch, update |
not name-restricted |
rbac.authorization.k8s.io |
clusterroles, clusterrolebindings |
delete, get |
pacto-dashboard |
Why the operator can read Secrets in every namespace
spec.contractRef.pullSecretRef names a Secret in the Pacto's own namespace, and a Pacto can be created in any namespace, so the read cannot be scoped to one. get resolves those registry credentials when a contract is pulled; list and watch back the Secret informer that re-reconciles a Pacto when its pull Secret changes.
controller.watchNamespace does not narrow this. It restricts what the controller reconciles; the ClusterRole is created unconditionally and grants the same cluster-wide read either way.
What does limit the blast radius is binding each credential to its host: give an Opaque pull Secret a registry key and the operator refuses to send it anywhere else, so a contract cannot redirect pull traffic to an attacker-controlled registry to exfiltrate the token. Beyond that, treat cluster-wide Secret read as the cost of the operator and install it on a cluster where that is acceptable.
Additionally granted when a managed component is enabled¶
When a managed component is on, the operator creates and reconciles that component's Deployment, Service, ServiceAccount and RBAC for you, and the chart widens the ClusterRole accordingly. At chart defaults dashboard.enabled is on and evidence.enabled is off, so every rule below is what a default install adds for the dashboard. Rendering the chart with --set dashboard.enabled=false --set evidence.enabled=false removes every rule in this table.
| API groups | Resources | Verbs | Limited to |
|---|---|---|---|
"" (core) |
namespaces |
create, get, list, watch |
not name-restricted |
"" (core) |
secrets |
create, delete, patch, update |
not name-restricted |
"" (core) |
serviceaccounts, services |
create, delete, get, list, patch, update, watch |
not name-restricted |
apps |
deployments |
create, delete, patch, update |
not name-restricted |
rbac.authorization.k8s.io |
clusterroles, clusterrolebindings |
create, delete, get, list, patch, update, watch |
not name-restricted |
This grant allows privilege escalation
The rules above include unrestricted create on clusterroles and clusterrolebindings. A subject that can create a ClusterRoleBinding can grant itself any permission in the cluster, so at chart defaults the operator is effectively cluster-admin-capable, not read-only. This is what lets it create the managed components' RBAC.
If your threat model does not allow that, install with --set dashboard.enabled=false --set evidence.enabled=false and deploy those components yourself. The operator then keeps only the Always granted table plus narrow get/delete on the specific objects a previous install may have left behind.
Namespaced Role (pacto-operator-leader-election)¶
Created in the release namespace and bound to the same ServiceAccount. Used only for the controller-runtime leader election lease.
| API groups | Resources | Verbs | Limited to |
|---|---|---|---|
"" (core) |
configmaps |
create, delete, get, list, patch, update, watch |
not name-restricted |
"" (core) |
events |
create, patch |
not name-restricted |
coordination.k8s.io |
leases |
create, delete, get, list, patch, update, watch |
not name-restricted |
Optional: metrics-observation ClusterRole¶
Needed alongside the base role when --enable-metrics-observation is set. It is a separate ClusterRole (metrics-observation-role), never a patch of the base role, so the base grants are untouched. The Helm chart does not package it, and the chart cannot set the flag that needs it -- apply the two objects yourself. Opt-in features has the YAML and the caveats.
| API groups | Resources | Verbs | Limited to |
|---|---|---|---|
monitoring.coreos.com |
servicemonitors, podmonitors |
get, list, watch |
not name-restricted |