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

# OCI OKE Should Use Dedicated Service Accounts

### More Info:

Each workload should run under a dedicated ServiceAccount with the minimum role bindings it needs, instead of sharing accounts across apps. This makes per-workload audit and revocation tractable.

### Risk Level

Medium

### Address

Compliance, Security

### Compliance Standards

* CIS OKE

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Using Console" defaultOpen="true">
        In OKE this is a Kubernetes-level fix, but you can drive it from the OCI Console (using Cloud Shell or downloaded kubeconfig). Below are the steps using the OCI Console.

        ***

        ### 1. Open your OKE cluster from the OCI Console

        1. Sign in to OCI Console.
        2. In the left menu: **Developer Services → Kubernetes Clusters (OKE)**.
        3. Select the **Compartment** that contains your cluster.
        4. Click your **cluster name**.

        ***

        ### 2. Get access to the cluster (from the Console)

        You have two easy options:

        #### Option A – Use OCI Cloud Shell (no local setup)

        1. On the top-right of the console, click the **Cloud Shell** icon (a `>_` terminal).

        2. In Cloud Shell, generate kubeconfig for this OKE cluster:

           ```bash theme={null}
           oci ce cluster create-kubeconfig \
             --cluster-id <your-cluster-ocid> \
             --file $HOME/.kube/config \
             --region <your-region> \
             --token-version 2.0.0 \
             --kube-endpoint PUBLIC_ENDPOINT
           ```

        3. Test access:

           ```bash theme={null}
           kubectl get nodes
           ```

        #### Option B – Download kubeconfig for local use

        1. In the OKE **cluster details** page, click **Access Cluster**.
        2. Click **Local Access** and follow the instructions:
           * Download the kubeconfig file.
           * Set the `KUBECONFIG` environment variable or merge into `~/.kube/config`.
        3. Test with:

           ```bash theme={null}
           kubectl get nodes
           ```

        ***

        ### 3. Create a dedicated Service Account for each workload

        Repeat per application / microservice.

        1. Decide the **namespace** and **SA name** (for example: namespace `prod-apps`, SA `orders-sa`).

        2. If the namespace doesn’t exist, create it:

           ```bash theme={null}
           kubectl create namespace prod-apps
           ```

        3. Create a ServiceAccount YAML file in Cloud Shell (or locally), e.g. `orders-sa.yaml`:

           ```yaml theme={null}
           apiVersion: v1
           kind: ServiceAccount
           metadata:
             name: orders-sa
             namespace: prod-apps
           ```

        4. Apply it:

           ```bash theme={null}
           kubectl apply -f orders-sa.yaml
           ```

        ***

        ### 4. Grant least-privilege RBAC to the Service Account

        Create a `Role`/`ClusterRole` and `RoleBinding`/`ClusterRoleBinding` for each SA.

        Example (namespace-scoped permissions):

        1. Create `orders-role.yaml`:

           ```yaml theme={null}
           apiVersion: rbac.authorization.k8s.io/v1
           kind: Role
           metadata:
             namespace: prod-apps
             name: orders-role
           rules:
             - apiGroups: [""]
               resources: ["pods", "services"]
               verbs: ["get", "list", "watch"]
           ```

        2. Create `orders-rolebinding.yaml`:

           ```yaml theme={null}
           apiVersion: rbac.authorization.k8s.io/v1
           kind: RoleBinding
           metadata:
             name: orders-rolebinding
             namespace: prod-apps
           subjects:
             - kind: ServiceAccount
               name: orders-sa
               namespace: prod-apps
           roleRef:
             kind: Role
             name: orders-role
             apiGroup: rbac.authorization.k8s.io
           ```

        3. Apply them:

           ```bash theme={null}
           kubectl apply -f orders-role.yaml
           kubectl apply -f orders-rolebinding.yaml
           ```

        ***

        ### 5. Configure workloads to use the dedicated Service Account

        For each **Deployment/StatefulSet/Job**, set `serviceAccountName`.

        Example deployment manifest snippet:

        ```yaml theme={null}
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: orders-deployment
          namespace: prod-apps
        spec:
          replicas: 3
          selector:
            matchLabels:
              app: orders
          template:
            metadata:
              labels:
                app: orders
            spec:
              serviceAccountName: orders-sa
              containers:
                - name: orders
                  image: <your-image>
                  ...
        ```

        Apply the updated deployment:

        ```bash theme={null}
        kubectl apply -f orders-deployment.yaml
        ```

        Verify the pods use the correct SA:

        ```bash theme={null}
        kubectl get pods -n prod-apps -o custom-columns=NAME:.metadata.name,SA:.spec.serviceAccountName
        ```

        ***

        ### 6. Restrict or avoid the default Service Account

        To enforce dedicated SAs:

        1. Remove unnecessary permissions from the namespace’s default SA:

           * Inspect bindings:

             ```bash theme={null}
             kubectl get rolebinding,clusterrolebinding -A | grep default
             ```

           * Remove any bindings that grant elevated rights to `default` ServiceAccounts.

        2. Optionally, use an **admission controller** (e.g., OPA Gatekeeper or Kyverno) to deny pods that don’t specify `serviceAccountName`.\
           This is configured inside the cluster, but can also be applied using manifests via Cloud Shell from the OCI Console.

        ***

        If you share how your workloads are currently defined (Deployment YAML or Helm chart), I can give you the exact edits needed to switch them to dedicated Service Accounts.
      </Accordion>

      <Accordion title="Using CLI">
        Below are step‑by‑step remediation instructions to ensure OCI OKE workloads use **dedicated Kubernetes service accounts** (not the `default` one), starting from OCI CLI and then using `kubectl`.

        ***

        ## 1. Get kubeconfig for your OKE cluster via OCI CLI

        1. Identify your OKE cluster OCID:
           ```bash theme={null}
           oci ce cluster list \
             --compartment-id <COMPARTMENT_OCID> \
             --query "data[].{name:name, id:id}" \
             --output table
           ```

        2. Generate/update kubeconfig for that cluster:
           ```bash theme={null}
           oci ce cluster create-kubeconfig \
             --cluster-id <CLUSTER_OCID> \
             --file $HOME/.kube/config \
             --region <OCI_REGION> \
             --token-version 2.0.0 \
             --kube-endpoint PUBLIC_ENDPOINT \
             --overwrite
           ```

        3. Test connectivity:
           ```bash theme={null}
           kubectl get nodes
           ```

        ***

        ## 2. Identify workloads using the default service account

        Check each namespace (or target namespaces):

        ```bash theme={null}
        kubectl get pods --all-namespaces -o custom-columns=\
        'NAMESPACE:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName'
        ```

        Any pod with empty `SA` or `default` in the `SA` column is using the default service account.

        ***

        ## 3. Create a dedicated service account per application

        Example for a namespace `prod` and app `my-app`:

        ```bash theme={null}
        kubectl create namespace prod   # if not existing

        kubectl create serviceaccount my-app-sa -n prod
        ```

        Confirm:

        ```bash theme={null}
        kubectl get sa -n prod
        ```

        ***

        ## 4. (Optional but recommended) Bind minimal RBAC permissions

        Create a minimal `Role` and `RoleBinding` for that service account.

        1. Create a Role manifest (file: `my-app-role.yaml`):

           ```yaml theme={null}
           apiVersion: rbac.authorization.k8s.io/v1
           kind: Role
           metadata:
             name: my-app-role
             namespace: prod
           rules:
           - apiGroups: [""]
             resources: ["pods"]
             verbs: ["get", "list"]
           ```

        2. Apply the Role:

           ```bash theme={null}
           kubectl apply -f my-app-role.yaml
           ```

        3. Create a RoleBinding (file: `my-app-rolebinding.yaml`):

           ```yaml theme={null}
           apiVersion: rbac.authorization.k8s.io/v1
           kind: RoleBinding
           metadata:
             name: my-app-rolebinding
             namespace: prod
           subjects:
           - kind: ServiceAccount
             name: my-app-sa
             namespace: prod
           roleRef:
             kind: Role
             name: my-app-role
             apiGroup: rbac.authorization.k8s.io
           ```

        4. Apply the RoleBinding:

           ```bash theme={null}
           kubectl apply -f my-app-rolebinding.yaml
           ```

        ***

        ## 5. Update workloads to use the dedicated service account

        Edit each Deployment/StatefulSet/Job/etc. so the pod spec uses your new service account.

        Example patch for a Deployment:

        ```bash theme={null}
        kubectl patch deployment my-app-deployment \
          -n prod \
          --type merge \
          -p '{"spec":{"template":{"spec":{"serviceAccountName":"my-app-sa"}}}}'
        ```

        Or edit directly:

        ```bash theme={null}
        kubectl edit deployment my-app-deployment -n prod
        ```

        Add under `spec.template.spec`:

        ```yaml theme={null}
        serviceAccountName: my-app-sa
        ```

        The Deployment rollout will recreate pods with the new service account.

        ***

        ## 6. Verify that pods are no longer using the default service account

        ```bash theme={null}
        kubectl get pods -n prod \
          -o custom-columns=NAME:.metadata.name,SA:.spec.serviceAccountName
        ```

        Ensure the relevant pods show `my-app-sa` (or another dedicated SA), not `default`.

        ***

        ## 7. (Optional) Enforce policy to prevent using `default` SA

        If you use **OPA Gatekeeper** or **Kyverno**, deploy a policy that rejects workloads using `default` service accounts. Example (Gatekeeper ConstraintTemplate snippet-style):

        ```yaml theme={null}
        apiVersion: templates.gatekeeper.sh/v1beta1
        kind: ConstraintTemplate
        metadata:
          name: k8sdisallowdefaultsa
        spec:
          crd:
            spec:
              names:
                kind: K8sDisallowDefaultSA
          targets:
          - target: admission.k8s.gatekeeper.sh
            rego: |
              package k8sdisallowdefaultsa

              violation[{"msg": msg}] {
                input.review.kind.kind == "Pod"
                sa := input.review.object.spec.serviceAccountName
                sa == "default" or sa == ""
                msg := sprintf("Using default service account is not allowed: %v", [sa])
              }
        ```

        Apply the template and a corresponding `Constraint` to enforce it across namespaces.

        ***

        Summary of OCI CLI involvement:

        * Use **OCI CLI** to get kubeconfig for the OKE cluster.
        * Perform the Kubernetes changes (`kubectl`) to create dedicated service accounts, bind RBAC, and update workloads.
      </Accordion>

      <Accordion title="Using Python">
        To remediate “OCI OKE Should Use Dedicated Service Accounts” you need to:

        1. **Stop using the `default` service account** for Pods/Deployments.
        2. **Create dedicated service accounts** per workload (or per trust boundary).
        3. **Bind only the minimal RBAC permissions** to each service account.
        4. **Patch workloads** to use those service accounts.

        Below is a step‑by‑step guide and Python examples using the Kubernetes Python client.

        ***

        ## 1. Prerequisites

        Install the Kubernetes Python client:

        ```bash theme={null}
        pip install kubernetes
        ```

        Make sure your environment can authenticate to the OKE cluster, e.g. by:

        ```bash theme={null}
        kubectl config use-context <oke-cluster-context>
        ```

        The Python client will reuse this kubeconfig.

        ***

        ## 2. Detect workloads using the default service account

        ```python theme={null}
        from kubernetes import client, config

        # Load kubeconfig (~/.kube/config)
        config.load_kube_config()

        apps_v1 = client.AppsV1Api()
        core_v1 = client.CoreV1Api()

        # List all namespaces
        namespaces = [ns.metadata.name for ns in core_v1.list_namespace().items]

        # Helper to check if pod template uses default SA
        def uses_default_sa(pod_spec):
            # If serviceAccountName is None, it will use 'default' in that namespace
            return pod_spec.service_account_name in [None, "", "default"]

        default_sa_workloads = []

        for ns in namespaces:
            # Deployments
            deps = apps_v1.list_namespaced_deployment(namespace=ns).items
            for d in deps:
                if uses_default_sa(d.spec.template.spec):
                    default_sa_workloads.append(("Deployment", ns, d.metadata.name))

            # StatefulSets
            ssets = apps_v1.list_namespaced_stateful_set(namespace=ns).items
            for s in ssets:
                if uses_default_sa(s.spec.template.spec):
                    default_sa_workloads.append(("StatefulSet", ns, s.metadata.name))

            # DaemonSets
            dsets = apps_v1.list_namespaced_daemon_set(namespace=ns).items
            for ds in dsets:
                if uses_default_sa(ds.spec.template.spec):
                    default_sa_workloads.append(("DaemonSet", ns, ds.metadata.name))

        print("Workloads using default ServiceAccount:")
        for kind, ns, name in default_sa_workloads:
            print(f"{kind} {ns}/{name}")
        ```

        ***

        ## 3. Create a dedicated service account (per workload or per app)

        Example: one dedicated service account per deployment:

        ```python theme={null}
        from kubernetes.client import V1ServiceAccount, V1ObjectMeta

        def ensure_service_account(namespace, sa_name):
            try:
                core_v1.read_namespaced_service_account(name=sa_name, namespace=namespace)
                print(f"ServiceAccount {namespace}/{sa_name} already exists")
            except client.exceptions.ApiException as e:
                if e.status == 404:
                    sa_body = V1ServiceAccount(
                        metadata=V1ObjectMeta(name=sa_name)
                    )
                    core_v1.create_namespaced_service_account(
                        namespace=namespace,
                        body=sa_body
                    )
                    print(f"Created ServiceAccount {namespace}/{sa_name}")
                else:
                    raise

        # Example: create one SA per workload found above
        for kind, ns, name in default_sa_workloads:
            sa_name = f"{name}-sa"
            ensure_service_account(ns, sa_name)
        ```

        ***

        ## 4. Bind minimal RBAC permissions to the service account

        Define `Role` and `RoleBinding` (adjust rules for what the workload actually needs):

        ```python theme={null}
        from kubernetes.client import (
            RbacAuthorizationV1Api,
            V1Role, V1RoleRule,
            V1RoleBinding, V1RoleRef, V1Subject
        )

        rbac_v1 = RbacAuthorizationV1Api()

        def ensure_role(namespace, role_name, rules):
            try:
                rbac_v1.read_namespaced_role(role_name, namespace)
                print(f"Role {namespace}/{role_name} already exists")
            except client.exceptions.ApiException as e:
                if e.status == 404:
                    role_body = V1Role(
                        metadata=V1ObjectMeta(name=role_name),
                        rules=rules
                    )
                    rbac_v1.create_namespaced_role(namespace, role_body)
                    print(f"Created Role {namespace}/{role_name}")
                else:
                    raise

        def ensure_role_binding(namespace, rb_name, role_name, sa_name):
            try:
                rbac_v1.read_namespaced_role_binding(rb_name, namespace)
                print(f"RoleBinding {namespace}/{rb_name} already exists")
            except client.exceptions.ApiException as e:
                if e.status == 404:
                    rb_body = V1RoleBinding(
                        metadata=V1ObjectMeta(name=rb_name),
                        role_ref=V1RoleRef(
                            api_group="rbac.authorization.k8s.io",
                            kind="Role",
                            name=role_name
                        ),
                        subjects=[
                            V1Subject(
                                kind="ServiceAccount",
                                name=sa_name,
                                namespace=namespace
                            )
                        ]
                    )
                    rbac_v1.create_namespaced_role_binding(namespace, rb_body)
                    print(f"Created RoleBinding {namespace}/{rb_name}")
                else:
                    raise

        # Example minimal rule: allow reading ConfigMaps in same namespace (adjust as needed)
        example_rules = [
            V1RoleRule(
                api_groups=[""],
                resources=["configmaps"],
                verbs=["get", "list"]
            )
        ]

        for kind, ns, name in default_sa_workloads:
            sa_name = f"{name}-sa"
            role_name = f"{name}-role"
            rb_name = f"{name}-rb"

            ensure_role(ns, role_name, example_rules)
            ensure_role_binding(ns, rb_name, role_name, sa_name)
        ```

        Customize `example_rules` per application’s real needs.

        ***

        ## 5. Patch workloads to use the dedicated service account

        ```python theme={null}
        def patch_workload_service_account(kind, namespace, name, sa_name):
            pod_spec_patch = {"spec": {"template": {"spec": {"serviceAccountName": sa_name}}}}

            if kind == "Deployment":
                apps_v1.patch_namespaced_deployment(
                    name=name,
                    namespace=namespace,
                    body=pod_spec_patch
                )
            elif kind == "StatefulSet":
                apps_v1.patch_namespaced_stateful_set(
                    name=name,
                    namespace=namespace,
                    body=pod_spec_patch
                )
            elif kind == "DaemonSet":
                apps_v1.patch_namespaced_daemon_set(
                    name=name,
                    namespace=namespace,
                    body=pod_spec_patch
                )
            else:
                raise ValueError(f"Unsupported kind: {kind}")

            print(f"Patched {kind} {namespace}/{name} to use ServiceAccount {sa_name}")

        for kind, ns, name in default_sa_workloads:
            sa_name = f"{name}-sa"
            patch_workload_service_account(kind, ns, name, sa_name)
        ```

        After patching, Kubernetes will roll Pods so they run with the new service account.

        ***

        ## 6. Validation

        ```bash theme={null}
        kubectl get pods -A -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName' | sort
        ```

        Ensure that no application Pods are using the `default` service account (except where strictly intended, and ideally never in production namespaces).

        ***

        This approach is generic for any OKE cluster, since OKE exposes a standard Kubernetes API; the Python code above can be run from any environment that has network access to the cluster and valid kubeconfig.
      </Accordion>

      <Accordion title="Using Terraform">
        This finding cannot be remediated on the `oci_containerengine_cluster` (OKE cluster) Terraform resource, because Kubernetes ServiceAccounts are workload-level objects inside the cluster, not an OCI cluster setting.

        To fix it in Terraform you must manage the workloads themselves (via the Kubernetes provider or Helm), and for each workload define and reference its own ServiceAccount, for example:

        ```hcl theme={null}
        # Provider pointing at your OKE cluster
        provider "kubernetes" {
          host                   = oci_containerengine_cluster.MY_CLUSTER.endpoints[0].kubernetes
          cluster_ca_certificate = base64decode(oci_containerengine_cluster.MY_CLUSTER.metadata[0].cluster_ca)
          token                  = data.oci_containerengine_cluster_kubeconfig.MY_CLUSTER_KUBECONFIG.token
        }

        resource "kubernetes_service_account" "APP_A_SA" {
          metadata {
            name      = "app-a-sa"
            namespace = "APP_A_NAMESPACE" # replace with the namespace for app A
          }
        }

        resource "kubernetes_role" "APP_A_ROLE" {
          metadata {
            name      = "app-a-role"
            namespace = "APP_A_NAMESPACE"
          }

          rule {
            api_groups = [""]
            resources  = ["configmaps"]
            verbs      = ["get", "list"] # minimum needed for this workload
          }
        }

        resource "kubernetes_role_binding" "APP_A_RB" {
          metadata {
            name      = "app-a-rb"
            namespace = "APP_A_NAMESPACE"
          }

          role_ref {
            api_group = "rbac.authorization.k8s.io"
            kind      = "Role"
            name      = kubernetes_role.APP_A_ROLE.metadata[0].name
          }

          subject {
            kind      = "ServiceAccount"
            name      = kubernetes_service_account.APP_A_SA.metadata[0].name
            namespace = "APP_A_NAMESPACE"
          }
        }

        resource "kubernetes_deployment" "APP_A_DEPLOYMENT" {
          metadata {
            name      = "app-a"
            namespace = "APP_A_NAMESPACE"
          }

          spec {
            replicas = 2

            selector {
              match_labels = {
                app = "app-a"
              }
            }

            template {
              metadata {
                labels = {
                  app = "app-a"
                }
              }

              spec {
                service_account_name = kubernetes_service_account.APP_A_SA.metadata[0].name

                container {
                  name  = "app-a"
                  image = "APP_A_IMAGE" # replace with your image
                }
              }
            }
          }
        }
        ```

        Replace:

        * `MY_CLUSTER` / `MY_CLUSTER_KUBECONFIG` with your actual `oci_containerengine_cluster` and kubeconfig data source.
        * `APP_A_NAMESPACE` with the namespace of each workload.
        * `APP_A_IMAGE` with the container image for the app.

        No resource replacement of the OKE cluster is required; only workloads and their RBAC objects are created/updated.

        Verification: `terraform plan` should show new `kubernetes_service_account`, `kubernetes_role`, `kubernetes_role_binding`, and updated workload specs (e.g., Deployment) using `service_account_name` per workload, with no changes to `oci_containerengine_cluster`.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
