Securing KEDA in a cluster shared by many teams: namespace isolation, ClusterTriggerAuthentication, RBAC for CRDs, per-namespace scaler restrictions, and OPA/Gatekeeper policies that govern KEDA usage.

In episode 13 we covered credential security: secret management, pod identity, and least privilege for queue or stream access. But KEDA itself is a cluster-wide component — it has CRDs (ScaledObject, ScaledJob, TriggerAuthentication) that anyone with cluster access can use. In this episode we discuss Multi-Tenancy & RBAC: how to make KEDA safe to share across many teams, from namespace isolation to admission policies that govern KEDA usage globally.
The first principle of multi-tenancy in Kubernetes: one team, one namespace. All of a team's workloads and KEDA configuration live in their own namespace. KEDA is actually a cluster-scoped operator — the operator and metrics server in the keda namespace manage ScaledObjects across the whole cluster. Isolation comes from RBAC, not from KEDA itself.
Inspect the KEDA components and their scope:
kubectl get pods -n keda
kubectl get deploy -n keda
kubectl api-resources | grep keda.shThe output of kubectl api-resources | grep keda.sh shows that ScaledObject, ScaledJob, and TriggerAuthentication are namespaced resources, while ClusterTriggerAuthentication is cluster-scoped. This means: a ScaledObject lives inside a single namespace and refers to a Deployment in the same namespace. That's the natural boundary — team A can't create a ScaledObject that autoscales team B's Deployment as long as RBAC limits ScaledObject creation to their own namespace.
Namespace isolation alone isn't enough. We need to control what configurations are allowed to be created. Three main areas:
| Area | Risk | Control |
|---|---|---|
| Scaler credentials | Reading another team's secrets | Per-namespace TriggerAuthentication |
| Max replicas | High maxReplicaCount = cluster resources exhausted | ResourceQuota + LimitRange + policy |
| Trigger type | Dangerous or disallowed scalers | OPA/Gatekeeper |
TriggerAuthentication is namespaced by default — it can be used by ScaledObjects in the same namespace. For credentials shared across namespaces (for example one SQS service account for the entire cluster), use ClusterTriggerAuthentication. It can be referenced from any namespace:
apiVersion: keda.sh/v1alpha1
kind: ClusterTriggerAuthentication
metadata:
name: cluster-sqs-auth
spec:
podIdentity:
provider: aws-eks
identityOwner: kedaA ScaledObject in any namespace can use it:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: sqs-consumer
namespace: orders
spec:
scaleTargetRef:
name: order-worker
maxReplicaCount: 20
triggers:
- type: aws-sqs-queue
clusterTriggerAuthenticationRef:
name: cluster-sqs-authNote the difference from a regular triggerAuthenticationRef: the cluster reference uses the cluster prefix. Because these credentials are shared, gate access through RBAC so only the platform team can create ClusterTriggerAuthentication — regular users can just use their team's namespaced TriggerAuthentication.
KEDA doesn't bundle RBAC roles for end users — we define them ourselves. A common pattern: give each team full rights over KEDA resources in their namespace, with no cross-namespace access.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: keda-user
namespace: orders
rules:
- apiGroups: ["keda.sh"]
resources: ["scaledobjects", "scaledjobs", "triggerauthentications"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]Bind that Role to the team's ServiceAccount in the same namespace:
kubectl create rolebinding keda-user --role keda-user \
--serviceaccount orders:team-orders -n ordersTip
Test RBAC with kubectl auth can-i: kubectl auth can-i create scaledobject -n orders --as system:serviceaccount:orders:team-orders. This command gives a quick answer before you roll out your pipeline.
KEDA installs admission webhooks — HTTPS servers that Kubernetes intercepts before KEDA resources are stored. There are two kinds: validating (rejects invalid configuration) and mutating (writes defaults into the configuration). Check the installed webhooks:
kubectl get validatingwebhookconfigurations | grep keda
kubectl get mutatingwebhookconfigurations | grep keda
kubectl logs -n keda deploy/keda-admission -fExamples of what gets validated: minReplicaCount greater than maxReplicaCount, pollingInterval below 10 seconds, or a cooldownPeriod less than 0. KEDA rejects such configuration up front, so the operator never receives a broken ScaledObject.
The mutating webhook fills in default values, for example pollingInterval: 30, cooldownPeriod: 300, and minReplicaCount: 1 when they're not specified. This is important for team peace of mind: a ScaledObject without explicit fields still behaves predictably.
KEDA's built-in validation checks configuration soundness. For business policy — "maxReplicaCount capped at 50", "kubernetes-api scaler types forbidden", "minReplicaCount must be 0 only in dev" — use a policy engine like OPA/Gatekeeper. Gatekeeper is an admission controller with Rego policies.
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: keda-maxreplica
spec:
crd:
spec:
names:
kind: KedaMaxReplica
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package keda
violation[{"msg": msg}] {
input.review.kind.kind == "ScaledObject"
spec := input.review.object.spec
spec.maxReplicaCount > input.parameters.maxReplica
msg := sprintf("maxReplicaCount %v melebihi batas %v", [
spec.maxReplicaCount, input.parameters.maxReplica])
}Apply the constraint per namespace, for example capping maxReplicaCount at 50 for all teams:
kubectl apply -f constrainttemplate.yaml
kubectl apply -f - <<EOF
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: KedaMaxReplica
metadata:
name: maxreplica-50
spec:
parameters:
maxReplica: 50
match:
kinds:
- apiGroups: ["keda.sh"]
kinds: ["ScaledObject"]
EOFBecause the ConstraintTemplate contains a rego block with template-like syntax, always keep the manifest in a fenced code block as above. Other common policies: forbidding non-platform users from creating ClusterTriggerAuthentication, requiring fallback.replicas for critical workloads, or rejecting minReplicaCount: 0 in the production namespace.
Warning
KEDA and Gatekeeper are both admission webhooks — execution order isn't guaranteed. Don't rely on KEDA's validating webhook for business policy, and don't rely on Gatekeeper for syntax validation. Two layers with different roles is the correct pattern.
TriggerAuthentication across namespaces via ConfigMap. ConfigMaps are namespaced; the correct approach is ClusterTriggerAuthentication or per-team credentials.ClusterRole to every team destroys isolation. Always use namespaced Roles.identityOwner: keda. In podIdentity, this value determines whether credentials are read from KEDA's own ServiceAccount (not the workload) — important for shared ClusterTriggerAuthentication.maxReplicaCount: 100 is still capped in CPU/memory by a ResourceQuota; without a quota, a single ScaledObject can drain a node.This episode makes KEDA a platform that's safe for many teams to share: namespace isolation as the logical boundary, the difference between TriggerAuthentication and ClusterTriggerAuthentication, RBAC for KEDA CRDs, the role of validating/mutating webhooks, and OPA/Gatekeeper policies that enforce business limits.
Points you should take away:
ClusterTriggerAuthentication for shared credentials across namespaces, with the right identityOwner.maxReplicaCount cap.kubectl auth can-i before rolling out a pipeline.The more teams that use KEDA, the bigger the costs that can be saved — but also the bigger the risk without governance. In the next episode, 15, we discuss Best Practice & Cost: FinOps with scale-to-zero for batch and AI inference, tuning activation to avoid thrashing, and reliability strategies like fallback replicas and scaler activation monitoring. See you in episode 15!