KubernetesLXV · Secrets SecuritySecrets security
Secrets RBAC — controlling who can read Secrets
What you'll learn
- Write a minimum-surface RBAC Role for Secret access
- Use `resourceNames` to scope access to specific Secrets
- Apply the production patterns (default-deny, per-Secret RBAC)
- Audit a cluster for over-broad Secret RBAC
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
Secrets RBAC is the API-server-level control for
Secret access. A workload with secrets: get can
read every Secret in the namespace (or in every
namespace, with a ClusterRoleBinding); a workload
without the verb cannot read any. The discipline is
to scope access to the minimum surface: specific
Secrets, specific verbs, specific subjects. This
lesson covers the patterns, the audit, and the
failure modes.
The secrets resource
secrets is a resource in the core API group
(""). The verbs are get, list, watch,
create, update, patch, delete. Read access
(get, list, watch) is the dangerous one; the
workload can read the Secret’s value.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: read-specific-secret
namespace: prod
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials"] # only this Secret
verbs: ["get"] # only read
This Role allows a SA to read one specific Secret. No list, no watch, no other Secret.
Default-deny
A defensible cluster has default-deny on Secrets: no Role or ClusterRole grants broad access; only specific Roles grant access to specific Secrets.
# Find broad Secrets RBAC
kubectl get roles,clusterroles -A -o json | \
jq '.items[] | select(.rules[]?.resources[]? == "secrets") |
{name: .metadata.name, rules: .rules[] | select(.resources[]? == "secrets")}'
A Role that grants secrets: [get, list, watch] without
resourceNames is a finding. A ClusterRoleBinding to a
ClusterRole with broad Secrets access is a Critical
finding.
Per-workload RBAC
Each workload has its own SA and its own Secret access:
# SA for the API workload
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-sa
namespace: prod
---
# Role for the API workload (read one Secret)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: api-secrets
namespace: prod
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials"]
verbs: ["get"]
---
# RoleBinding for the API workload
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: api-secrets
namespace: prod
subjects:
- kind: ServiceAccount
name: api-sa
namespace: prod
roleRef:
kind: Role
name: api-secrets
apiGroup: rbac.authorization.k8s.io
The api-sa SA can read the db-credentials Secret.
No other Secret.
Verifying with kubectl auth can-i
# Can the SA read the specific Secret?
kubectl auth can-i get secrets/db-credentials -n prod \
--as=system:serviceaccount:prod:api-sa
# yes
# Can the SA list Secrets?
kubectl auth can-i list secrets -n prod \
--as=system:serviceaccount:prod:api-sa
# no
# Can the SA read any Secret?
kubectl auth can-i get secrets -n prod \
--as=system:serviceaccount:prod:api-sa
# no (because resourceNames restricts to db-credentials)
The audit is the verification. The cluster’s RBAC is defensible if every SA’s effective Secret access is the minimum the SA needs.
Production patterns
- Per-workload SAs with per-Secret RBAC. Each workload has its own SA; each SA has access to only the Secrets it needs.
resourceNameson every Secrets Role. The Role allows access to specific Secrets, not all.- No
secrets: listfor workloads. The verb is denied for workloads that need onlyget. - Audit quarterly. A script walks every SA and verifies its effective Secret access.
Production failure modes
- A SA has
secrets: listandsecrets: get. The SA can enumerate and read every Secret in the namespace. The fix is to scope to specific Secrets. - A ClusterRoleBinding grants
secrets: getto a SA cluster-wide. The SA can read every Secret in every namespace. The fix is to remove the ClusterRoleBinding and bind to the specific namespace. - A developer has
secrets: getin production. A leaked developer kubeconfig has production Secret access. The fix is OIDC + IdP groups for production RBAC. - A controller has broad Secrets access. A
controller with
secrets: get, list, watchis over-broad.resourceNamescannot restrictlistorwatch, so the fix is a namespaced Role bound once in each namespace the controller manages.
Cross-course references
- The Observability course covers the audit log entries for Secret access.
- The Linux course covers the file permissions on mounted Secret data.
Quiz
Knowledge check · 4 questions
Q1. What is the role of `resourceNames` in a Secrets RBAC Role?
Q2. A SA with `secrets: list` can enumerate every Secret in the namespace; if it also has `secrets: get`, it can read every Secret.
Q3. Your team's monitoring stack has a ClusterRoleBinding that grants `secrets: get, list, watch` cluster-wide. The binding is to the Prometheus SA. An attacker compromises the Prometheus SA and reads every Secret in every namespace. Walk the response.
The ClusterRoleBinding is `monitoring-secrets` -> `prometheus-sa` -> ClusterRole `monitoring-all-secrets`. The ClusterRole grants `secrets: [get, list, watch]`. The Prometheus SA is bound cluster-wide. The attacker compromised the Prometheus SA via a known CVE in a sidecar.
Q4. What does 'default-deny' for Secrets RBAC mean, and how is it enforced?
Passing score: 75%. Answers are checked in this browser.
Production discipline
Secrets RBAC is the API-server-level control for
Secret access. A defensible RBAC programme uses
resourceNames to scope access to specific Secrets,
denies list for workloads, audits every SA’s
effective Secret access quarterly, and removes
broad secrets: get, list, watch bindings. The
audit is the verification: every SA’s effective
access is the minimum the SA needs. A cluster whose
Secrets RBAC is minimum-surface has a Secret
programme that is auditable.