RBAC and Security Reference#
This page documents the exact Kubernetes permissions Reloader requires, the pod security context it runs with, and the options available for hardening the deployment.
All information on this page is derived from the Reloader Helm chart.
How RBAC is provisioned#
The Helm chart creates RBAC resources automatically. The type depends on reloader.watchGlobally:
watchGlobally |
RBAC resources created |
|---|---|
true (default) |
ClusterRole + ClusterRoleBinding |
false, reloader.namespaces unset |
Role + RoleBinding in the deployment namespace |
false, reloader.namespaces set |
Role + RoleBinding in each listed namespace, plus the deployment namespace |
In every case the chart also creates a small Role named <release>-metadata-role in the deployment namespace. See Metadata Role.
RBAC creation is controlled by reloader.rbac.enabled (default: true). To bring your own RBAC and skip chart-managed resources, set it to false — but you must then create the Role/ClusterRole manually using the rules below.
ClusterRole rules (default mode)#
These are the exact rules the chart creates when watchGlobally: true.
Core resources (read)#
- apiGroups: [""]
resources: ["secrets", "configmaps"]
verbs: ["list", "get", "watch"]
Reloader watches Secrets and ConfigMaps for data changes. These are read-only permissions.
- The
secretsrule is omitted whenreloader.ignoreSecrets: true. - The
configmapsrule is omitted whenreloader.ignoreConfigMaps: true.
Namespaces (read) — only when namespaceSelector is set#
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list", "watch"]
Required so Reloader can evaluate namespace labels when filtering with namespaceSelector. Not added to the ClusterRole when namespaceSelector is unset.
Workloads (patch)#
- apiGroups: ["apps"]
resources: ["deployments", "daemonsets", "statefulsets"]
verbs: ["list", "get", "update", "patch"]
Reloader patches the pod template of matching workloads to trigger a rolling restart.
CronJobs and Jobs#
- apiGroups: ["batch"]
resources: ["cronjobs"]
verbs: ["list", "get"]
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["create", "delete", "list", "get"]
list/get on CronJobs allows Reloader to find and evaluate CronJob workloads. The Jobs verbs support the CronJob restart mechanism: Reloader creates a Job from the CronJob spec and cleans it up afterwards.
- The
cronjobsrule is omitted whenreloader.ignoreCronJobs: true. - The
jobsrule is omitted whenreloader.ignoreJobs: true.
Secrets Store CSI — only when enableCSIIntegration: true#
- apiGroups: ["secrets-store.csi.x-k8s.io"]
resources: ["secretproviderclasspodstatuses", "secretproviderclasses"]
verbs: ["list", "get", "watch"]
Read-only access to Secrets Store CSI Driver resources, used to detect rotation of CSI-mounted secrets. Not added when enableCSIIntegration: false (the default).
Events#
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
Reloader emits Kubernetes Events when it triggers a restart. These appear in kubectl describe output for the workload.
Argo Rollouts — only when isArgoRollouts: true#
- apiGroups: ["argoproj.io"]
resources: ["rollouts"]
verbs: ["list", "get", "update", "patch"]
Only added when isArgoRollouts: true and the Argo Rollouts CRD (argoproj.io/v1alpha1) is detected in the cluster.
OpenShift DeploymentConfigs — only when isOpenshift: true#
- apiGroups: ["apps.openshift.io"]
resources: ["deploymentconfigs"]
verbs: ["list", "get", "update", "patch"]
Only added when isOpenshift: true and the OpenShift API (apps.openshift.io/v1) is detected.
Role rules (namespace-scoped mode)#
When watchGlobally: false, the chart creates namespace-scoped Role resources instead of a ClusterRole:
- Single namespace (default): with
reloader.namespacesunset, oneRoleis created in the deployment namespace — Reloader watches only the namespace it is deployed in. - Selected namespaces: with
reloader.namespacesset to a list, oneRoleis created per listed namespace, plus the deployment namespace — the chart always adds its own namespace to the watch set, and passes the combined, de-duplicated list to the--namespacesflag. Reloader watches those namespaces with no cluster-wide permissions.
The rules are identical to the ClusterRole above, except:
- The
namespacesrule is never added (not applicable to a Role). - The
cronjobsandjobsrules are always added, regardless ofreloader.ignoreCronJobsandreloader.ignoreJobs. Those flags omit the rules from theClusterRoleonly; the namespace-scopedRoleis not currently gated on them, so Reloader retainsjobs: create, deleteeven when Jobs are ignored.
Setting reloader.namespaces together with watchGlobally: true is invalid — the chart fails rendering with an explicit error.
Metadata Role (always created)#
In addition to the watch-scope RBAC above, the chart always creates a small Role named <release>-metadata-role in the deployment namespace (when rbac.enabled: true):
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["list", "get", "watch", "create", "update"]
This is the only write permission Reloader holds on ConfigMaps, and it is confined to Reloader's own namespace. It is used to publish a single fixed-name ConfigMap, reloader-meta-info, containing operational metadata (build/version info, active configuration, deployment info) — never application data, and never in watched namespaces.
Leader election — only when enableHA: true#
When HA mode is enabled, the metadata Role additionally receives Lease permissions for leader election:
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["create"]
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
resourceNames: ["stakater-reloader-lock"]
verbs: ["get", "update"]
get and update are restricted by resourceNames to the single lock Lease — replicas can read and renew only Reloader's own lock, not other Leases in the namespace.
Disabling RBAC creation#
If your organisation manages RBAC outside of Helm (for example via OPA/Gatekeeper, a centralised RBAC controller, or a GitOps pipeline), disable chart-managed RBAC:
reloader:
rbac:
enabled: false
You must then create the Role or ClusterRole manually with the rules listed above, and bind it to the ServiceAccount Reloader uses.
This also skips the <release>-metadata-role, so remember to recreate it. Without it Reloader cannot publish its reloader-meta-info ConfigMap, and in HA mode leader election has no Lease permissions.
ServiceAccount#
The chart creates a ServiceAccount in the deployment namespace by default.
reloader:
serviceAccount:
create: true
name: "" # auto-generated if empty
annotations: {} # use for IRSA, Workload Identity, etc.
To disable ServiceAccount creation and supply your own:
reloader:
serviceAccount:
create: false
name: my-existing-sa
To control whether the token is auto-mounted:
reloader:
deployment:
automountServiceAccountToken: false
Pod security context#
The chart sets the following pod-level security context by default:
securityContext:
runAsNonRoot: true
runAsUser: 65534
seccompProfile:
type: RuntimeDefault
runAsNonRoot: true— the container must not run as root; Kubernetes enforces this.runAsUser: 65534— thenobodyuser; no special privileges.seccompProfile: RuntimeDefault— uses the node's default seccomp profile, which restricts syscalls to the safe default set.
On OpenShift 4.13.3 and later, the platform assigns UIDs dynamically. Set runAsUser to null to allow this:
reloader:
deployment:
securityContext:
runAsUser: null
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
Container security context#
The container-level security context is empty by default. To harden it:
reloader:
deployment:
containerSecurityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
readOnlyRootFilesystem: true can also be set via the top-level shorthand:
reloader:
readOnlyRootFileSystem: true
When readOnlyRootFileSystem: true is set (either way), the chart automatically mounts an emptyDir volume at /tmp/ so Reloader can write temporary files.
Network policy#
The chart includes an optional NetworkPolicy that restricts ingress and egress for the Reloader pod. Disabled by default.
reloader:
netpol:
enabled: true
When enabled, the policy allows:
- Ingress on port
9090— for Prometheus metrics scraping - Egress on port
443— to reach the Kubernetes API server (https)
Caveat: the egress port is fixed at 443 and is not configurable. NetworkPolicy is evaluated against the API server endpoint port, which on many self-managed clusters (and kind) is 6443 — there the policy blocks Reloader's API access and the pod fails. Check your endpoint port with kubectl get endpoints kubernetes before enabling. Managed control planes (EKS, GKE, AKS) expose the endpoint on 443 and are unaffected.
You can further restrict the ingress source with netpol.from and the egress destination with netpol.to:
reloader:
netpol:
enabled: true
from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
to:
- ipBlock:
cidr: 10.96.0.1/32 # cluster API server IP
Verifying RBAC in a running cluster#
Check what ClusterRole or Role was created:
# Global mode
kubectl get clusterrole -l app=reloader-reloader
kubectl describe clusterrole reloader-reloader-role
# Namespace-scoped mode
kubectl get role -n reloader -l app=reloader-reloader
kubectl describe role reloader-reloader-role -n reloader
# Metadata Role, always created in the deployment namespace
kubectl describe role reloader-reloader-metadata-role -n reloader
Check the binding:
kubectl get clusterrolebinding -l app=reloader-reloader
kubectl describe clusterrolebinding reloader-reloader-role-binding
Confirm the ServiceAccount:
kubectl get serviceaccount -n reloader
If Reloader logs show failed to update deployment, check that its ServiceAccount has update and patch on apps/deployments:
kubectl auth can-i patch deployments \
--as=system:serviceaccount:reloader:reloader-reloader \
--all-namespaces