Skip to content

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 secrets rule is omitted when reloader.ignoreSecrets: true.
  • The configmaps rule is omitted when reloader.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 cronjobs rule is omitted when reloader.ignoreCronJobs: true.
  • The jobs rule is omitted when reloader.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.namespaces unset, one Role is created in the deployment namespace — Reloader watches only the namespace it is deployed in.
  • Selected namespaces: with reloader.namespaces set to a list, one Role is 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 --namespaces flag. Reloader watches those namespaces with no cluster-wide permissions.

The rules are identical to the ClusterRole above, except:

  • The namespaces rule is never added (not applicable to a Role).
  • The cronjobs and jobs rules are always added, regardless of reloader.ignoreCronJobs and reloader.ignoreJobs. Those flags omit the rules from the ClusterRole only; the namespace-scoped Role is not currently gated on them, so Reloader retains jobs: create, delete even 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 — the nobody user; 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