OCI OKE Should Limit Default Service Account Usage
More Info:
Workloads should not run under the namespaces default ServiceAccount. Mounting tokens from the default SA to every pod blurs blast-radius and breaks per-workload least privilege.
Risk Level
Medium
Address
Compliance, Security
Compliance Standards
- CIS OKE
Triage and Remediation
- Remediation
Remediation
Using Console
In OKE this is a Kubernetes-level issue, so you remediate it by changing service accounts and RBAC inside the cluster. You can start from the OCI Console, but the actual fix is done via kubectl (e.g., from OCI Cloud Shell).
Below are the steps doing everything initiated from the OCI Console.
1. Open Cloud Shell and connect to the OKE cluster
-
Sign in to the OCI Console.
-
Open the navigation menu → Developer Services → Kubernetes Clusters (OKE).
-
Select the Compartment and click your cluster.
-
On the cluster details page, click Access Cluster.
-
In the panel that opens, click Cloud Shell Access (or open Cloud Shell from the top-right “>_” icon and follow the displayed
oci ce cluster create-kubeconfigcommand). -
Run the suggested
oci ce cluster create-kubeconfigcommand in Cloud Shell, for example:oci ce cluster create-kubeconfig \--cluster-id <OCID_OF_CLUSTER> \--file $HOME/.kube/config \--region <region> \--token-version 2.0.0 \--kube-endpoint PRIVATE_ENDPOINT -
Verify access:
kubectl get nodes
2. Disable token auto-mount for the default service account
You need to patch the default service account in each namespace where workloads run. Commonly at least default and any custom namespaces.
-
List namespaces:
kubectl get ns -
For each relevant namespace (e.g.
default,production,staging), run:kubectl patch serviceaccount default \-n <namespace> \-p '{"automountServiceAccountToken": false}'Example for the default namespace:
kubectl patch serviceaccount default \-n default \-p '{"automountServiceAccountToken": false}'
This prevents pods in that namespace from automatically mounting the default token unless explicitly overridden.
3. Create dedicated, least-privilege service accounts
Instead of relying on the default service account, create service accounts per application with minimal RBAC.
-
Create a service account:
kubectl create serviceaccount my-app-sa -n <namespace> -
Define an RBAC role (example: read-only access to ConfigMaps in that namespace). Create a YAML file
role.yamlin Cloud Shell:apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata:name: my-app-readonlynamespace: <namespace>rules:- apiGroups: [""]resources: ["configmaps"]verbs: ["get", "list", "watch"]Apply it:
kubectl apply -f role.yaml -
Bind the role to the new service account. Create
rolebinding.yaml:apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:name: my-app-readonly-bindingnamespace: <namespace>subjects:- kind: ServiceAccountname: my-app-sanamespace: <namespace>roleRef:kind: Rolename: my-app-readonlyapiGroup: rbac.authorization.k8s.ioApply it:
kubectl apply -f rolebinding.yaml
4. Update workloads to stop using the default service account
For each deployment/statefulset/daemonset, explicitly set a non-default service account and (optionally) ensure auto-mount is disabled if not needed.
-
Edit the deployment (example):
kubectl edit deployment my-app -n <namespace> -
Under
spec.template.spec, add or change:spec:serviceAccountName: my-app-saautomountServiceAccountToken: false # set to true only if the pod truly needs the token -
Save and exit; Kubernetes will roll out the updated pods.
-
Verify:
kubectl get pods -n <namespace> -o jsonpath='{range .items[*]}{.metadata.name}{" => "}{.spec.serviceAccountName}{"\n"}{end}'
5. (Optional) Enforce this pattern for new namespaces
For each new namespace you create, immediately:
kubectl patch serviceaccount default \
-n <new-namespace> \
-p '{"automountServiceAccountToken": false}'
And always define explicit service accounts + RBAC for new applications.
If you share how your workloads are structured (namespaces, critical apps), I can give you concrete kubectl patches tailored to your setup.
Using CLI
You can’t directly change Kubernetes service accounts with oci itself; you use oci to get kubeconfig, then kubectl to do the remediation on the OKE cluster.
Below are the minimal CLI steps to limit default service account usage in OKE.
1. Get kubeconfig for your OKE cluster (with OCI CLI)
# Set variables
COMPARTMENT_OCID="<compartment_ocid>"
CLUSTER_OCID="<cluster_ocid>"
KUBECONFIG_PATH="$HOME/.kube/config"
# Generate kubeconfig
oci ce cluster create-kubeconfig \
--cluster-id "$CLUSTER_OCID" \
--file "$KUBECONFIG_PATH" \
--region "<region_identifier>" \
--token-version 2.0.0 \
--kube-endpoint PUBLIC_ENDPOINT
Verify:
kubectl get nodes
2. Disable token automount on the default service account
Run this for each namespace where you want to restrict the default SA (including default and any app namespaces):
NAMESPACE="default"
kubectl patch serviceaccount default \
-n "$NAMESPACE" \
-p '{"automountServiceAccountToken": false}'
Optional: verify
kubectl get sa default -n "$NAMESPACE" -o yaml | grep automountServiceAccountToken
3. Ensure workloads don’t use the default SA
For each Deployment/StatefulSet/DaemonSet, patch them to either:
Option A – Explicitly disable token mounting (if they don’t need K8s API access)
kubectl patch deployment <deployment_name> \
-n <namespace> \
--type merge \
-p '{"spec":{"template":{"spec":{"automountServiceAccountToken": false}}}}'
Option B – Use a dedicated least-privilege service account
-
Create a new service account:
kubectl create serviceaccount <sa_name> -n <namespace> -
Bind only needed permissions (example: read-only in namespace):
cat <<EOF | kubectl apply -f -apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata:name: <role_name>namespace: <namespace>rules:- apiGroups: [""]resources: ["pods"]verbs: ["get", "list", "watch"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:name: <rb_name>namespace: <namespace>subjects:- kind: ServiceAccountname: <sa_name>namespace: <namespace>roleRef:kind: Rolename: <role_name>apiGroup: rbac.authorization.k8s.ioEOF -
Patch the workload to use this SA:
kubectl patch deployment <deployment_name> \-n <namespace> \--type merge \-p '{"spec":{"template":{"spec":{"serviceAccountName":"<sa_name>","automountServiceAccountToken": true}}}}'
4. (Optional) Restrict the default service account’s RBAC rights
If the default service account already has bindings, remove or tighten them:
# List all RoleBindings/ClusterRoleBindings referencing default SA
kubectl get rolebindings,clusterrolebindings --all-namespaces -o yaml | \
grep -B5 -A5 "name: default"
Then delete or replace those bindings as appropriate, for example:
kubectl delete rolebinding <rb_name> -n <namespace>
kubectl delete clusterrolebinding <crb_name>
These steps, driven via OCI CLI → kubeconfig → kubectl, will effectively limit use and privileges of the default service account in your OKE cluster.
Using Python
Here’s how to remediate “OCI OKE Should Limit Default Service Account Usage” using Python and the Kubernetes API.
Goal
- Stop the
defaultServiceAccount from automatically getting tokens. - Detect workloads still using the
defaultServiceAccount so you can fix them.
1. Prerequisites
Install the Kubernetes Python client:
pip install kubernetes
Make sure your local kubectl can access the OKE cluster (e.g., via oci ce cluster create-kubeconfig ... and KUBECONFIG or ~/.kube/config).
2. Disable token auto-mounting for the default ServiceAccount in all namespaces
This script:
- Lists all namespaces
- Patches the
defaultServiceAccount in each namespace to setautomountServiceAccountToken: false
from kubernetes import client, config
from kubernetes.client.rest import ApiException
def disable_default_sa_automount():
# Load kubeconfig (adjust if running inside a pod)
config.load_kube_config()
v1 = client.CoreV1Api()
# Get all namespaces
namespaces = [ns.metadata.name for ns in v1.list_namespace().items]
for ns in namespaces:
print(f"Processing namespace: {ns}")
try:
# Build patch body
patch_body = {
"automountServiceAccountToken": False
}
# Patch the default ServiceAccount in this namespace
v1.patch_namespaced_service_account(
name="default",
namespace=ns,
body=patch_body
)
print(f" Patched default ServiceAccount in {ns}")
except ApiException as e:
if e.status == 404:
print(f" No default ServiceAccount found in {ns} (unexpected)")
else:
print(f" Failed to patch default SA in {ns}: {e}")
if __name__ == "__main__":
disable_default_sa_automount()
This makes the default service account safer (no token auto-mount), but it does not prevent workloads from explicitly specifying serviceAccountName: default or inheriting it.
3. Detect pods using the default ServiceAccount
You should also identify existing pods and controllers using the default SA so you can fix manifests (Deployments, StatefulSets, Jobs, etc.) to use dedicated service accounts.
from kubernetes import client, config
def find_pods_using_default_sa():
config.load_kube_config()
v1 = client.CoreV1Api()
pods = v1.list_pod_for_all_namespaces().items
print("Pods using the 'default' ServiceAccount:")
for pod in pods:
sa_name = pod.spec.service_account_name or "default"
if sa_name == "default":
print(f"- Namespace: {pod.metadata.namespace}, Pod: {pod.metadata.name}")
if __name__ == "__main__":
find_pods_using_default_sa()
Use this output to:
- Create dedicated service accounts with minimal RBAC.
- Update Deployment/Job/StatefulSet specs to use those service accounts.
- Redeploy workloads so new pods don’t run with
default.
4. (Optional) Enforce at Namespace Level
You can also set automountServiceAccountToken: false at the namespace level as a default:
from kubernetes import client, config
def set_namespace_default_automount_false():
config.load_kube_config()
v1 = client.CoreV1Api()
namespaces = [ns.metadata.name for ns in v1.list_namespace().items]
for ns in namespaces:
print(f"Processing namespace: {ns}")
patch_body = {
"metadata": {
"annotations": {
"kubernetes.io/automount-service-account-token": "false"
}
}
}
try:
v1.patch_namespace(name=ns, body=patch_body)
print(f" Patched namespace {ns}")
except Exception as e:
print(f" Failed to patch namespace {ns}: {e}")
if __name__ == "__main__":
set_namespace_default_automount_false()
Minimal remediation steps in OKE:
- Run the script to set
automountServiceAccountToken: falseon alldefaultServiceAccounts. - Identify pods using the
defaultSA and update their controllers to use dedicated service accounts with least-privilege RBAC. - (Optional) Annotate namespaces to default to no token auto-mount.
Using Terraform
# This cannot be remediated on oci_containerengine_cluster itself; it is a Kubernetes-level setting
# that must be applied inside the OKE cluster via the Kubernetes provider.
provider "kubernetes" {
# Configure this provider to talk to your OKE cluster (substitute as appropriate)
host = "https://OKE_API_ENDPOINT" # replace with your OKE API server endpoint
token = "KUBE_BEARER_TOKEN" # replace with a valid token
cluster_ca_certificate = file("PATH_TO_OKE_CLUSTER_CA_CERT.pem") # replace with the CA file path
}
# 1) Disable default ServiceAccount token mounting in a namespace
resource "kubernetes_service_account" "default_sa_patch" {
metadata {
name = "default"
namespace = "TARGET_NAMESPACE" # replace with the namespace you are hardening
}
automount_service_account_token = false
}
# 2) Create a dedicated ServiceAccount for a workload
resource "kubernetes_service_account" "app_sa" {
metadata {
name = "APP_SERVICE_ACCOUNT_NAME" # e.g., "payments-api-sa"
namespace = "TARGET_NAMESPACE"
}
automount_service_account_token = true
}
# 3) Example: ensure a deployment uses the dedicated ServiceAccount (not the default)
resource "kubernetes_deployment" "app" {
metadata {
name = "APP_DEPLOYMENT_NAME" # e.g., "payments-api"
namespace = "TARGET_NAMESPACE"
labels = {
app = "APP_LABEL"
}
}
spec {
replicas = 2
selector {
match_labels = {
app = "APP_LABEL"
}
}
template {
metadata {
labels = {
app = "APP_LABEL"
}
}
spec {
service_account_name = kubernetes_service_account.app_sa.metadata[0].name
container {
name = "APP_CONTAINER_NAME"
image = "APP_IMAGE_REF"
}
}
}
}
}
# No replacement of the OKE cluster resource is required; these are in-cluster changes only.
# Verification: `terraform plan` should show:
# - an in-place update for the `kubernetes_service_account.default_sa_patch` (creating or modifying the "default" SA)
# - creation of `kubernetes_service_account.app_sa`
# - creation or update of `kubernetes_deployment.app` to reference the non-default ServiceAccount.