Skip to main content

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

Manual Steps
  1. List all ClusterRoles with secret access

    • Run on: any machine with kubectl access
    • 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, or watch secrets.
  2. Inspect each flagged ClusterRole’s secret permissions in detail

    • Run on: any machine with kubectl access
    • For each ClusterRole name from step 1:
      kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml \
      | sed -n '/rules:/,$p'
    • Review which secrets resources and verbs are granted, and whether any * verbs or * resources are present.
  3. Map ClusterRoles to subjects via ClusterRoleBindings

    • Run on: any machine with kubectl access
    • 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.
  4. Decide least-privilege requirements per subject

    • For each subject from step 3, answer:
      • Does it truly need get/list/watch on secrets?
      • If yes, is cluster-wide access required, or only specific namespaces?
      • Can access be narrowed to specific secret names (via resourceNames) or limited to get only instead of list/watch?
    • Document which permissions are justified and which should be removed or scope-reduced.
  5. Adjust ClusterRoles and bindings to enforce least privilege

    • Run on: any machine with kubectl access
    • 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 secrets from .rules[].resources, or
        • Remove unnecessary verbs (list, watch, or *) from .rules[].verbs, or
        • Add .resourceNames to restrict to specific secret names if appropriate.
    • If cluster-scoped access is not needed, create namespace-scoped Roles and RoleBindings instead and then remove the corresponding ClusterRoleBinding(s).
  6. Re-verify effective secret access after changes

    • Run on: any machine with kubectl access
    • 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.
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, or watch on the secrets resource.
  • 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 with verbs: ["*"] or including get, list, watch.
    • resources: ["secrets"] with verbs including any of get, list, watch where 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 create or update for 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, or system:serviceaccounts:<namespace> when the role has secret read access.
    • Bindings to many generic service accounts that don’t clearly need secret read access.

# 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.

# 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 secrets with get, list, or watch indicate 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