> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Minimize Cluster Access To Read-Only

### More Info:

Set up the kubeconfig file appropriately and restrict cluster access so users have only the minimum permissions needed, preferring read-only access where possible.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* CIS OKE

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify how access is granted to the cluster**
           * On any machine with access to your tenancy, list all OKE clusters and note which IAM groups/users use them (via tags, naming, or documentation).
           * In OCI Console: Developer Services → Kubernetes Clusters (OKE) → each cluster → Access Cluster, and note which IAM policies reference those compartments.
           * In Terraform (if used), inspect all `oci_containerengine_cluster`, `oci_identity_policy`, and `oci_containerengine_cluster_kube_config` resources to see who is granted access.

        2. **Review OCI IAM policies that permit OKE access**
           * In OCI Console: Identity & Security → Policies → search for statements with verbs related to OKE and Kubernetes access, for example `use cluster-family`, `manage cluster-family`, `use kubeconfig`.
           * For each policy, record:
             * The group(s)/dynamic group(s) it applies to.
             * Whether it allows broad access (e.g. `manage all-resources` on the compartment/tenancy) versus specific OKE-related verbs.
           * Decide which groups should be read-only versus admin/maintainer for OKE.

        3. **Inspect generated kubeconfig files and bound identities**
           * On any machine with kubectl access, list configured contexts and clusters:
             ```sh theme={null}
             KUBECONFIG=$HOME/.kube/config kubectl config get-contexts
             KUBECONFIG=$HOME/.kube/config kubectl config view --minify --raw
             ```
           * For each user/context, determine whether the underlying OCI principal is a user or instance principal (from how the kubeconfig was created and from OCI CLI configuration in `~/.oci/config`).
           * Map each user/context back to an OCI IAM group or dynamic group discovered in step 2.

        4. **Evaluate Kubernetes RBAC for those identities**
           * On any machine with kubectl access, gather RBAC assignments:
             ```sh theme={null}
             kubectl get clusterrolebindings,rolebindings -A -o yaml > rbac-bindings.yaml
             kubectl get clusterroles,roles -A -o yaml > rbac-roles.yaml
             ```
           * In these files, look for `subjects` that match the identities used in kubeconfig (service accounts, users, groups).
           * For each subject, verify whether the bound `Role`/`ClusterRole` is:
             * Read-only (e.g. only `get`, `list`, `watch`) or
             * Broad/admin (e.g. `*` verbs, built‑in `cluster-admin`, `edit`, `admin`).
           * Flag any identities that don’t need full privileges but are bound to admin or overly broad roles.

        5. **Adjust access to minimize privileges (prefer read-only)**
           * In OCI IAM (console or IaC), for groups that should only have read-only cluster access, replace broad policy statements with least-privilege ones (for example, `inspect`/`read` level verbs on OKE resources instead of `manage all-resources`).
           * In your IaC (Terraform or similar), update:
             * Any `oci_identity_policy` resources to narrow actions and scopes.
             * Any automation that generates kubeconfigs so that:
               * Only appropriate groups/users can generate admin kubeconfigs.
               * Others get kubeconfigs aligned with read-only RBAC where possible.
           * In Kubernetes RBAC (via manifests in IaC), ensure:
             * Operational/admin identities are bound to admin roles as required.
             * Other identities are bound to custom or built-in read-only roles (`view` or custom roles with only `get`, `list`, `watch`).
             * Remove or downgrade unnecessary `cluster-admin`/`edit` bindings.

        6. **Re-verify effective access**
           * From a sample read-only user’s environment, confirm access is limited:
             ```sh theme={null}
             kubectl auth can-i get pods --all-namespaces
             kubectl auth can-i delete pods --all-namespaces
             kubectl auth can-i '*''*' --all-namespaces
             ```
           * Ensure:
             * Read operations (`get`, `list`, `watch`) required for that user are allowed.
             * Mutating operations (`create`, `update`, `patch`, `delete`) and wildcard verbs/resources are denied for users that should be read-only.
           * Repeat for admin/maintainer roles to verify they still have the necessary permissions.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot be used to change how kubeconfig files are created or how Oracle Cloud Infrastructure access to the cluster is restricted; those settings are controlled in the OCI console / IAM / API key configuration and any associated IaC. To address this finding, follow the guidance in the Manual Steps section for hardening kubeconfig generation and IAM policies.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # CIS OKE 5.1.3 – Minimize Cluster Access To Read‑Only
        #
        # This script ONLY reports on cluster‑side RBAC state so you can
        # compare it against who should have read‑only vs broader access.
        #
        # Run from: any machine with kubectl access and appropriate privileges.
        # Requirements: kubectl, jq
        #
        # Usage:
        #   ./cis_oke_5_1_3_rbac_report.sh > rbac-report.txt

        set -euo pipefail

        echo "===== CIS OKE 5.1.3 – RBAC READ-ONLY REVIEW REPORT ====="
        echo "Cluster context: $(kubectl config current-context 2>/dev/null || echo 'N/A')"
        echo

        timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
        echo "Generated at: $timestamp"
        echo

        # 1. List all users and groups that appear in RoleBindings / ClusterRoleBindings
        echo "=== 1. Subjects in RoleBindings and ClusterRoleBindings ==="
        echo

        echo "--- ClusterRoleBindings (cluster‑wide subjects) ---"
        kubectl get clusterrolebindings -o json | jq -r '
          .items[]
          | {
              name: .metadata.name,
              subjects: (.subjects // [])
            }
          | . as $crb
          | $crb.subjects[]
          | [ $crb.name, .kind, .name, (.namespace // "-"), (.apiGroup // "-") ]
          | @tsv
        ' 2>/dev/null | sort -u | awk 'BEGIN { FS="\t"; OFS="\t";
          print "CRB_NAME","SUBJECT_KIND","SUBJECT_NAME","SUBJECT_NAMESPACE","SUBJECT_APIGROUP"
        } { print }'

        echo
        echo "--- RoleBindings (namespace‑scoped subjects) ---"
        kubectl get rolebindings --all-namespaces -o json | jq -r '
          .items[]
          | {
              name: .metadata.name,
              namespace: .metadata.namespace,
              subjects: (.subjects // [])
            }
          | . as $rb
          | $rb.subjects[]
          | [ $rb.namespace, $rb.name, .kind, .name, (.namespace // "-"), (.apiGroup // "-") ]
          | @tsv
        ' 2>/dev/null | sort -u | awk 'BEGIN { FS="\t"; OFS="\t";
          print "RB_NAMESPACE","RB_NAME","SUBJECT_KIND","SUBJECT_NAME","SUBJECT_NAMESPACE","SUBJECT_APIGROUP"
        } { print }'

        echo
        echo ">>> Review: Identify human users / groups here that should be read‑only."
        echo ">>> Any user or group listed below with powerful ClusterRoles/Roles (next sections)"
        echo ">>> is a potential issue for this control."
        echo

        # 2. List ClusterRoles that are clearly more than read‑only
        echo "=== 2. ClusterRoles with non‑read‑only permissions ==="
        echo

        kubectl get clusterroles -o json | jq -r '
          .items[]
          | {
              name: .metadata.name,
              rules: (.rules // [])
            }
          | select(
              any(.rules[]?;
                any(.verbs[]?;
                  . != "get" and . != "list" and . != "watch"
                )
              )
            )
          | . as $cr
          | $cr.rules[]
          | . as $r
          | ($r.verbs[] | select(. != "get" and . != "list" and . != "watch")) as $v
          | [
              $cr.name,
              ($r.apiGroups | join(",")),
              ($r.resources | join(",")),
              $v,
              ((.resourceNames // []) | join(","))
            ]
          | @tsv
        ' 2>/dev/null | sort -u | awk 'BEGIN { FS="\t"; OFS="\t";
          print "CLUSTERROLE","APIGROUPS","RESOURCES","NON_READONLY_VERB","RESOURCE_NAMES"
        } { print }'

        echo
        echo ">>> Any ClusterRole shown here grants more than read‑only."
        echo ">>> If bound to human users or groups from section 1, it is likely non‑compliant."
        echo

        # 3. List Roles that are more than read‑only (namespace‑scoped)
        echo "=== 3. Roles with non‑read‑only permissions (namespace‑scoped) ==="
        echo

        kubectl get roles --all-namespaces -o json | jq -r '
          .items[]
          | {
              name: .metadata.name,
              namespace: .metadata.namespace,
              rules: (.rules // [])
            }
          | select(
              any(.rules[]?;
                any(.verbs[]?;
                  . != "get" and . != "list" and . != "watch"
                )
              )
            )
          | . as $role
          | $role.rules[]
          | . as $r
          | ($r.verbs[] | select(. != "get" and . != "list" and . != "watch")) as $v
          | [
              $role.namespace,
              $role.name,
              ($r.apiGroups | join(",")),
              ($r.resources | join(",")),
              $v,
              ((.resourceNames // []) | join(","))
            ]
          | @tsv
        ' 2>/dev/null | sort -u | awk 'BEGIN { FS="\t"; OFS="\t";
          print "NAMESPACE","ROLE","APIGROUPS","RESOURCES","NON_READONLY_VERB","RESOURCE_NAMES"
        } { print }'

        echo
        echo ">>> Any Role shown here grants more than read‑only in its namespace."
        echo

        # 4. Map bindings to powerful ClusterRoles/Roles
        echo "=== 4. Bindings that attach non‑read‑only roles to subjects ==="
        echo

        echo "--- ClusterRoleBindings with non‑read‑only ClusterRoles ---"
        kubectl get clusterrolebindings -o json | jq -r '
          # Build a lookup of ClusterRole -> has_non_readonly
          .items as $crbs
          | (input | .items) as $crs
        ' 2>/dev/null <<'EOF_CR_JSON' | sort -u | awk 'BEGIN { FS="\t"; OFS="\t";
          print "CRB_NAME","CLUSTERROLE","SUBJECT_KIND","SUBJECT_NAME","SUBJECT_NAMESPACE","NON_READONLY"
        } { print }'
        {
          "items": []
        }
        EOF_CR_JSON >/dev/null 2>&1 || true
        # The above placeholder is a no-op to keep jq happy on some shells.

        # Because joining two JSON streams with jq inline is cumbersome, do a simpler pass:
        #   1) Get list of non‑read‑only ClusterRoles
        #   2) Print ClusterRoleBindings that reference any of them

        non_ro_crs="$(kubectl get clusterroles -o json | jq -r '
          .items[]
          | select(
              any(.rules[]?;
                any(.verbs[]?;
                  . != "get" and . != "list" and . != "watch"
                )
              )
            )
          | .metadata.name
        ')"

        if [ -n "${non_ro_crs:-}" ]; then
          echo "$non_ro_crs" | while read -r cr; do
            [ -z "$cr" ] && continue
            kubectl get clusterrolebindings -o json | jq -r --arg CR "$cr" '
              .items[]
              | select(.roleRef.kind=="ClusterRole" and .roleRef.name==$CR)
              | . as $crb
              | (.subjects // [])[]
              | [ $crb.metadata.name, $CR, .kind, .name, (.namespace // "-"), "true" ]
              | @tsv
            ' 2>/dev/null
          done | sort -u | awk 'BEGIN { FS="\t"; OFS="\t";
            print "CRB_NAME","CLUSTERROLE","SUBJECT_KIND","SUBJECT_NAME","SUBJECT_NAMESPACE","NON_READONLY"
          } { print }'
        else
          echo "No ClusterRoles with non‑read‑only permissions found."
        fi

        echo
        echo "--- RoleBindings with non‑read‑only Roles ---"
        non_ro_roles="$(kubectl get roles --all-namespaces -o json | jq -r '
          .items[]
          | select(
              any(.rules[]?;
                any(.verbs[]?;
                  . != "get" and . != "list" and . != "watch"
                )
              )
            )
          | [.metadata.namespace, .metadata.name] | @tsv
        ')"

        if [ -n "${non_ro_roles:-}" ]; then
          echo "$non_ro_roles" | while IFS=$'\t' read -r ns role; do
            [ -z "$ns" ] && continue
            kubectl get rolebindings -n "$ns" -o json | jq -r --arg ROLE "$role" '
              .items[]
              | select(.roleRef.kind=="Role" and .roleRef.name==$ROLE)
              | . as $rb
              | (.subjects // [])[]
              | [ $rb.metadata.namespace, $rb.metadata.name, $ROLE, .kind, .name, (.namespace // "-"), "true" ]
              | @tsv
            ' 2>/dev/null
          done | sort -u | awk 'BEGIN { FS="\t"; OFS="\t";
            print "RB_NAMESPACE","RB_NAME","ROLE","SUBJECT_KIND","SUBJECT_NAME","SUBJECT_NAMESPACE","NON_READONLY"
          } { print }'
        else
          echo "No Roles with non‑read‑only permissions found."
        fi

        echo
        echo ">>> Any human user / group listed here has more than read‑only access."
        echo ">>> Compare these subjects with your intended access model and kubeconfig recipients."
        echo
        echo "===== END OF REPORT ====="
        ```

        **How to interpret problems:**

        * In sections 2 and 3, any Role/ClusterRole listed has verbs beyond `get`, `list`, `watch`.
        * In section 4, any human user or group (from your IdP) that appears is a candidate violation of “read‑only where possible”.
        * Use this report together with your cloud/IaC configuration and kubeconfig distribution records to decide where access should be reduced or split into read‑only vs admin profiles.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
