This page targets OKE Enhanced Cluster as the baseline. Basic clusters silently lack three critical security capabilities — Workload Identity (oci-k8s-02), image verification policy (oci-k8s-05), and add-on lifecycle management (covered in oci-k8s-04). Auditors evaluating an OCI Kubernetes deployment against this guide should first confirm the cluster type is ENHANCED_CLUSTER (not BASIC_CLUSTER) before applying the remaining controls. See general/kubernetes.html for the cross-cutting threat model and cluster-baseline principles that apply to all providers.
Enhanced-only controls — Workload Identity (oci-k8s-02), image verification policy (oci-k8s-05), and the Enhanced-scoped variants of Vault CMK encryption (oci-k8s-03) and NSG-based segmentation (oci-k8s-06) — carry an inline <div class="callout-warning">Requires Enhanced OKE cluster</div> annotation so an auditor can flag them on a Basic deployment. Control oci-k8s-04 is the explicit Enhanced Cluster gate: if it is RED, every other Enhanced-only control on this page is implicitly RED regardless of any other configuration. Supporting IAM prerequisites are on oci/iam.html; VCN networking prerequisites are on oci/network.html; OCI Audit + Logging sink configuration is on oci/logging.html.
Terraform examples use oracle/oci ~> 6.0 as the page pin. The sealed v1.0 OCI pages use ~> 5.0 — do not edit those pages. No HashiCorp-namespaced OCI provider exists — the OCI provider has always been published under oracle/oci. (Authoring-time verification: the 6.x line of oracle/oci includes both oci_containerengine_cluster.type = "ENHANCED_CLUSTER" and the OKE Workload Identity annotation surface needed by oci-k8s-02; no upgrade to ~> 8.0 was required.)
oci-k8s-01!CRITICALPREVENTIVE
Enhanced Cluster (default for this page): private API endpoint is enabled via endpoint_config.is_public_ip_enabled = false with an NSG attached to the API endpoint subnet.
Basic Cluster: a private API endpoint is also supported, but the absence of Enhanced-only controls (Workload Identity, image verification) means the cluster fails the page baseline regardless of this control's status.
Disable the public IP on the Kubernetes API endpoint so the kube-apiserver is reachable only from authorized subnets via Network Security Group rules. Use private DNS for API resolution and bastion-mediated access for human operators. A public OKE control-plane endpoint is the number-one credential-leak blast amplifier — any leaked kubeconfig becomes immediately exploitable from the internet without network-level checks.
# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
--compartment-id "$COMPARTMENT_OCID" \
--config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
--repository-url "https://example.com/org/hardening-iac" \
--branch-name "main" \
--working-directory "modules/oci-k8s-01-private-api-endpoint" \
--display-name "oci-k8s-01-private-api-endpoint" \
--terraform-version "1.5.x"
# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
--compartment-id "$COMPARTMENT_OCID" \
--display-name "oci-k8s-01-private-api-endpoint" \
--query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
--stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
--stack-id "$STACK_OCID" \
--execution-plan-strategy FROM_PLAN_JOB \
--execution-plan-job-id "$PLAN_JOB_OCID"
Remediation — Pulumi (TypeScript)
import * as pulumi from "@pulumi/pulumi";
import * as oci from "@pulumi/oci";
// OKE cluster with PRIVATE API endpoint (no public Kubernetes API surface).
const cfg = new pulumi.Config();
const compartmentId = cfg.require("compartmentOcid");
const vcnOcid = cfg.require("vcnOcid");
const privateApiSubnetOcid = cfg.require("privateApiSubnetOcid");
const apiNsgOcid = cfg.require("apiNsgOcid");
const cluster = new oci.containerengine.Cluster("hardened-oke", {
compartmentId: compartmentId,
vcnId: vcnOcid,
kubernetesVersion: "v1.30.1",
name: "hardened-oke",
type: "ENHANCED_CLUSTER", // enables more controls (audit, addons)
endpointConfig: {
subnetId: privateApiSubnetOcid,
isPublicIpEnabled: false, // PRIVATE endpoint
nsgIds: [apiNsgOcid], // restrict to bastion/jumpbox NSG
},
options: {
serviceLbSubnetIds: [privateApiSubnetOcid],
admissionControllerOptions: {
isPodSecurityPolicyEnabled: false, // PSP deprecated — Pod Security Standards via OPA-Gatekeeper
},
},
});
export const clusterOcid = cluster.id;
Compliance mapping
Control
Severity
Type
Provider
CIS Kubernetes Benchmark v1.11.0
CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0
NIST SP 800-53 rev5
ISO/IEC 27001:2022
ISO/IEC 27017:2015
NIST SP 800-190 (Sep 2017)
NSA/CISA Kubernetes Hardening Guide v1.2
oci-k8s-01
CRITICAL
PREVENTIVE
OCI OKE
n/a (managed control plane)
n/a (verify against CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0 PDF)
OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'containerengine' with eventName = 'UpdateCluster' whose request payload flips endpointConfig.isPublicIpEnabled from false to true.
Cluster create events landing an OKE control-plane endpoint inside a subnet whose route table contains an Internet Gateway target.
OKE API-server kube-apiserver audit deltas indicating sustained authentication attempts originating from public IP ranges outside the documented bastion CIDR.
Query
'Log Source' = 'OCI Audit Logs'
and 'Service Name' = 'containerengine'
and eventName in ('CreateCluster', 'UpdateCluster')
| eval public_api = data.request.payload.endpointConfig.isPublicIpEnabled
| where public_api = 'true'
| stats count by 'User Name', data.target.cluster.id, 'Compartment Name'
The OKE control-plane visibility surface is the single most expensive misconfiguration on the cluster — saving this search at 5-minute cadence is the recommended baseline.
Alert threshold
Any UpdateCluster turning the public API endpoint on — page; this is a regression of the cluster's network exposure posture.
Any new cluster created with isPublicIpEnabled = true outside a documented green-field engineering exception — page.
Initial response
Re-apply the Terraform stack via Resource Manager to flip endpoint_config.is_public_ip_enabled back to false; OKE accepts the change online.
Audit OKE API-server access from the period when the endpoint was public via the 'kube-apiserver' audit feed; identify any non-bastion principals.
Rotate the cluster's kubeconfig admin tokens and any service-account tokens older than the public-exposure window per general/ir.html.
Enhanced Cluster: OKE Workload Identity issues federated principals to pods via a ServiceAccount annotation, so each workload authenticates to OCI services with its own scoped identity.
Basic Cluster: Workload Identity is NOT available — pods fall back to the node's instance principal (node-scoped IAM), which violates least-privilege because every pod on a node inherits the same permissions.
Enable OKE Workload Identity so a Kubernetes ServiceAccount authenticates to OCI services using a federated principal rather than sharing the node's instance principal. The cluster identity provider issues a short-lived token bound to the pod's ServiceAccount; OCI IAM policies can then grant permissions to the specific workload identity (cluster <cluster-ocid> + ServiceAccount) instead of to the entire node pool.
# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
--compartment-id "$COMPARTMENT_OCID" \
--config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
--repository-url "https://example.com/org/hardening-iac" \
--branch-name "main" \
--working-directory "modules/oci-k8s-02-workload-identity" \
--display-name "oci-k8s-02-workload-identity" \
--terraform-version "1.5.x"
# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
--compartment-id "$COMPARTMENT_OCID" \
--display-name "oci-k8s-02-workload-identity" \
--query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
--stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
--stack-id "$STACK_OCID" \
--execution-plan-strategy FROM_PLAN_JOB \
--execution-plan-job-id "$PLAN_JOB_OCID"
Compliance mapping
Control
Severity
Type
Provider
CIS Kubernetes Benchmark v1.11.0
CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0
NIST SP 800-53 rev5
ISO/IEC 27001:2022
ISO/IEC 27017:2015
NIST SP 800-190 (Sep 2017)
NSA/CISA Kubernetes Hardening Guide v1.2
oci-k8s-02
HIGH
PREVENTIVE
OCI OKE
n/a (provider-specific identity federation)
n/a (verify against CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0 PDF)
IA-2; AC-6; IA-5
A.5.15; A.5.18
n/a
NIST SP 800-190 §4.4.2
NSA/CISA Kubernetes Hardening Guide v1.2 §4 (Authentication and authorization)
Log signals
OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and eventName = 'UpdateCluster' whose payload removes or nulls the workloadIdentityConfig block.
Pods inside OKE clusters carrying static OCI API signing keys mounted as Kubernetes Secrets — anti-pattern detectable via Cloud Guard OCIK8SExposedSecret detector.
Kubernetes ServiceAccount mutations adding the oci.oraclecloud.com/workload-identity annotation pointing to a dynamic group outside the cluster's documented allow-list.
Query
'Log Source' = 'OCI Audit Logs'
and 'Service Name' = 'containerengine'
and eventName = 'UpdateCluster'
| eval wi_disabled = if(data.request.payload.workloadIdentityConfig.workloadIdentityEnabled = 'false', 'YES', 'NO')
| where wi_disabled = 'YES'
| stats count by 'User Name', data.target.cluster.id
Pair with a Cloud Guard custom detector recipe rule on the same JMESPath; the Cloud Guard finding fans out to the OKE platform team's Notifications topic.
Alert threshold
Any UpdateCluster turning workload-identity off on a cluster previously running with it on — page; this strips per-pod IAM scoping.
A ServiceAccount annotation that points to a dynamic group outside the documented OKE-bound allow-list — page.
Initial response
Re-enable workload identity on the cluster via Resource Manager terraform apply; OKE accepts the toggle online without node-pool disruption.
Audit the pods running during the disabled window for any that fell back to baked-in API keys; rotate those signing keys against OCI Vault.
Restore the supported per-pod dynamic-group binding and reconcile any drifted ServiceAccount annotations from the cluster Git state.
Enhanced Cluster: customer-managed key (CMK) envelope encryption for Kubernetes Secrets stored in OKE-managed etcd via OCI Vault. The cluster references the key OCID at create time through kms_key_id.
Basic Cluster: CMK encryption is technically configurable but the full Enhanced-scoped key-rotation and audit integration depends on Enhanced Cluster features; treat as Enhanced-only for this guide.
Enable customer-managed key encryption for Kubernetes Secrets in OKE-managed etcd. Create a Vault and a Master Encryption Key (HSM or software protection mode); reference the key OCID on the cluster. Restrict access to the key with a vault-id-bound IAM condition so only the OKE cluster identity and break-glass administrators can use it.
# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
--compartment-id "$COMPARTMENT_OCID" \
--config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
--repository-url "https://example.com/org/hardening-iac" \
--branch-name "main" \
--working-directory "modules/oci-k8s-03-vault-secrets-encryption" \
--display-name "oci-k8s-03-vault-secrets-encryption" \
--terraform-version "1.5.x"
# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
--compartment-id "$COMPARTMENT_OCID" \
--display-name "oci-k8s-03-vault-secrets-encryption" \
--query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
--stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
--stack-id "$STACK_OCID" \
--execution-plan-strategy FROM_PLAN_JOB \
--execution-plan-job-id "$PLAN_JOB_OCID"
Compliance mapping
Control
Severity
Type
Provider
CIS Kubernetes Benchmark v1.11.0
CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0
NIST SP 800-53 rev5
ISO/IEC 27001:2022
ISO/IEC 27017:2015
NIST SP 800-190 (Sep 2017)
NSA/CISA Kubernetes Hardening Guide v1.2
oci-k8s-03
HIGH
PREVENTIVE
OCI OKE
§1.2 (etcd encryption — managed control plane)
n/a (verify against CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0 PDF)
OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and eventName = 'UpdateCluster' whose request payload removes or nulls kmsKeyId on the OKE etcd-encryption configuration.
Cluster create events where kmsKeyId is unset, indicating etcd at-rest encryption falls back to the Oracle-managed default rather than a customer Vault key.
Vault audit entries showing ScheduleKeyDeletion against a key actively bound to one or more OKE clusters.
Query
'Log Source' = 'OCI Audit Logs'
and 'Service Name' = 'containerengine'
and eventName in ('CreateCluster', 'UpdateCluster')
| eval key = data.request.payload.kmsKeyId
| where key is null
| stats count by 'User Name', data.target.cluster.id, 'Compartment Name'
Correlate the result with the kms service event feed for ScheduleKeyDeletion against the cluster-bound key OCID inventory.
Alert threshold
Any new or updated cluster with kmsKeyId null — page; etcd at rest must hold a customer-controlled key for tenancy-wide BYOK posture.
Any ScheduleKeyDeletion on a key OCID present in the OKE cluster inventory — page within the Vault key's pending-deletion window.
Initial response
Re-bind the cluster to its customer-controlled Vault key via Resource Manager; OKE re-wraps the etcd DEK on the next reconciliation cycle without data movement.
Cancel any pending ScheduleKeyDeletion targeting bound keys using oci kms management key cancel-key-deletion.
Confirm the cluster's etcd-encryption status reaches ACTIVE with the expected kmsKeyId via oci ce cluster get; document the rollback per general/ir.html.
Enhanced Cluster (REQUIRED for this guide): create with type = "ENHANCED_CLUSTER". Enhanced unlocks Workload Identity (oci-k8s-02), image verification policy (oci-k8s-05), full NSG attachment surface (oci-k8s-06), and OKE-managed add-on lifecycle (CoreDNS, kube-proxy, OCI VCN-Native Pod Networking).
Basic Cluster: silently lacks the controls above. Migration from Basic to Enhanced is supported via in-place upgrade.
This is the explicit Enhanced Cluster gate control for the page. Deploy OKE with type = "ENHANCED_CLUSTER" to access the full security capability surface. Enhanced also enables OKE-managed add-on lifecycle — CoreDNS, kube-proxy, and OCI VCN-Native Pod Networking can be installed and version-managed via the oci_containerengine_addon resource, addressing the add-on lifecycle requirement (REQ OKE-10). Node OS hardening is a related concern handled in oci-k8s-09; reference that control for Oracle Linux 8 minimal images and private node pool placement.
# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
--compartment-id "$COMPARTMENT_OCID" \
--config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
--repository-url "https://example.com/org/hardening-iac" \
--branch-name "main" \
--working-directory "modules/oci-k8s-04-enhanced-cluster" \
--display-name "oci-k8s-04-enhanced-cluster" \
--terraform-version "1.5.x"
# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
--compartment-id "$COMPARTMENT_OCID" \
--display-name "oci-k8s-04-enhanced-cluster" \
--query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
--stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
--stack-id "$STACK_OCID" \
--execution-plan-strategy FROM_PLAN_JOB \
--execution-plan-job-id "$PLAN_JOB_OCID"
Remediation — Pulumi (TypeScript)
import * as pulumi from "@pulumi/pulumi";
import * as oci from "@pulumi/oci";
// ENHANCED_CLUSTER type — required for cluster add-ons (Calico, Kyverno, Audit-to-Logging).
const cfg = new pulumi.Config();
const compartmentId = cfg.require("compartmentOcid");
const vcnOcid = cfg.require("vcnOcid");
const apiSubnetOcid = cfg.require("apiSubnetOcid");
const enhancedCluster = new oci.containerengine.Cluster("oke-enhanced", {
compartmentId: compartmentId,
vcnId: vcnOcid,
kubernetesVersion: "v1.30.1",
name: "oke-enhanced",
type: "ENHANCED_CLUSTER", // BASIC_CLUSTER lacks add-on framework + per-cluster SLA
endpointConfig: {
subnetId: apiSubnetOcid,
isPublicIpEnabled: false,
},
clusterPodNetworkOptions: [{
cniType: "OCI_VCN_IP_NATIVE", // native VCN CNI — required for NSG-per-pod
}],
});
// Pin add-ons explicitly so version drift is impossible.
const calicoAddon = new oci.containerengine.Addon("calico", {
clusterId: enhancedCluster.id,
addonName: "Calico",
removeAddonResourcesOnDelete: false,
configurations: [{ key: "numOfReplicas", value: "1" }],
});
export const clusterOcid = enhancedCluster.id;
Compliance mapping
Control
Severity
Type
Provider
CIS Kubernetes Benchmark v1.11.0
CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0
NIST SP 800-53 rev5
ISO/IEC 27001:2022
ISO/IEC 27017:2015
NIST SP 800-190 (Sep 2017)
NSA/CISA Kubernetes Hardening Guide v1.2
oci-k8s-04
CRITICAL
PREVENTIVE
OCI OKE
n/a (provider-specific cluster tier)
n/a (verify against CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0 PDF)
OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and eventName = 'UpdateCluster' whose payload sets type to BASIC on a cluster previously running as ENHANCED.
OKE Cluster Add-On disable events (DisableAddon) on Enhanced-only managed add-ons (CertManager, ClusterAutoscaler, Database operator), indicating loss of managed-lifecycle coverage.
OKE service-limit notifications signalling drift in node-pool count beyond the BASIC tier ceiling — a downgrade tells.
Query
'Log Source' = 'OCI Audit Logs'
and 'Service Name' = 'containerengine'
and eventName = 'UpdateCluster'
| eval tier = data.request.payload.type
| where tier = 'BASIC'
| stats count by 'User Name', data.target.cluster.id, 'Compartment Name'
Run hourly; a tier downgrade is a planned, ticketed activity, so a non-zero count outside the maintenance calendar is anomalous.
Alert threshold
Any cluster type transition from ENHANCED to BASIC — page; this strips managed add-on lifecycle and per-node-pool customisation.
Disable events targeting any Oracle-managed add-on listed in the cluster's documented allow-list — open a ticket on the platform team.
Initial response
Promote the cluster back to ENHANCED via oci ce cluster update --type ENHANCED; the upgrade is non-destructive and online.
Re-enable any Oracle-managed add-ons that were disabled during the downgrade window via the Resource Manager state; reconcile any custom add-on configuration that lapsed.
Document the rollback per general/ir.html and update the OKE tier inventory dashboard.
Enhanced Cluster: configure image_policy_config with up to 5 KMS keys for signed-image enforcement at pod scheduling time. Pods whose images are not signed by a referenced key are rejected by admission.
Basic Cluster: image verification policy is NOT available — any image the node can pull will run.
Enable sign-required image admission: pods are blocked from scheduling unless their container image is signed by an attested KMS key. Up to 5 KMS keys can be referenced per cluster, supporting trust hierarchies (per-team build pipelines plus a central security override key). Combine with OCIR repository policies that require signatures on push to close the supply-chain loop.
oci ce cluster update \
--cluster-id <CLUSTER-OCID> \
--image-policy-config 'isPolicyEnabled=true,keyDetails=[{kmsKeyId=<KMS-KEY-OCID>}]'
Remediation — OCI Resource Manager
# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
--compartment-id "$COMPARTMENT_OCID" \
--config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
--repository-url "https://example.com/org/hardening-iac" \
--branch-name "main" \
--working-directory "modules/oci-k8s-05-image-verification-policy" \
--display-name "oci-k8s-05-image-verification-policy" \
--terraform-version "1.5.x"
# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
--compartment-id "$COMPARTMENT_OCID" \
--display-name "oci-k8s-05-image-verification-policy" \
--query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
--stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
--stack-id "$STACK_OCID" \
--execution-plan-strategy FROM_PLAN_JOB \
--execution-plan-job-id "$PLAN_JOB_OCID"
Compliance mapping
Control
Severity
Type
Provider
CIS Kubernetes Benchmark v1.11.0
CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0
NIST SP 800-53 rev5
ISO/IEC 27001:2022
ISO/IEC 27017:2015
NIST SP 800-190 (Sep 2017)
NSA/CISA Kubernetes Hardening Guide v1.2
oci-k8s-05
HIGH
PREVENTIVE
OCI OKE
n/a (provider-specific admission)
n/a (verify against CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0 PDF)
OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'containerengine' with eventName = 'UpdateImagePolicyConfig' turning verification off on an OKE cluster.
OCIR signing-verification failures emitted as Cloud Guard problems where image pull would have been blocked had the policy remained on.
Kubernetes pod admission events admitting an image whose digest is absent from the OCIR signature index — surface via Cloud Guard OCIK8SUnsignedImage finding type.
Query
'Log Source' = 'OCI Audit Logs'
and 'Service Name' = 'containerengine'
and eventName in ('UpdateImagePolicyConfig', 'CreateImagePolicyConfig')
| eval enabled = data.request.payload.imagePolicyConfig.isPolicyEnabled
| where enabled = 'false'
| stats count by 'User Name', data.target.cluster.id, 'Compartment Name'
OKE image-policy configuration is a single boolean on the cluster resource; a flip to false is unambiguous.
Alert threshold
Any UpdateImagePolicyConfig with isPolicyEnabled = false on a cluster running production workloads — page.
More than two OCIR signature-verification failures per cluster per hour — open a workload-team ticket on the image build pipeline.
Initial response
Re-enable image-policy verification on the cluster via Resource Manager and re-attach the signed-image key inventory.
Review the pods scheduled during the disabled window; cordon any node running an unsigned image until the build pipeline can re-issue a signed digest.
Brief the workload team on the image-signing pipeline expectations and capture the rollback per general/ir.html.
Enhanced Cluster: attach NSGs at three layers — API endpoint subnet, node pool subnet, and pod subnet (when using OCI VCN-Native Pod Networking) — for granular east-west and egress control.
Basic Cluster: NSG attachment at the pod-subnet layer is more limited; defense-in-depth is reduced.
Apply Network Security Groups at multiple layers — API endpoint subnet, node pool subnet, and pod subnet (VCN-Native Pod Networking) — for defense-in-depth network segmentation. Default-deny outbound to the public internet; allow only required egress, such as OCI service endpoints via a Service Gateway and the OKE management plane endpoints. NSGs complement (do not replace) Kubernetes NetworkPolicy (oci-k8s-09) — NSGs operate at the VCN layer; NetworkPolicy operates inside the cluster.
# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
--compartment-id "$COMPARTMENT_OCID" \
--config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
--repository-url "https://example.com/org/hardening-iac" \
--branch-name "main" \
--working-directory "modules/oci-k8s-06-network-security-groups" \
--display-name "oci-k8s-06-network-security-groups" \
--terraform-version "1.5.x"
# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
--compartment-id "$COMPARTMENT_OCID" \
--display-name "oci-k8s-06-network-security-groups" \
--query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
--stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
--stack-id "$STACK_OCID" \
--execution-plan-strategy FROM_PLAN_JOB \
--execution-plan-job-id "$PLAN_JOB_OCID"
Compliance mapping
Control
Severity
Type
Provider
CIS Kubernetes Benchmark v1.11.0
CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0
OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'core' with eventName = 'UpdateNetworkSecurityGroupSecurityRules' targeting an NSG OCID present in the OKE node-pool NSG binding.
VCN Flow Logs records ('Log Source' = 'OCI VCN Flow Logs') showing East-West traffic between OKE node subnets that was previously denied by the NSG baseline.
OKE cluster update events that swap the bound NSG OCID list to one that no longer references the curated egress NSG.
Query
'Log Source' = 'OCI Audit Logs'
and 'Service Name' = 'core'
and eventName = 'UpdateNetworkSecurityGroupSecurityRules'
| eval rules = data.request.payload.securityRules
| where rules like '%0.0.0.0/0%'
| stats count by 'User Name', data.target.networkSecurityGroup.id, 'Compartment Name'
Correlate the OCID list against the OKE node-pool NSG binding inventory so only OKE-relevant NSG widenings page.
Alert threshold
Any new rule inside an OKE-bound NSG with source equal to 0.0.0.0/0 on ports 22, 3389, 10250, or 6443 — page.
OKE cluster update that removes a previously bound egress NSG OCID — page; egress-NSG omission re-opens the worker-node DNS+API egress posture.
Initial response
Revert the NSG rule set via Resource Manager to the last-known-good HCL; OCI applies the change without re-creating the NSG.
Capture a 30-minute window of VCN Flow Logs around the widening for the affected NSG; export to Object Storage for forensic retention.
Re-attach the OKE-bound NSG list to the cluster and node pools per the cluster's Resource Manager stack output.
Enhanced Cluster / Basic Cluster: OCI Audit captures containerengine API operations by default. Kubernetes API audit log forwarding to OCI Logging is a separate stream — it requires a Log Group plus a Log object pointing at the cluster resource. It is NOT on by default.
OCI Audit captures all OCI-side operations on the cluster resource (create, update, scale node pool, update endpoint, install add-on). Kubernetes API audit logs are a separate stream — forward them to OCI Logging via a Log object on the cluster so in-cluster kubectl, controller, and webhook activity is durable and queryable. Configure log retention to match the organization's incident-investigation horizon.
# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
--compartment-id "$COMPARTMENT_OCID" \
--config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
--repository-url "https://example.com/org/hardening-iac" \
--branch-name "main" \
--working-directory "modules/oci-k8s-07-oci-audit-logging" \
--display-name "oci-k8s-07-oci-audit-logging" \
--terraform-version "1.5.x"
# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
--compartment-id "$COMPARTMENT_OCID" \
--display-name "oci-k8s-07-oci-audit-logging" \
--query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
--stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
--stack-id "$STACK_OCID" \
--execution-plan-strategy FROM_PLAN_JOB \
--execution-plan-job-id "$PLAN_JOB_OCID"
Compliance mapping
Control
Severity
Type
Provider
CIS Kubernetes Benchmark v1.11.0
CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0
NIST SP 800-53 rev5
ISO/IEC 27001:2022
ISO/IEC 27017:2015
NIST SP 800-190 (Sep 2017)
NSA/CISA Kubernetes Hardening Guide v1.2
oci-k8s-07
HIGH
DETECTIVE
OCI OKE
§1.2.22 (audit logging — managed control plane)
n/a (verify against CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0 PDF)
OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'logging' with eventName in (DeleteLog, UpdateLog) targeting a log OCID that backs OKE cluster audit ingestion.
OKE cluster update events that flip options.addOns.isKubernetesDashboardEnabled or any audit-policy field — the latter changes the Kubernetes audit verbosity feeding the log group.
Logging Analytics ingestion gap on the OKE audit log source longer than five minutes during steady-state hours.
Query
'Log Source' = 'OCI Audit Logs'
and 'Service Name' = 'logging'
and eventName in ('DeleteLog', 'UpdateLog', 'DeleteLogGroup')
| eval is_oke_log = if(data.target.log.displayName like '%kube-apiserver%' or data.target.log.displayName like '%oke-audit%', 'YES', 'NO')
| where is_oke_log = 'YES'
| stats count by 'User Name', data.target.log.displayName, eventName
Tag the OKE audit log OCIDs with a defined-tag OKEAuditPipeline = critical so the saved search can filter by tag rather than display-name pattern.
Alert threshold
Any DeleteLog on an OKE audit log OCID — page; loss of the log object breaks the detection pipeline for the cluster.
An ingestion gap exceeding 5 minutes for the OKE Kubernetes audit log source during business hours — page.
Initial response
Re-create the deleted log via Resource Manager; OCI Logging recreates the log object idempotently and re-binds it to the OKE cluster's audit-policy reference.
Pull the cluster's kube-apiserver audit feed directly via oci ce cluster get-kubeconfig and kubectl get events covering the gap; export to Object Storage.
Confirm the Logging Analytics namespace resumes ingestion and update the OKE log inventory dashboard.
Enhanced Cluster / Basic Cluster: the built-in PodSecurity admission controller (K8s 1.23+) enforces Pod Security Standards profiles via namespace labels. No add-on is required — the controller is built into kube-apiserver and is active on all current OKE Kubernetes versions.
Use the built-in PodSecurity admission controller to enforce the Restricted or Baseline Pod Security Standards (PSS) profile at the namespace level. Target Restricted for application namespaces; reserve Baseline for namespaces that legitimately need broader pod capabilities (e.g. CNI components, log forwarders) — and document the exemption. PSS replaces the removed legacy admission mechanism; do not look for a separate cluster-level policy object.
Remediation — kubectl
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest
kubectl label namespace production \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
--compartment-id "$COMPARTMENT_OCID" \
--config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
--repository-url "https://example.com/org/hardening-iac" \
--branch-name "main" \
--working-directory "modules/oci-k8s-08-pod-security-standards" \
--display-name "oci-k8s-08-pod-security-standards" \
--terraform-version "1.5.x"
# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
--compartment-id "$COMPARTMENT_OCID" \
--display-name "oci-k8s-08-pod-security-standards" \
--query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
--stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
--stack-id "$STACK_OCID" \
--execution-plan-strategy FROM_PLAN_JOB \
--execution-plan-job-id "$PLAN_JOB_OCID"
Compliance mapping
Control
Severity
Type
Provider
CIS Kubernetes Benchmark v1.11.0
CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0
NIST SP 800-53 rev5
ISO/IEC 27001:2022
ISO/IEC 27017:2015
NIST SP 800-190 (Sep 2017)
NSA/CISA Kubernetes Hardening Guide v1.2
oci-k8s-08
HIGH
PREVENTIVE
OCI OKE
§5.2 (Pod Security Standards)
n/a (verify against CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0 PDF)
OKE Kubernetes audit log entries forwarded to OCI Logging Analytics where 'Log Source' = 'OCI Audit Logs' filtered to annotations."pod-security.kubernetes.io/enforce" field deltas at the Namespace object scope.
Pod admission entries showing responseStatus.code = 403 with reason text containing violates PodSecurity — these are denials and themselves a healthy detection signal.
Namespace update events removing the Pod Security enforce label entirely, downgrading the namespace to admission-free state.
Query
'Log Source' = 'OCI Audit Logs'
and data.kubernetesAudit.verb = 'update'
and data.kubernetesAudit.objectRef.resource = 'namespaces'
| eval enforce = data.kubernetesAudit.requestObject.metadata.labels."pod-security.kubernetes.io/enforce"
| where enforce in ('privileged', '')
| stats count by 'User Name', data.kubernetesAudit.objectRef.name, enforce
OKE forwards Kubernetes audit log records into Logging Analytics by default in Enhanced clusters; surface the kubernetesAudit JSONPath as a tagged field at parser time.
Alert threshold
Any namespace whose enforce label moves from restricted or baseline down to privileged or empty — page on first occurrence.
More than five admission denials per namespace per hour — open a workload-team ticket; either the workload is non-compliant or the policy needs tuning.
Initial response
Re-apply the namespace baseline via the cluster's GitOps state (Flux or Argo CD reconciliation) — the namespace label is reverted on the next sync interval.
Review the pods that were admitted during the downgraded window; evict any that would have been blocked under restricted enforcement.
Brief the workload team on the Pod Security baseline contract per general/ir.html.
Enhanced Cluster / Basic Cluster: install Calico CNI for NetworkPolicy enforcement on OKE. OCI VCN-Native Pod Networking plus Calico for policy is the typical pairing.
Node OS hardening: Oracle Linux 8 minimal is the default OKE node image; private node pools must have no public IPs on node VNICs.
Apply a default-deny NetworkPolicy in every namespace, then add explicit allow rules for required east-west and egress paths. Calico is the NetworkPolicy enforcer on OKE — install via the documented manifest or Helm chart after cluster create. This control also covers REQ OKE-09 node-hardening requirements: select Oracle Linux 8 minimal as the node image; place node pools in private subnets with no public IPs on node VNICs.
# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
--compartment-id "$COMPARTMENT_OCID" \
--config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
--repository-url "https://example.com/org/hardening-iac" \
--branch-name "main" \
--working-directory "modules/oci-k8s-09-network-policy-calico" \
--display-name "oci-k8s-09-network-policy-calico" \
--terraform-version "1.5.x"
# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
--compartment-id "$COMPARTMENT_OCID" \
--display-name "oci-k8s-09-network-policy-calico" \
--query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
--stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
--stack-id "$STACK_OCID" \
--execution-plan-strategy FROM_PLAN_JOB \
--execution-plan-job-id "$PLAN_JOB_OCID"
Compliance mapping
Control
Severity
Type
Provider
CIS Kubernetes Benchmark v1.11.0
CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0
NIST SP 800-53 rev5
ISO/IEC 27001:2022
ISO/IEC 27017:2015
NIST SP 800-190 (Sep 2017)
NSA/CISA Kubernetes Hardening Guide v1.2
oci-k8s-09
HIGH
PREVENTIVE
OCI OKE
§5.3 (Network policies)
n/a (verify against CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0 PDF)
OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' with eventName = 'UpdateClusterAddon' targeting the Calico add-on with payload flipping configurations.networkPolicyEnabled to false or disabling the add-on outright.
Kubernetes audit entries showing NetworkPolicy resource deletions across all namespaces within a short window — a deliberate teardown.
VCN Flow Logs showing pod-to-pod traffic between namespaces previously denied by a NetworkPolicy now succeeding.
Query
'Log Source' = 'OCI Audit Logs'
and 'Service Name' = 'containerengine'
and eventName in ('UpdateClusterAddon', 'DisableAddon')
| eval addon = data.request.payload.addonName
| where addon like '%Calico%' or addon = 'NetworkPolicy'
| stats count by 'User Name', data.target.cluster.id, addon, eventName
OKE manages Calico via the add-on plane on Enhanced clusters — a configuration change is a single audit-level event.
Alert threshold
Any UpdateClusterAddon on the Calico add-on flipping policy enforcement off — page; cluster default becomes permit-all.
Bulk deletion of NetworkPolicy resources across two or more namespaces inside 10 minutes — page.
Initial response
Re-enable Calico policy enforcement via Resource Manager; the add-on reconciles to ACTIVE within a few minutes and reinstates default-deny inside affected namespaces.
Re-apply the cluster's NetworkPolicy manifests from the GitOps repo; verify Calico Felix logs show policy load success.
Sample VCN Flow Logs across the gap window and document any pod-to-pod traffic that occurred outside policy per general/ir.html.
Enhanced Cluster / Basic Cluster: scope OCI IAM dynamic groups to compartments using least-privilege allow dynamic-group ... to manage ... in compartment ... rules. Avoid manage all-resources in tenancy for any cluster identity.
Use OCI IAM dynamic groups to grant the cluster control plane and node pool exactly the permissions required — never tenancy-wide. Scope each statement to a specific compartment (APP for application resources; NET for networking; SHARED for shared KMS keys with a vault-id-bound condition). Where a grant must reach beyond a single compartment, prefer a where condition that restricts the target by OCID rather than a broader compartment scope.
NEVER: allow dynamic-group ... to manage all-resources in tenancy — this collapses the entire compartment hierarchy into a single trust boundary.
Remediation — Terraform
# Terraform OCI provider ~> 6.0
terraform {
required_providers {
oci = {
source = "oracle/oci"
version = "~> 6.0"
}
}
}
resource "oci_identity_dynamic_group" "oke_cluster" {
compartment_id = var.tenancy_ocid
name = "oke-cluster-dyn-group"
description = "OKE cluster + node-pool identities"
matching_rule = "ANY {ALL {instance.compartment.id = '${var.compartment_id}', tag.oke.cluster.value = '${oci_containerengine_cluster.hardened.id}'}}"
}
resource "oci_identity_policy" "oke_cluster" {
compartment_id = var.tenancy_ocid
name = "oke-cluster-policy"
description = "Least-privilege OKE cluster + node-pool grants"
statements = [
"allow dynamic-group oke-cluster-dyn-group to use cluster-family in compartment APP",
"allow dynamic-group oke-cluster-dyn-group to manage instances in compartment APP",
"allow dynamic-group oke-cluster-dyn-group to use vaults in compartment SHARED where target.vault.id = '${oci_kms_vault.oke.id}'",
]
}
Remediation — OCI CLI
oci iam dynamic-group create \
--compartment-id <TENANCY-OCID> \
--name oke-cluster-dyn-group \
--description "OKE cluster + node-pool identities" \
--matching-rule "ANY {ALL {instance.compartment.id = '<COMPARTMENT-OCID>', tag.oke.cluster.value = '<CLUSTER-OCID>'}}"
oci iam policy create \
--compartment-id <TENANCY-OCID> \
--name oke-cluster-policy \
--description "Least-privilege OKE grants" \
--statements '["allow dynamic-group oke-cluster-dyn-group to use cluster-family in compartment APP", "allow dynamic-group oke-cluster-dyn-group to manage instances in compartment APP", "allow dynamic-group oke-cluster-dyn-group to use vaults in compartment SHARED where target.vault.id = '\''<VAULT-OCID>'\''"]'
Remediation — OCI Resource Manager
# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
--compartment-id "$COMPARTMENT_OCID" \
--config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
--repository-url "https://example.com/org/hardening-iac" \
--branch-name "main" \
--working-directory "modules/oci-k8s-10-iam-least-privilege-cluster-access" \
--display-name "oci-k8s-10-iam-least-privilege-cluster-access" \
--terraform-version "1.5.x"
# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
--compartment-id "$COMPARTMENT_OCID" \
--display-name "oci-k8s-10-iam-least-privilege-cluster-access" \
--query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
--stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
--stack-id "$STACK_OCID" \
--execution-plan-strategy FROM_PLAN_JOB \
--execution-plan-job-id "$PLAN_JOB_OCID"
Compliance mapping
Control
Severity
Type
Provider
CIS Kubernetes Benchmark v1.11.0
CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0
NIST SP 800-53 rev5
ISO/IEC 27001:2022
ISO/IEC 27017:2015
NIST SP 800-190 (Sep 2017)
NSA/CISA Kubernetes Hardening Guide v1.2
oci-k8s-10
HIGH
PREVENTIVE
OCI OKE
§5.1 (RBAC and service accounts)
n/a (verify against CIS Oracle Container Engine for Kubernetes (OKE) Benchmark v1.8.0 PDF)
AC-2; AC-3; AC-6
A.5.15; A.5.16; A.5.18
CLD.12.1.5
NIST SP 800-190 §4.4.2
NSA/CISA Kubernetes Hardening Guide v1.2 §4 (Authentication and authorization)
Log signals
OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'identity' with eventName = 'UpdatePolicy' whose statement body widens grants on cluster-family or cluster-content-read resource types.
Kubernetes RBAC ClusterRoleBinding creation events binding cluster-admin to subjects outside the documented OKE admin allow-list.
Kubeconfig generation events (CreateClusterKubeconfig) issued to OCI user OCIDs not present in the cluster operator inventory.
Query
'Log Source' = 'OCI Audit Logs'
and 'Service Name' = 'identity'
and eventName in ('CreatePolicy', 'UpdatePolicy')
| eval stmt = data.request.payload.statements
| where stmt like '%manage cluster-family%' or stmt like '%use cluster-content-read%'
| stats count by 'User Name', data.target.policy.name, 'Compartment Name'
Run continuously; OKE policy statements are narrow by convention and any new manage cluster-family in tenancy grant is a control-fence break.
Alert threshold
Any new policy statement granting manage cluster-family in tenancy to a group outside OKEAdmins — page on first event.
Kubernetes RBAC cluster-admin binding outside the cluster's documented break-glass group — page; the in-cluster grant bypasses OCI IAM scoping.
Initial response
Revert the OCI policy via the OCI Identity policy version history; the prior statement set was the last-known-good least-privilege scope.
Remove the in-cluster ClusterRoleBinding via the cluster's GitOps reconciliation loop; any drift snaps back on the next sync.
Rotate any kubeconfig tokens issued during the widened window and document the rollback per general/ir.html.