Skip to main content

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

Manual Steps
  1. 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 -u
      kubectl 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
  2. 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_NAME from 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
  3. 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, or watch on 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>
  4. 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/watch if 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, and watch for secrets from the relevant Role/ClusterRole rules.
  5. 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.yaml
      kubectl delete clusterrolebinding <NAME> # only when confirmed no longer needed
      kubectl delete rolebinding -n <NAMESPACE> <NAME>
  6. 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 -u
      kubectl 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, or watch on secrets.
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):

    • verbs includes any of: ["get"], ["list"], ["watch"], or ["*"]
    • Wildcard resources or API groups:
      • resources: ["*"] together with verbs that include get, list, or watch
      • apiGroups: ["*"] or very broad API groups
    • ClusterRoles that include secrets and are bound widely (see below).
  • Lower‑risk / typically acceptable:

    • verbs limited 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 ClusterRoleBinding to:
    • system:serviceaccounts (all service accounts), or
    • system: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: ["*"] or resources: ["*"], 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