Skip to main content

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

Remediation

Using Console

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:

    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:

    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:

    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:

    kubectl create namespace prod-apps
  3. Create a ServiceAccount YAML file in Cloud Shell (or locally), e.g. orders-sa.yaml:

    apiVersion: v1
    kind: ServiceAccount
    metadata:
    name: orders-sa
    namespace: prod-apps
  4. Apply it:

    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:

    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:

    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:

    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:

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:

kubectl apply -f orders-deployment.yaml

Verify the pods use the correct SA:

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:

      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.

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:

    oci ce cluster list \
    --compartment-id <COMPARTMENT_OCID> \
    --query "data[].{name:name, id:id}" \
    --output table
  2. Generate/update kubeconfig for that cluster:

    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:

    kubectl get nodes

2. Identify workloads using the default service account

Check each namespace (or target namespaces):

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:

kubectl create namespace prod # if not existing

kubectl create serviceaccount my-app-sa -n prod

Confirm:

kubectl get sa -n prod

Create a minimal Role and RoleBinding for that service account.

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

    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:

    kubectl apply -f my-app-role.yaml
  3. Create a RoleBinding (file: my-app-rolebinding.yaml):

    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:

    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:

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

Or edit directly:

kubectl edit deployment my-app-deployment -n prod

Add under spec.template.spec:

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

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):

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

pip install kubernetes

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

kubectl config use-context <oke-cluster-context>

The Python client will reuse this kubeconfig.


2. Detect workloads using the default service account

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:

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):

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

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

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.

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:

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