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 for what has to be cluster-wide, a Role (pacto-operator-manager) in the release namespace for the managed components that run there, a second one of the same name in the node agent's namespace when the agent is on, and a Role (pacto-operator-leader-election) for leader election. The tables render the release namespace as pacto-operator-system.
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, list, watch |
not name-restricted |
"" (core) |
events |
create, patch |
not name-restricted |
"" (core) |
pods |
get, list, watch |
not name-restricted |
"" (core) |
secrets |
get, list, watch |
not name-restricted |
"" (core) |
services |
get, list, watch |
not name-restricted |
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 |
gateway.networking.k8s.io |
httproutes |
get, list, watch |
not name-restricted |
networking.k8s.io |
ingresses |
get, list, watch |
not name-restricted |
pacto.trianalab.io |
noderuntimeobservations |
delete, 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.
Always granted in the release namespace (pacto-operator-manager Role)¶
The teardown half of the managed components: get and delete on the fixed names a previous install may have created there, the node agent's included (releases before the dedicated agent namespace ran it in the release namespace).
| API groups | Resources | Verbs | Limited to |
|---|---|---|---|
"" (core) |
secrets |
delete, get |
pacto-dashboard-oci-creds |
"" (core) |
serviceaccounts |
delete, get |
pacto-node-agent |
"" (core) |
serviceaccounts, services |
delete, get |
pacto-dashboard |
"" (core) |
services |
delete, get |
pacto-evidence |
apps |
daemonsets |
delete, get |
pacto-node-agent |
apps |
deployments |
delete, get |
pacto-dashboard |
apps |
deployments |
delete, get |
pacto-evidence |
rbac.authorization.k8s.io |
roles, rolebindings |
delete, get |
pacto-node-agent |
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 |
|---|---|---|---|
rbac.authorization.k8s.io |
clusterroles, clusterrolebindings |
create, delete, get, list, patch, update, watch |
not name-restricted |
Their namespaced half is granted by the release namespace's pacto-operator-manager Role, so the operator can create these kinds there and nowhere else:
| API groups | Resources | Verbs | Limited to |
|---|---|---|---|
"" (core) |
secrets, serviceaccounts, services |
create, delete, get, patch, update |
not name-restricted |
apps |
deployments |
create, delete, get, patch, update |
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.
Additionally granted with the node agent (nodeAgent.enabled)¶
The agent runs in its own namespace (pacto-node-agent by default), which the chart creates with the privileged Pod Security level. Cluster-wide, the operator gains a node read (the orphan prune), a read and relabel of that one namespace by name, and a read of the agents' heartbeat Leases (also granted when controller.kernelEvidence.enabled is on alone):
| API groups | Resources | Verbs | Limited to |
|---|---|---|---|
"" (core) |
namespaces |
get, patch |
pacto-node-agent |
"" (core) |
nodes |
get |
not name-restricted |
coordination.k8s.io |
leases |
get, list, watch |
not name-restricted |
Everything it creates for the agent is granted by the pacto-operator-manager Role in the agent namespace. The agents' own Role (written by the operator) is get, create and patch on their observations and get, create, patch and update on their Leases, and Kubernetes lets the operator grant only what it holds, so it holds those verbs here:
| API groups | Resources | Verbs | Limited to |
|---|---|---|---|
"" (core) |
serviceaccounts |
create, delete, get, patch |
not name-restricted |
apps |
daemonsets |
create, delete, get, patch, update |
not name-restricted |
coordination.k8s.io |
leases |
create, delete, get, patch, update |
not name-restricted |
pacto.trianalab.io |
noderuntimeobservations |
create, delete, get, patch |
not name-restricted |
pacto.trianalab.io |
noderuntimeobservations/status |
create, get, patch |
not name-restricted |
rbac.authorization.k8s.io |
roles, rolebindings |
create, delete, get, patch |
not name-restricted |
Where the API serves ValidatingAdmissionPolicy, the chart also renders one that lets each agent write only the observation and Lease named after the node in its token, which RBAC cannot express.
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 grants the same rules itself when controller.enableMetricsObservation is on; apply the two objects yourself only for an operator installed without the chart. 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 |