OCI Minimize Access To Secrets
More Info:
Where possible, remove get, list and watch access to secret objects in the cluster.
Risk Level
High
Address
Security
Compliance Standards
- APRA CPS 234 (Australia)
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS Critical Security Controls v8
- CIS OKE
- CMMC 2.0
- CSA Cloud Controls Matrix v4
- DPDPA
- Digital Operational Resilience Act (EU)
- Essential 8
- ISO/IEC 27017
- ISO/IEC 27018
- ISO/IEC 27701
- KSA PDPL
- MAS Technology Risk Management (Singapore)
- MITRE ATT&CK (Cloud)
- NIS2 Directive
- NIST SP 800-171
- NYDFS 23 NYCRR 500
- SWIFT Customer Security Controls Framework
- Sarbanes-Oxley IT General Controls
- UK NCSC Cyber Assessment Framework
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List all Roles/ClusterRoles that can access Secrets
- Run on: any machine with kubectl access
- Command:
kubectl get clusterroles -o json | jq -r '.items[] |select(.rules[]? | any(.resources[]?; . == "secrets") and any(.verbs[]?; IN("get","list","watch"))) |.metadata.name' | sort -ukubectl get roles --all-namespaces -o json | jq -r '.items[] |select(.rules[]? | any(.resources[]?; . == "secrets") and any(.verbs[]?; IN("get","list","watch"))) |(.metadata.namespace + "/" + .metadata.name)' | sort -u
-
Identify who is bound to those Roles/ClusterRoles
- Run on: any machine with kubectl access
- For each ClusterRole name from step 1:
kubectl get clusterrolebindings -o json | jq -r '.items[] |select(.roleRef.kind=="ClusterRole" and .roleRef.name=="<CLUSTER_ROLE_NAME>") |.metadata.name as $b |.subjects[]? | "\($b) \(.kind)/\(.namespace // "-")/\(.name)"' | sort
- For each namespaced Role
NAMESPACE/ROLE_NAMEfrom step 1:kubectl get rolebindings -n <NAMESPACE> -o json | jq -r '.items[] |select(.roleRef.kind=="Role" and .roleRef.name=="<ROLE_NAME>") |.metadata.name as $b |.subjects[]? | "\($b) \(.kind)/\(.namespace // "-")/\(.name)"' | sort
-
Review necessity of Secrets access per subject
- For each subject (User, Group, ServiceAccount) identified in step 2, determine:
- What application or admin function it supports.
- Whether it truly needs
get,list, orwatchon all Secrets, or only on a subset, or not at all.
- Optional evidence commands:
kubectl describe clusterrole <CLUSTER_ROLE_NAME>kubectl describe role -n <NAMESPACE> <ROLE_NAME>
- For each subject (User, Group, ServiceAccount) identified in step 2, determine:
-
Narrow overly broad permissions
- If a subject only needs specific secrets, create or edit a Role/ClusterRole to scope access by namespace and, where possible, by name or label, and remove
list/watchif not strictly required. Example (edit manifest, then apply):kubectl apply -f /absolute/path/to/edited-role.yaml - If a subject no longer needs secrets at all, remove
get,list, andwatchforsecretsfrom the relevant Role/ClusterRole rules.
- If a subject only needs specific secrets, create or edit a Role/ClusterRole to scope access by namespace and, where possible, by name or label, and remove
-
Update or create RoleBindings/ClusterRoleBindings to use least-privilege roles
- Bind subjects to newly created or tightened roles, and unbind from broad roles that grant secrets access they no longer require.
- Commands:
kubectl apply -f /absolute/path/to/edited-binding.yamlkubectl delete clusterrolebinding <NAME> # only when confirmed no longer neededkubectl delete rolebinding -n <NAMESPACE> <NAME>
-
Verify reduced Secrets access
- Re-run evidence collection:
kubectl get clusterroles -o json | jq -r '.items[] |select(.rules[]? | any(.resources[]?; . == "secrets") and any(.verbs[]?; IN("get","list","watch"))) |.metadata.name' | sort -ukubectl get roles --all-namespaces -o json | jq -r '.items[] |select(.rules[]? | any(.resources[]?; . == "secrets") and any(.verbs[]?; IN("get","list","watch"))) |(.metadata.namespace + "/" + .metadata.name)' | sort -u
- Confirm that only those roles strictly required for operations still have
get,list, orwatchonsecrets.
- Re-run evidence collection:
Using kubectl
# 1) List all Roles and their rules that touch "secrets"
# Run on: any machine with kubectl access
kubectl get roles --all-namespaces -o yaml | \
awk '
$1=="kind:" && $2=="Role" {role=$0}
/resources:/ {inres=1}
inres && /secrets/ {print "---"; print role; inrole=1}
inrole && /^ - / {print}
/^rules:/ {inrole=1}
/^metadata:/ {meta=1}
meta && /name:/ {print}
'
# 2) List all ClusterRoles and their rules that touch "secrets"
kubectl get clusterroles -o yaml | \
awk '
$1=="kind:" && $2=="ClusterRole" {cr=$0}
/resources:/ {inres=1}
inres && /secrets/ {print "---"; print cr; incr=1}
incr && /^ - / {print}
/^rules:/ {incr=1}
/^metadata:/ {meta=1}
meta && /name:/ {print}
'
# Alternative, more direct view for Roles:
kubectl get roles --all-namespaces -o yaml | \
yq '.items[]
| select(.rules[]? | any(.resources[]? == "secrets"))
| {namespace: .metadata.namespace, name: .metadata.name, rules: .rules}'
# Alternative, more direct view for ClusterRoles:
kubectl get clusterroles -o yaml | \
yq '.items[]
| select(.rules[]? | any(.resources[]? == "secrets"))
| {name: .metadata.name, rules: .rules}'
What to look for in the output
You need to manually review each Role/ClusterRole that includes resources: ["secrets"] (or ["*"]) and see which verbs are allowed:
-
High‑risk patterns (likely problems):
verbsincludes any of:["get"],["list"],["watch"], or["*"]- Wildcard resources or API groups:
resources: ["*"]together withverbsthat includeget,list, orwatchapiGroups: ["*"]or very broad API groups
- ClusterRoles that include
secretsand are bound widely (see below).
-
Lower‑risk / typically acceptable:
verbslimited to mutation without read, e.g.["create", "update", "patch", "delete"]for tightly scoped, system components that must manage secrets.- Roles confined to a narrow namespace and bound only to a specific service account that clearly needs the access.
After identifying suspect Roles/ClusterRoles, inspect who can use them:
# 3) See which subjects are bound to a specific Role (namespace-scoped)
# Replace ROLE_NAME and NAMESPACE with actual values from the previous output.
kubectl get rolebindings -n NAMESPACE -o yaml | \
yq '.items[]
| select(.roleRef.kind == "Role" and .roleRef.name == "ROLE_NAME")
| {name: .metadata.name, subjects: .subjects, roleRef: .roleRef}'
# 4) See which subjects are bound to a specific ClusterRole (cluster-scoped)
# Replace CLUSTERROLE_NAME with actual values from the previous output.
kubectl get clusterrolebindings -o yaml | \
yq '.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "CLUSTERROLE_NAME")
| {name: .metadata.name, subjects: .subjects, roleRef: .roleRef}'
What indicates a problem in bindings
A Role/ClusterRole that can get, list, or watch secrets is especially concerning when:
- It is a ClusterRole bound via
ClusterRoleBindingto:system:serviceaccounts(all service accounts), orsystem:authenticated,system:unauthenticated, or other very broad groups, or- Many different user subjects where the business need is unclear.
- It is a namespace Role bound to:
- A generic or shared service account used by many workloads.
- Human users or groups without a strong justification to see secrets.
- It uses
verbs: ["*"]orresources: ["*"], which implicitly allow full secret read access.
These outputs tell you where secret read access exists; you then decide which Roles/ClusterRoles and bindings should be tightened or removed according to the benchmark guidance.
Automation
#!/usr/bin/env bash
# Report which subjects have get/list/watch access to core/v1 secrets cluster-wide.
# Run on any machine with kubectl access and appropriate RBAC permissions.
set -euo pipefail
echo "=== Cluster‑wide RBAC access to core/v1 secrets (get, list, watch) ==="
echo
# 1) Show all RBAC rules that allow get/list/watch on secrets
echo "== Roles and ClusterRoles that grant get/list/watch on secrets =="
kubectl get clusterrole,role -A -o json \
| jq -r '
.items[]
| {
kind,
metadata: {name, namespace},
rules
}
| select(.rules != null)
| . as $r
| $r.rules[]
| select(
(.resources[]? | . == "secrets")
and
(.verbs[]? | IN("get","list","watch"))
)
| {
kind: $r.kind,
name: $r.metadata.name,
namespace: ($r.metadata.namespace // ""),
resources: .resources,
verbs: .verbs
}
' | jq -s '
sort_by(.kind, .namespace, .name)
' || echo "Failed to query roles/clusterroles. Check RBAC or kubectl context."
echo
# 2) For each such Role/ClusterRole, show which subjects are bound to it
echo "== RoleBindings and ClusterRoleBindings for those roles =="
# Extract names of roles/clusterroles that touch secrets with get/list/watch
ROLE_LIST=$(kubectl get clusterrole,role -A -o json \
| jq -r '
.items[]
| {
kind,
metadata: {name, namespace},
rules
}
| select(.rules != null)
| . as $r
| select(
[ .rules[]
| select(
(.resources[]? | . == "secrets")
and
(.verbs[]? | IN("get","list","watch"))
)
] | length > 0
)
| if .kind == "ClusterRole" then
"ClusterRole " + .metadata.name
else
"Role " + .metadata.namespace + "/" + .metadata.name
end
' | sort -u)
if [ -z "${ROLE_LIST}" ]; then
echo "No roles/clusterroles grant get/list/watch on secrets."
exit 0
fi
echo "Roles/ClusterRoles with secrets get/list/watch access:"
echo "${ROLE_LIST}"
echo
echo "Bindings and subjects:"
kubectl get clusterrolebinding,rolebinding -A -o json \
| jq -r --argfile roles <(
# Convert ROLE_LIST into JSON array for jq
printf '%s\n' "${ROLE_LIST}" | jq -R . | jq -s .
) '
.items[]
| {
kind,
metadata: {name, namespace},
roleRef,
subjects
}
| select(.roleRef != null and .subjects != null)
| . as $b
| (
if $b.roleRef.kind == "ClusterRole" then
"ClusterRole " + $b.roleRef.name
else
"Role " + ($b.metadata.namespace // "") + "/" + $b.roleRef.name
end
) as $ref
| select($ref as $rref | $roles[] | . == $rref)
| {
bindingKind: .kind,
bindingName: .metadata.name,
bindingNamespace: (.metadata.namespace // ""),
roleRefKind: .roleRef.kind,
roleRefName: .roleRef.name,
subjects: .subjects
}
' | jq -s 'sort_by(.bindingKind, .bindingNamespace, .bindingName)'
echo
cat <<'EOF'
How to interpret this output (what indicates a problem):
1) In the "Roles and ClusterRoles that grant get/list/watch on secrets" section:
- Any Role/ClusterRole that:
- applies to "secrets" AND
- includes "get", "list", or "watch"
represents potential exposure of secret contents.
Pay special attention to:
- ClusterRoles (they can apply cluster-wide).
- Roles in namespaces that do not need direct secret access.
- Roles with broad verbs including "list"/"watch" (enumeration/continuous access).
2) In the "Bindings and subjects" section:
- Each listed RoleBinding/ClusterRoleBinding shows:
- Which Role/ClusterRole (roleRef*) grants secrets access.
- Which subjects (users, groups, serviceAccounts) receive that access.
Potential issues include:
- Bindings to "system:authenticated" or other broad groups.
- Service accounts used by many workloads or by external integrations.
- Human user accounts that do not have a clear operational need for secrets access.
Use this report to:
- Identify Roles/ClusterRoles that should drop get/list/watch on secrets.
- Identify bindings that should be removed or replaced with narrower roles.
- Confirm that any remaining access is strictly necessary and documented.
EOF