Skip to main content

OCI OKE Access to Secrets Should Be Restricted

More Info:

Roles that allow get, list, or watch on secrets effectively expose every credential they reference. Limit secret access to controllers and service accounts that strictly need it and audit changes to those bindings.

Risk Level

High

Address

Compliance, Security

Compliance Standards

  • CIS OKE

Triage and Remediation

Remediation

Using Console

Below are concrete, console-based steps to restrict access to OCI Vault secrets for OKE (Oracle Kubernetes Engine).

Assumptions:

  • OKE worker nodes use Dynamic Groups + IAM policies to access Vault.
  • The misconfiguration is that these policies are too broad (e.g., read secret-bundles on entire compartment/tenancy).

1. Identify who currently has access to secrets

  1. Sign in to OCI Console.
  2. Open the navigation menu → Identity & SecurityPolicies.
  3. In the Compartment dropdown, select the compartment where your OKE policies live.
  4. Find policies that contain actions like:
    • read secret-bundles
    • manage secret-family
    • manage vaults or read vaults
  5. Look for statements that reference:
    • dynamic-group <your-oke-nodes-dg>
    • or groups used by your platform/team for OKE access.

Examples of risky policies:

Allow dynamic-group oke-nodes to read secret-bundles in compartment app-compartment
Allow dynamic-group oke-nodes to manage secret-family in compartment app-compartment
Allow group devs to manage secret-family in tenancy

Write down:

  • The dynamic group names (e.g., oke-nodes).
  • The compartments and vaults they can access.

2. Confirm which dynamic groups are tied to your OKE clusters

  1. Go to Identity & SecurityDynamic Groups.
  2. Open each suspected dynamic group (e.g., oke-nodes).
  3. Check Matching Rules:
    • Typical OKE node rule looks like:
      ALL {instance.compartment.id = 'ocid1.compartment.oc1..xxxx'}
      or:
      ANY {instance.ocid = 'ocid1.instance.oc1..xxxx'}
  4. Confirm these are indeed the worker node instances (used by your OKE node pools).

3. Scope Vault access to the smallest necessary compartment/vault

If workers currently have wide access (e.g., whole compartment), narrow that scope.

A. Optionally move secrets into a dedicated “app-secrets” compartment

  1. Navigation menu → Identity & SecurityCompartments.
  2. Create a new compartment (e.g., app-secrets).
  3. Go to SecurityVault → select your Vault.
  4. For each secret that should be tightly controlled:
    • Click the secret → Move Resource (if available) → move it into app-secrets.
    • If “Move” is not available, create a new secret in app-secrets and start using that in your apps, then delete the old one when no longer needed.

This allows you to apply stricter policies only on app-secrets.


4. Replace broad IAM policies with least-privilege ones

A. Edit or create a new policy with narrow permissions

  1. Go to Identity & SecurityPolicies.

  2. Either:

    • Edit the existing broad policy (preferred), or
    • Create a new policy (e.g., oke-app-secrets-policy) in the root or relevant parent compartment.
  3. Use narrow statements such as:

Only allow OKE nodes to read secrets in one compartment:

Allow dynamic-group oke-nodes to read secret-bundles in compartment app-secrets

If you want to restrict to a single vault only (where conditions supported):

Allow dynamic-group oke-nodes to read secret-bundles in compartment app-secrets
where target.vault.id = 'ocid1.vault.oc1..xxxx'

If only some users need to manage secrets (not all devs):

Allow group secret-admins to manage secret-family in compartment app-secrets
  1. Remove or comment out (delete) existing overly broad rules, for example:
    • Allow dynamic-group oke-nodes to read secret-bundles in tenancy
    • Allow group devs to manage secret-family in tenancy
    • Allow dynamic-group oke-nodes to manage vaults in tenancy

Save the policy.


5. Restrict who can manage Vaults and Keys

  1. Go to SecurityVault.
  2. For each vault:
    • Note its compartment.
  3. Return to Identity & SecurityPolicies and ensure:
    • Only a small admin group (e.g., security-admins) can manage vaults and keys:
      Allow group security-admins to manage vaults in compartment security
      Allow group security-admins to manage keys in compartment security
    • Regular users or OKE node dynamic groups should not have manage vaults or manage keys.

6. Validate OKE access from a sample pod

  1. Apply a simple test pod/deployment in the OKE cluster that uses the OCI SDK or Secrets Store CSI Driver to read a specific secret.
  2. Ensure:
    • The pod can only read the secrets it is supposed to.
    • Attempts to read secrets outside app-secrets (or outside allowed vault) fail with NotAuthorizedOrNotFound.

If unauthorized access still works, revisit which policies grant access and further tighten them.


7. Ongoing hardening

  • Keep secrets in a separate secrets-only compartment per application or environment.
  • Avoid manage secret-family for dynamic groups; usually they only need read secret-bundles.
  • Periodically review:
    • Policies under Identity & Security.
    • Dynamic Groups associated with OKE nodes.
    • Audit logs (Identity & Security → Audit) for who is reading secrets.

If you can share the current policy text you’re using for OKE to read secrets, I can give you an exact minimal replacement policy statement.

Using CLI

Below is a practical, CLI-focused way to tighten access to OCI Vault secrets and Kubernetes Secrets in OKE.


1. Restrict OCI Vault Secret Access for OKE Nodes

1.1. Identify dynamic groups used by OKE

OKE worker nodes usually belong to a dynamic group. List dynamic groups:

oci iam dynamic-group list --compartment-id <TENANCY_OCID>

Inspect each:

oci iam dynamic-group get --dynamic-group-id <DYNAMIC_GROUP_OCID>

You’re looking for rules like:

ALL {resource.type = 'instance', tag.oke-cluster.id = '<cluster-id>'}

That’s likely your OKE worker node dynamic group.


1.2. List policies that grant vault/secret access

List policies (tenant‑wide or per compartment):

oci iam policy list --compartment-id <COMPARTMENT_OCID>

For each policy:

oci iam policy get --policy-id <POLICY_OCID>

Look for broad statements involving secret-bundles or vaults, e.g.:

Allow dynamic-group <dg-name> to read secret-bundles in compartment <compartment-name>
Allow dynamic-group <dg-name> to manage vaults in tenancy

These are what you will tighten.


1.3. Replace broad policies with least‑privilege ones

a) Narrow to specific compartment

If currently:

Allow dynamic-group <dg-name> to read secret-bundles in tenancy

Replace with:

Allow dynamic-group <dg-name> to read secret-bundles in compartment <compartment-name>

Update with CLI:

  1. Get existing statements into a JSON file:
oci iam policy get --policy-id <POLICY_OCID> \
--query 'data."statements"' --raw-output > statements.json
  1. Edit statements.json locally:

    • Remove overly broad lines
    • Add the narrowed lines you want.
  2. Update the policy:

oci iam policy update \
--policy-id <POLICY_OCID> \
--statements file://statements.json \
--description "Tightened OKE secret access"

b) Limit to specific vault or secret (optional, more strict)

Use a policy with conditions (if you want only specific secret(s)):

Example (per secret):

Allow dynamic-group <dg-name> to read secret-bundles in compartment <compartment-name>
where target.secret.id = '<SECRET_OCID>'

Or per vault:

Allow dynamic-group <dg-name> to read secret-bundles in compartment <compartment-name>
where target.vault.id = '<VAULT_OCID>'

You still use the same oci iam policy update procedure, but with the conditioned statements.


1.4. Remove unneeded manage permissions

If you see:

Allow dynamic-group <dg-name> to manage secret-family in compartment <compartment-name>
Allow dynamic-group <dg-name> to manage vaults in compartment <compartment-name>

Change manage to read for worker nodes:

Allow dynamic-group <dg-name> to read secret-bundles in compartment <compartment-name>

Update via the same statements.json + oci iam policy update method.


2. Restrict Kubernetes Secret Access Inside OKE

OCI CLI is used to configure kubeconfig, then kubectl enforces RBAC.

2.1. Get kubeconfig via OCI CLI

oci ce cluster create-kubeconfig \
--cluster-id <OKE_CLUSTER_OCID> \
--file $HOME/.kube/config \
--region <REGION> \
--token-version 2.0.0 \
--kube-endpoint PUBLIC_ENDPOINT

Now kubectl uses that config.


2.2. Audit who can read secrets

List ClusterRoles and RoleBindings:

kubectl get clusterroles
kubectl get clusterrolebindings
kubectl get roles -A
kubectl get rolebindings -A

Find any with secrets and broad verbs:

kubectl get clusterrole -o yaml | grep -A5 "secrets"
kubectl get role -A -o yaml | grep -A5 "secrets"

Look for things like:

resources:
- secrets
verbs:
- get
- list
- watch
- update
- delete
- create
- '*'

Especially on system:authenticated, system:serviceaccounts, etc.


2.3. Remove or tighten overly broad bindings

Example: if a ClusterRoleBinding gives all service accounts cluster‑wide access to secrets:

kubectl get clusterrolebinding secret-full-access -o yaml

If it is not needed, delete:

kubectl delete clusterrolebinding secret-full-access

Or rebind it only to specific service account(s):

  1. Create a YAML file (e.g. restricted-sa-binding.yaml):
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: namespace-secret-reader
namespace: target-namespace
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: namespace-secret-reader-binding
namespace: target-namespace
subjects:
- kind: ServiceAccount
name: app-sa
namespace: target-namespace
roleRef:
kind: Role
name: namespace-secret-reader
apiGroup: rbac.authorization.k8s.io
  1. Apply:
kubectl apply -f restricted-sa-binding.yaml
  1. Remove any old RoleBindings/ClusterRoleBindings that granted wider access than necessary.

2.4. Avoid mounting all secrets as environment variables

Check Deployments:

kubectl get deploy -A -o yaml | grep -A10 "envFrom"

Prefer specific secret keys instead of whole-secret or whole-namespace access.


3. Summary of CLI Commands Used

  • IAM / Vault via OCI CLI:
    • oci iam dynamic-group list/get
    • oci iam policy list/get/update
  • OKE / Kubernetes:
    • oci ce cluster create-kubeconfig
    • Then kubectl for RBAC and secret usage.

If you share an example of your current IAM policy and dynamic group for OKE, I can give you an exact statements.json you can paste into oci iam policy update.

Using Python

Below is a practical, step‑by‑step way to restrict access to Kubernetes Secrets in OCI OKE using Python.
This focuses on tightening Kubernetes RBAC (what workloads and users can get/list/watch secrets).


1. Prerequisites

  1. An OKE cluster is already created.
  2. kubeconfig for the cluster is available (e.g. ~/.kube/config).
  3. Python packages installed:
    pip install kubernetes oci
  4. Your local environment can talk to the OKE API (e.g., kubectl get pods works).

2. Principle: Least-Privilege RBAC on Secrets

You want:

  • Only specific service accounts / users / groups to:
    • get, list, watch, create, update, delete Secrets.
  • All others should have no secret permissions (or only what is strictly required).

We’ll do this by:

  1. Auditing current bindings.
  2. Creating minimal Role / ClusterRole definitions.
  3. Adjusting RoleBinding / ClusterRoleBinding objects via Python.

3. Configure Kubernetes Python Client

from kubernetes import client, config

# Use default kubeconfig
config.load_kube_config() # or config.load_incluster_config() if running inside the cluster

rbac_api = client.RbacAuthorizationV1Api()
core_api = client.CoreV1Api()

4. Audit Current Secret Access

List all ClusterRoles and Roles that touch secrets:

from kubernetes import client, config

config.load_kube_config()
rbac_api = client.RbacAuthorizationV1Api()

def roles_with_secret_access():
roles_with_access = []

# ClusterRoles
cr_list = rbac_api.list_cluster_role()
for cr in cr_list.items:
for rule in cr.rules or []:
if 'secrets' in (rule.resources or []):
roles_with_access.append(("ClusterRole", cr.metadata.name, rule))

# Namespaced Roles
ns_list = client.CoreV1Api().list_namespace()
for ns in ns_list.items:
role_list = rbac_api.list_namespaced_role(namespace=ns.metadata.name)
for role in role_list.items:
for rule in role.rules or []:
if 'secrets' in (rule.resources or []):
roles_with_access.append(("Role", f"{ns.metadata.name}/{role.metadata.name}", rule))

return roles_with_access

for kind, name, rule in roles_with_secret_access():
print(f"{kind} {name} -> verbs={rule.verbs}, resources={rule.resources}, apiGroups={rule.api_groups}")

Use this to identify overly permissive roles (e.g., * verbs on secrets, or broad ClusterRole bindings).


5. Create a Least‑Privilege Role for Secrets (Namespace Scoped)

Example: allow only a specific service account to read secrets in namespace prod.

5.1 Create Role

from kubernetes.client import V1ObjectMeta, V1PolicyRule, V1Role

namespace = "prod"
role_name = "secret-reader"

role_body = V1Role(
metadata=V1ObjectMeta(name=role_name, namespace=namespace),
rules=[
V1PolicyRule(
api_groups=[""],
resources=["secrets"],
verbs=["get", "list"] # minimal; add 'watch' if truly needed
)
]
)

rbac_api = client.RbacAuthorizationV1Api()
created_role = rbac_api.create_namespaced_role(namespace=namespace, body=role_body)
print("Created Role:", created_role.metadata.name)

5.2 Bind Role to a Service Account

from kubernetes.client import V1RoleBinding, V1RoleRef, V1Subject

sa_name = "app-sa" # service account allowed to read secrets

role_binding_body = V1RoleBinding(
metadata=V1ObjectMeta(name="secret-reader-binding", namespace=namespace),
subjects=[
V1Subject(
kind="ServiceAccount",
name=sa_name,
namespace=namespace
)
],
role_ref=V1RoleRef(
api_group="rbac.authorization.k8s.io",
kind="Role",
name=role_name
)
)

created_rb = rbac_api.create_namespaced_role_binding(namespace=namespace, body=role_binding_body)
print("Created RoleBinding:", created_rb.metadata.name)

Now only app-sa in prod has get/list for secrets in that namespace.


6. Remove / Restrict Broad Secret Permissions

Typical bad pattern: ClusterRoleBinding to system:authenticated or system:serviceaccounts with a role that grants secrets access.

  1. Identify dangerous bindings:
crb_list = rbac_api.list_cluster_role_binding()
for crb in crb_list.items:
for subj in crb.subjects or []:
if subj.kind in ["User", "Group", "ServiceAccount"]:
# heuristic: check widely-scoped subjects
if subj.name in ["system:authenticated", "system:serviceaccounts"]:
print("Potentially dangerous CRB:", crb.metadata.name)
  1. Inspect the ClusterRole they refer to. If it has secrets permissions and is too broad, either:
    • Create new, narrower ClusterRoles and rebind.
    • Or patch/remove the binding if not needed.

Example: Patch a ClusterRole to Remove Secrets Access

from kubernetes.client import V1ClusterRole

cr_name = "example-cluster-role"
cr = rbac_api.read_cluster_role(name=cr_name)

new_rules = []
for rule in cr.rules:
# Remove 'secrets' from resources list
if "secrets" in (rule.resources or []):
filtered_resources = [r for r in rule.resources if r != "secrets"]
if filtered_resources:
rule.resources = filtered_resources
new_rules.append(rule) # keep rule, just without secrets
# else drop the rule entirely
else:
new_rules.append(rule)

cr.rules = new_rules

updated_cr = rbac_api.replace_cluster_role(name=cr_name, body=cr)
print("Updated ClusterRole:", updated_cr.metadata.name)

Be careful: do this only after verifying no critical workloads rely on that access.


7. Use Dedicated Service Accounts per App

For each deployment needing secret access:

  1. Create a dedicated service account.
  2. Bind only the minimal secret permissions to that SA.

Example: ensure a Deployment uses the app-sa:

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: prod
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
serviceAccountName: app-sa
containers:
- name: app
image: my-image:latest

You can apply this YAML via Python using the Kubernetes client if desired, but usually kubectl apply -f is simpler.


8. (Optional) Move Sensitive Data to OCI Vault and Narrow Access

For especially sensitive secrets:

  1. Store them in OCI Vault (KMS).
  2. Grant only needed OKE node pool instances / dynamic groups permissions to read those secrets via IAM policies.
  3. App accesses Vault with the OCI SDK from inside the pod instead of Kubernetes Secrets.

Python snippet (OCI Vault read example – high level):

import oci

config = oci.config.from_file() # or instance principal config
secrets_client = oci.secrets.SecretsClient(config)

secret_ocid = "ocid1.vaultsecret.oc1..xxxx"
response = secrets_client.get_secret_bundle(secret_id=secret_ocid)
secret_content = response.data.secret_bundle_content.content
# base64 decode if needed depending on contentType

Then your pod uses this method instead of relying on Kubernetes Secret objects.


9. Verification

  1. Attempt to list secrets with a service account or user that should not have access; verify it fails:
    kubectl auth can-i list secrets --as=system:serviceaccount:prod:unauthorized-sa -n prod
    # should print "no"
  2. Verify permitted SA works:
    kubectl auth can-i get secrets --as=system:serviceaccount:prod:app-sa -n prod
    # should print "yes"

If you share your current RBAC definitions (or an example of a failing security check), I can give a concrete Python script tailored to that configuration.

Using Terraform
# OKE cluster (for context)
resource "oci_containerengine_cluster" "OKE_CLUSTER" {
name = "OKE_CLUSTER_NAME"
compartment_id = "COMPARTMENT_OCID"
vcn_id = "VCN_OCID"

kubernetes_version = "K8S_VERSION"

endpoint_config {
is_public_ip_enabled = true
subnet_id = "ENDPOINT_SUBNET_OCID"
}
}

# Kubernetes provider configured to talk to the OKE cluster
# Substitute the placeholders with values from the OKE cluster's kubeconfig.
provider "kubernetes" {
host = "https://OKE_API_ENDPOINT_URL"
cluster_ca_certificate = file("PATH_TO_OKE_CA_CERT_PEM")
token = "OKE_BEARER_TOKEN"
}

# Restrict secret access to only the specific service account that needs it
# (instead of broad roles/bindings like system:authenticated, system:serviceaccounts, etc.)

resource "kubernetes_cluster_role" "restricted_secrets_reader" {
metadata {
name = "restricted-secrets-reader"
}

rule {
api_groups = [""]
resources = ["secrets"]
verbs = ["get", "list", "watch"]
}
}

resource "kubernetes_cluster_role_binding" "restricted_secrets_reader_binding" {
metadata {
name = "restricted-secrets-reader-binding"
}

role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = kubernetes_cluster_role.restricted_secrets_reader.metadata[0].name
}

subject {
kind = "ServiceAccount"
name = "SERVICE_ACCOUNT_NAME" # replace with the service account that needs secret access
namespace = "SERVICE_ACCOUNT_NS" # replace with the namespace of that service account
}
}

# (Optional) Example of a namespace‑scoped Role instead of a ClusterRole
resource "kubernetes_role" "namespace_secrets_reader" {
metadata {
name = "namespace-secrets-reader"
namespace = "TARGET_NAMESPACE" # replace with namespace where secrets live
}

rule {
api_groups = [""]
resources = ["secrets"]
verbs = ["get", "list", "watch"]
}
}

resource "kubernetes_role_binding" "namespace_secrets_reader_binding" {
metadata {
name = "namespace-secrets-reader-binding"
namespace = "TARGET_NAMESPACE" # same as above
}

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

subject {
kind = "ServiceAccount"
name = "SERVICE_ACCOUNT_NAME" # same service account as above or another strictly‑scoped one
namespace = "SERVICE_ACCOUNT_NS"
}
}

This change does not replace the oci_containerengine_cluster itself; it only adds/updates Kubernetes RBAC objects on the existing OKE cluster.

To verify, terraform plan should show the kubernetes_cluster_role, kubernetes_cluster_role_binding, and (if used) kubernetes_role/kubernetes_role_binding being created or updated, with no destroy/replace on oci_containerengine_cluster.OKE_CLUSTER.