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