OCI Minimize Access To Secrets
More Info:
Broad get, list and watch access to secret objects allows credentials to be exfiltrated. Restrict these permissions to only the workloads and users that require them.
Risk Level
High
Address
Security
Compliance Standards
- CIS OKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List all ClusterRoles with secret access
- Run on: any machine with
kubectlaccess - Command:
kubectl get clusterroles -o json \| jq -r '.items[]| select(.rules[]?| (.resources[]? == "secrets")and ([.verbs[]?] | inside(["get","list","watch"])))| .metadata.name' | sort -u
- This identifies ClusterRoles that can
get,list, orwatchsecrets.
- Run on: any machine with
-
Inspect each flagged ClusterRole’s secret permissions in detail
- Run on: any machine with
kubectlaccess - For each ClusterRole name from step 1:
kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml \| sed -n '/rules:/,$p'
- Review which
secretsresources and verbs are granted, and whether any*verbs or*resources are present.
- Run on: any machine with
-
Map ClusterRoles to subjects via ClusterRoleBindings
- Run on: any machine with
kubectlaccess - For each ClusterRole from step 1:
kubectl get clusterrolebindings -o json \| jq -r --arg cr "<CLUSTERROLE_NAME>" '.items[]| select(.roleRef.kind == "ClusterRole" and .roleRef.name == $cr)| .metadata.name as $rb| .subjects[]?| "\($rb)\t\(.kind)\t\(.name)\t\(.namespace // "-")"'
- Use this to determine which users, groups, or service accounts receive secret access and in which namespaces they operate.
- Run on: any machine with
-
Decide least-privilege requirements per subject
- For each subject from step 3, answer:
- Does it truly need
get/list/watchonsecrets? - If yes, is cluster-wide access required, or only specific namespaces?
- Can access be narrowed to specific secret names (via
resourceNames) or limited togetonly instead oflist/watch?
- Does it truly need
- Document which permissions are justified and which should be removed or scope-reduced.
- For each subject from step 3, answer:
-
Adjust ClusterRoles and bindings to enforce least privilege
- Run on: any machine with
kubectlaccess - To remove or narrow secret access from a ClusterRole:
kubectl edit clusterrole <CLUSTERROLE_NAME>
- In the editor, either:
- Remove the entire rule that includes
secrets, or - Remove
secretsfrom.rules[].resources, or - Remove unnecessary verbs (
list,watch, or*) from.rules[].verbs, or - Add
.resourceNamesto restrict to specific secret names if appropriate.
- Remove the entire rule that includes
- In the editor, either:
- If cluster-scoped access is not needed, create namespace-scoped
Roles andRoleBindings instead and then remove the corresponding ClusterRoleBinding(s).
- Run on: any machine with
-
Re-verify effective secret access after changes
- Run on: any machine with
kubectlaccess - Re-run the detection query:
kubectl get clusterroles -o json \| jq -r '.items[]| select(.rules[]?| (.resources[]? == "secrets")and ([.verbs[]?] | inside(["get","list","watch"])))| .metadata.name' | sort -u
- Confirm that only the ClusterRoles you explicitly decided to keep with secret access remain, and that their definitions match your least-privilege decisions from step 4.
- Run on: any machine with
Using kubectl
# 1. List all ClusterRoles that can access secrets
# Run on: any machine with kubectl access
kubectl get clusterroles -o json | jq -r '
.items[]
| select(
.rules[]
| select(
(.resources[]? == "secrets")
and ((.verbs[]? | IN("get","list","watch")) == true)
)
)
| .metadata.name
' | sort -u
Output meaning:
- Any ClusterRole name printed here has at least one rule granting
get,list, orwatchon thesecretsresource. - Each such role must be manually reviewed to decide if that level of access is truly required.
# 2. Inspect the full definition of each such ClusterRole
# Replace <clusterrole-name> with a name from the previous output
kubectl get clusterrole <clusterrole-name> -o yaml
What to look for:
- Problematic examples:
resources: ["*"]combined withverbs: ["*"]or includingget,list,watch.resources: ["secrets"]withverbsincluding any ofget,list,watchwhere the role is used by broad subjects (e.g., all service accounts in a namespace).
- Safer patterns:
- Roles scoped to specific non-secret resources only.
- Roles that only use non-read verbs for secrets (e.g., rare cases of
createorupdatefor a tightly scoped automation, though these also warrant review).
# 3. Find which subjects are bound to each ClusterRole
# Run for each ClusterRole of interest from step 1
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<clusterrole-name>")
| .metadata.name + " " +
(.subjects // [] | map(.kind + ":" + .namespace + ":" + .name) | join(","))
'
Output meaning:
- Prints:
<clusterrolebinding-name> <subject-kind:namespace:name,...> - Problematic indicators:
- Bindings to
system:authenticated,system:serviceaccounts, orsystem:serviceaccounts:<namespace>when the role has secret read access. - Bindings to many generic service accounts that don’t clearly need secret read access.
- Bindings to
# 4. Also review namespaced Roles that can read secrets
kubectl get roles --all-namespaces -o json | jq -r '
.items[]
| select(
.rules[]
| select(
(.resources[]? == "secrets")
and ((.verbs[]? | IN("get","list","watch")) == true)
)
)
| .metadata.namespace + "/" + .metadata.name
' | sort
Then inspect each Role:
kubectl get role -n <namespace> <role-name> -o yaml
What to look for:
- Roles with secret read verbs in namespaces where many workloads run, especially paired with broad RoleBindings.
# 5. See which service accounts and users get these Roles
# For a specific Role in a namespace:
kubectl get rolebindings -n <namespace> -o json | jq -r '
.items[]
| select(.roleRef.kind == "Role" and .roleRef.name == "<role-name>")
| .metadata.name + " " +
(.subjects // [] | map(.kind + ":" + .namespace + ":" + .name) | join(","))
'
Problem indicators:
- RoleBindings that attach secret-reading Roles to:
- Default service accounts (e.g.,
ServiceAccount:<namespace>:default). - Large groups or all service accounts in a namespace.
- Default service accounts (e.g.,
# 6. (Optional) Quick summary: who can read secrets cluster-wide
kubectl auth can-i \
--list \
--all-namespaces \
2>/dev/null | grep -E '\bsecrets\b' | grep -E '\b(get|list|watch)\b'
Output meaning:
- Shows effective permissions; lines mentioning
secretswithget,list, orwatchindicate that the associated user/group/service account can read secrets. - Use this as a cross-check to focus your manual review.
Automation
#!/usr/bin/env bash
# Report broad access to Secrets across the cluster
# Run on: any machine with kubectl access and current context set
set -euo pipefail
echo "=== ClusterRoles and Roles with get/list/watch on secrets ==="
echo
# 1) Show all ClusterRoles that can get/list/watch secrets
kubectl get clusterroles -o json \
| jq -r '
.items[]
| select(
.rules[]
| (.resources // []) | index("secrets")
and (
(.verbs // []) | index("get")
or (.verbs // []) | index("list")
or (.verbs // []) | index("watch")
)
)
| .metadata.name
' | sort -u | while read -r cr; do
echo "ClusterRole: ${cr}"
kubectl get clusterrole "${cr}" -o json \
| jq '
{
name: .metadata.name,
rules: (
.rules
| map(
select(
(.resources // []) | index("secrets")
and (
(.verbs // []) | index("get")
or (.verbs // []) | index("list")
or (.verbs // []) | index("watch")
)
)
)
)
}'
echo
done
echo "=== Roles with get/list/watch on secrets (all namespaces) ==="
echo
kubectl get roles --all-namespaces -o json \
| jq -r '
.items[]
| select(
.rules[]
| (.resources // []) | index("secrets")
and (
(.verbs // []) | index("get")
or (.verbs // []) | index("list")
or (.verbs // []) | index("watch")
)
)
| "\(.metadata.namespace) \(.metadata.name)"
' | sort -u | while read -r ns role; do
echo "Role: ${role} (namespace: ${ns})"
kubectl get role "${role}" -n "${ns}" -o json \
| jq '
{
namespace: .metadata.namespace,
name: .metadata.name,
rules: (
.rules
| map(
select(
(.resources // []) | index("secrets")
and (
(.verbs // []) | index("get")
or (.verbs // []) | index("list")
or (.verbs // []) | index("watch")
)
)
)
)
}'
echo
done
echo "=== ClusterRoleBindings referencing those ClusterRoles ==="
echo
# Get list once to avoid recomputation
problem_clusterroles=$(
kubectl get clusterroles -o json \
| jq -r '
.items[]
| select(
.rules[]
| (.resources // []) | index("secrets")
and (
(.verbs // []) | index("get")
or (.verbs // []) | index("list")
or (.verbs // []) | index("watch")
)
)
| .metadata.name
' | sort -u
)
if [ -n "${problem_clusterroles}" ]; then
kubectl get clusterrolebindings -o json \
| jq -r --argjson crs "$(printf '%s\n' ${problem_clusterroles} | jq -R . | jq -s .)" '
.items[]
| select(.roleRef.kind == "ClusterRole")
| select(.roleRef.name as $n | $crs | index($n))
| .metadata.name
' | sort -u | while read -r crb; do
echo "ClusterRoleBinding: ${crb}"
kubectl get clusterrolebinding "${crb}" -o json \
| jq '
{
name: .metadata.name,
roleRef: .roleRef,
subjects: (.subjects // [])
}'
echo
done
else
echo "No ClusterRoles with get/list/watch on secrets found."
fi
echo "=== RoleBindings referencing those Roles (by namespace) ==="
echo
# Build a temp file of roles-with-secret-access to speed up lookups
tmp_roles=$(mktemp)
kubectl get roles --all-namespaces -o json \
| jq -r '
.items[]
| select(
.rules[]
| (.resources // []) | index("secrets")
and (
(.verbs // []) | index("get")
or (.verbs // []) | index("list")
or (.verbs // []) | index("watch")
)
)
| "\(.metadata.namespace) \(.metadata.name)"
' | sort -u > "${tmp_roles}"
kubectl get rolebindings --all-namespaces -o json \
| jq -r '
.items[]
| "\(.metadata.namespace) \(.metadata.name) \(.roleRef.kind) \(.roleRef.name)"
' \
| while read -r ns rbkind kind name; do
if [ "${kind}" = "Role" ]; then
if grep -q "^${ns} ${name}$" "${tmp_roles}"; then
echo "RoleBinding: ${rbkind} (namespace: ${ns})"
kubectl get rolebinding "${rbkind}" -n "${ns}" -o json \
| jq '
{
namespace: .metadata.namespace,
name: .metadata.name,
roleRef: .roleRef,
subjects: (.subjects // [])
}'
echo
fi
fi
done
rm -f "${tmp_roles}"
echo "=== INTERPRETATION ==="
cat <<'EOF'
Output that indicates potential problems:
1. ClusterRoles or Roles whose rules include:
- resources: ["secrets"] (or contains "secrets")
- verbs includes any of: "get", "list", "watch"
AND they are:
- Generic or broad-scope roles (e.g., used cluster-wide, default/admin/view/edit-like roles you created, or names suggesting infrastructure-wide use).
- Intended for many users/service accounts rather than a single tightly-scoped workload.
2. ClusterRoleBindings or RoleBindings that attach such roles to:
- system:authenticated, system:serviceaccounts, or other large groups.
- Namespaces or service accounts not explicitly needing Secret read access.
Use this report to:
- Confirm which subjects (users, groups, service accounts) truly need get/list/watch on secrets.
- Plan manual tightening: remove or narrow rules/verbs/resources, or split roles so that only specific workloads keep Secret access.
This script does NOT apply any changes; it only surfaces where broad Secret read permissions exist.
EOF