Kubernetes Minimize Access To Secrets
More Info:
The Kubernetes API stores secrets, which may be service account tokens for the Kubernetes API or credentials used by workloads in the cluster. Access to these secrets should be restricted to the smallest possible group of users to reduce the risk of privilege escalation.
Risk Level
High
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify which subjects currently have broad Secret access
- Run on any machine with kubectl access:
kubectl get clusterrole,role -A -o yaml | grep -nA5 -B3 "resources:.*secrets"kubectl get clusterrolebinding,rolebinding -A -o yaml | grep -nA8 -B3 "roleRef:"
- Run on any machine with kubectl access:
-
List the ClusterRoles/Roles that grant
get,list, orwatchon Secrets- Run:
kubectl get clusterrole,role -A -o json \| jq -r '.items[]| select(.rules != null)| select([.rules[]| select(.resources != null)| select(.resources|index("secrets"))| select(.verbs|map(.=="get" or .=="list" or .=="watch")|any)]|length>0)| (.kind + "/" + .metadata.name + " (ns: " + (.metadata.namespace // "cluster-scope") + ")")'
- Run:
-
Review and edit roles to remove or narrow Secret access
- For each non-system role you decide should not have broad Secret access, edit it:
or for a namespace-scoped role:kubectl edit clusterrole <clusterrole-name>kubectl edit role <role-name> -n <namespace>
- In the opened YAML, locate
rules:entries withresources: ["secrets"](or includingsecrets) and:- Remove the
get,list, andwatchverbs fromverbs:, or - Remove the entire rule if no longer needed.
- Remove the
- Save and exit to apply.
- For each non-system role you decide should not have broad Secret access, edit it:
-
Adjust bindings so only intended subjects keep Secret access
- For roles/clusterroles where some Secret access is still required but only for a limited group, edit bindings:
orkubectl edit clusterrolebinding <binding-name>kubectl edit rolebinding <binding-name> -n <namespace>
- Under
subjects:, remove users/groups/serviceaccounts that should not be able to access Secrets. - Save and exit.
- For roles/clusterroles where some Secret access is still required but only for a limited group, edit bindings:
-
Re-test access for specific identities before global verification
- For high‑risk groups (for example
system:authenticatedor a CI user), explicitly test:kubectl auth can-i get,list,watch secrets --all-namespaces --as=<user-or-group> - Confirm it returns
nofor identities that should not have cluster‑wide Secret read access.
- For high‑risk groups (for example
-
Verification (derived from the audit command)
- Run on any machine with kubectl access:
echo "canGetListWatchSecretsAsSystemAuthenticated: $(kubectl auth can-i get,list,watch secrets --all-namespaces --as=system:authenticated)"
- Confirm the output shows:
canGetListWatchSecretsAsSystemAuthenticated: no
- Run on any machine with kubectl access:
Using kubectl
# 1) Identify which ClusterRoles currently allow get/list/watch on secrets
# Run on: any machine with kubectl access
kubectl get clusterrole -o json \
| jq -r '
.items[]
| select(
.rules[]
| select(
(.resources // []) | index("secrets")
and ((.verbs // []) | (.[] | IN("get","list","watch")))
)
)
| .metadata.name
' | sort -u
# 2) Inspect each identified ClusterRole to decide if it truly needs secret read access
# Replace <clusterrole-name> with each name from the previous command.
kubectl get clusterrole <clusterrole-name> -o yaml
# 3) Edit ClusterRoles to remove get/list/watch on secrets where not required
# Example: remove only the secret-related verbs, keep others.
# Run once per ClusterRole you’ve decided should not read secrets.
# This opens your editor; adjust the 'rules' section to drop get/list/watch from
# any entry where 'resources: ["secrets"]' (or includes "secrets").
kubectl edit clusterrole <clusterrole-name>
# 4) For fine-grained control, split secret access into a dedicated ClusterRole (optional)
# Run on: any machine with kubectl access
# Create a narrow-scope ClusterRole that allows only what is actually needed;
# adjust namespaces and subjects before applying.
cat << 'EOF' > readonly-secrets-narrow-clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: readonly-secrets-narrow
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"] # Remove "list","watch" unless absolutely necessary
EOF
kubectl apply -f readonly-secrets-narrow-clusterrole.yaml
# Example: bind this narrowly to a specific service account instead of broad groups
cat << 'EOF' > readonly-secrets-narrow-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: readonly-secrets-narrow-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: readonly-secrets-narrow
subjects:
- kind: ServiceAccount
name: my-workload-sa
namespace: default
EOF
kubectl apply -f readonly-secrets-narrow-binding.yaml
# 5) Verification: confirm system:authenticated can no longer get/list/watch secrets cluster-wide
# Run on: any machine with kubectl access
kubectl auth can-i get,list,watch secrets --all-namespaces --as=system:authenticated
Automation
#!/usr/bin/env bash
#
# Minimize access to Secrets for system:authenticated
#
# Scope: any machine with kubectl access and current-context pointing to the target cluster.
# Requirements: kubectl, cluster-admin (or equivalent) privileges.
set -euo pipefail
echo "=== Checking current access for system:authenticated to secrets ==="
CURRENT_ACCESS=$(kubectl auth can-i get,list,watch secrets --all-namespaces --as=system:authenticated || echo "error")
echo "canGetListWatchSecretsAsSystemAuthenticated: ${CURRENT_ACCESS}"
echo "=== Enumerating RBAC bindings granting get/list/watch on secrets to system:authenticated ==="
# Temporary files
TMP_CLUSTERROLES=$(mktemp)
TMP_ROLES=$(mktemp)
trap 'rm -f "$TMP_CLUSTERROLES" "$TMP_ROLES"' EXIT
# 1) Find ClusterRoles that grant get/list/watch on secrets
kubectl get clusterroles -o json > "${TMP_CLUSTERROLES}"
echo "- ClusterRoles with get/list/watch on secrets:"
jq -r '
.items[]
| select(
(.rules // [])
| map(
(.resources // [] | index("secrets")) and
(.verbs // [] | (index("get") or index("list") or index("watch")))
)
| any
)
| .metadata.name
' "${TMP_CLUSTERROLES}" | sort -u || true
CLUSTERROLES_WITH_SECRET_ACCESS=$(jq -r '
.items[]
| select(
(.rules // [])
| map(
(.resources // [] | index("secrets")) and
(.verbs // [] | (index("get") or index("list") or index("watch")))
)
| any
)
| .metadata.name
' "${TMP_CLUSTERROLES}" | sort -u)
# 2) Find Roles that grant get/list/watch on secrets
kubectl get roles --all-namespaces -o json > "${TMP_ROLES}"
echo "- Namespaced Roles with get/list/watch on secrets:"
jq -r '
.items[]
| select(
(.rules // [])
| map(
(.resources // [] | index("secrets")) and
(.verbs // [] | (index("get") or index("list") or index("watch")))
)
| any
)
| (.metadata.namespace + "/" + .metadata.name)
' "${TMP_ROLES}" | sort -u || true
ROLES_WITH_SECRET_ACCESS=$(jq -r '
.items[]
| select(
(.rules // [])
| map(
(.resources // [] | index("secrets")) and
(.verbs // [] | (index("get") or index("list") or index("watch")))
)
| any
)
| (.metadata.namespace + "/" + .metadata.name)
' "${TMP_ROLES}" | sort -u)
echo "=== Enumerating bindings that attach those roles to system:authenticated ==="
echo "- ClusterRoleBindings to system:authenticated that use affected ClusterRoles:"
kubectl get clusterrolebindings -o json \
| jq -r --argjson crn "[\"$(echo "${CLUSTERROLES_WITH_SECRET_ACCESS}" | paste -sd '","' -)\"]" '
.items[]
| select(
(.roleRef.kind == "ClusterRole")
and ((.roleRef.name) as $rname | ($crn | index($rname)))
and (
(.subjects // [])
| map(.kind == "Group" and .name == "system:authenticated")
| any
)
)
| .metadata.name
' | sort -u || true
CLUSTERROLEBINDINGS_TO_SYSTEM_AUTH=$(kubectl get clusterrolebindings -o json \
| jq -r --argjson crn "[\"$(echo "${CLUSTERROLES_WITH_SECRET_ACCESS}" | paste -sd '","' -)\"]" '
.items[]
| select(
(.roleRef.kind == "ClusterRole")
and ((.roleRef.name) as $rname | ($crn | index($rname)))
and (
(.subjects // [])
| map(.kind == "Group" and .name == "system:authenticated")
| any
)
)
| .metadata.name
' | sort -u)
echo "- RoleBindings to system:authenticated that use affected Roles:"
kubectl get rolebindings --all-namespaces -o json \
| jq -r --argjson rn "[\"$(echo "${ROLES_WITH_SECRET_ACCESS}" | paste -sd '","' -)\"]" '
.items[]
| select(
(.roleRef.kind == "Role")
and ((.metadata.namespace + "/" + .roleRef.name) as $rname | ($rn | index($rname)))
and (
(.subjects // [])
| map(.kind == "Group" and .name == "system:authenticated")
| any
)
)
| (.metadata.namespace + "/" + .metadata.name)
' | sort -u || true
ROLEBINDINGS_TO_SYSTEM_AUTH=$(kubectl get rolebindings --all-namespaces -o json \
| jq -r --argjson rn "[\"$(echo "${ROLES_WITH_SECRET_ACCESS}" | paste -sd '","' -)\"]" '
.items[]
| select(
(.roleRef.kind == "Role")
and ((.metadata.namespace + "/" + .roleRef.name) as $rname | ($rn | index($rname)))
and (
(.subjects // [])
| map(.kind == "Group" and .name == "system:authenticated")
| any
)
)
| (.metadata.namespace + "/" + .metadata.name)
' | sort -u)
echo "=== REVIEW REQUIRED (MANUAL CONTROL) ==="
echo "This control is MANUAL. There is no safe automatic edit of RBAC without human review."
echo
echo "1) Review the listed ClusterRoles and Roles and determine:"
echo " - Which uses of get/list/watch on secrets are strictly required."
echo " - Whether they must apply to the broad group system:authenticated."
echo
echo "2) For each binding below that is NOT required, you may remove system:authenticated"
echo " from subjects or replace it with a narrower group/service account."
echo
echo "ClusterRoleBindings to review (cluster-wide effect):"
echo "${CLUSTERROLEBINDINGS_TO_SYSTEM_AUTH:-<none>}"
echo
echo "RoleBindings to review (namespaced effect):"
echo "${ROLEBINDINGS_TO_SYSTEM_AUTH:-<none>}"
echo
echo "To edit a binding, run for each chosen binding:"
echo " # Example: edit ClusterRoleBinding"
echo " kubectl edit clusterrolebinding <clusterrolebinding-name>"
echo
echo " # Example: edit RoleBinding in a namespace"
echo " kubectl edit rolebinding <rolebinding-name> -n <namespace>"
echo
echo "In the opened YAML, locate subjects: and remove or replace entries like:"
echo " - kind: Group"
echo " name: system:authenticated"
echo
read -r -p "Press Enter after you have completed RBAC edits to re-check access, or Ctrl+C to abort... " _
echo "=== Re-checking access for system:authenticated to secrets after RBAC review ==="
FINAL_ACCESS=$(kubectl auth can-i get,list,watch secrets --all-namespaces --as=system:authenticated || echo "error")
echo "canGetListWatchSecretsAsSystemAuthenticated: ${FINAL_ACCESS}"
echo "=== Completed. ==="