OCI OKE Access to Kubernetes Control Plane Should Be
More Info:
The Kubernetes API endpoint should be reachable only from approved networks (VCN CIDRs, bastions, or specific NSG members). Open control-plane endpoints are a primary target for credential and token abuse.
Risk Level
Critical
Address
Compliance, Security
Compliance Standards
- CIS OKE
Triage and Remediation
- Remediation
Remediation
Using Console
Here’s how to restrict access to the OKE Kubernetes API (control plane) using the OCI Console.
There are two main ways:
- Restrict a public endpoint with an IP allow-list
- Use a private endpoint (accessible only from your VCN)
1. Check current endpoint type and access
- Sign in to the OCI Console.
- Open the navigation menu → Developer Services → Kubernetes Clusters (OKE).
- Select your compartment, then click the cluster.
- On the Cluster details page, under Endpoint, note:
- Endpoint Type: Public or Private
- For Public: check configured Authorized IPs (CIDR list)
2. If you must keep a Public endpoint – restrict by IP
Use this if you still want internet access, but locked to specific IPs (e.g., office VPN, bastion).
- In the Cluster details page, click Edit on the Endpoint Configuration section (name may appear as “Kubernetes API Endpoint” or similar).
- Ensure Endpoint Type is Public.
- In Authorized IPs (or "API Endpoint CIDR allowlist"):
- Remove
0.0.0.0/0or any broad networks you don’t intend to allow. - Add only the required source IPs/CIDRs, e.g.:
- Your corporate public IP:
203.0.113.10/32 - VPN egress subnet:
198.51.100.0/24
- Your corporate public IP:
- Remove
- Save/Update the configuration.
- Test from:
- An allowed IP:
kubectl get nodesshould work. - A disallowed IP: connection to the API should fail.
- An allowed IP:
3. Prefer a Private endpoint – restrict to your VCN
This is the most secure: the API is reachable only inside your VCN (via VCN, VPN, FastConnect, or bastion).
3.1 Convert/ensure cluster is using a private endpoint
For new clusters, you can select Private API endpoint during creation.
For existing clusters, you may need to update or recreate depending on your OKE version and options available.
- In Kubernetes Clusters (OKE) → your cluster → Cluster details.
- Look for Edit cluster / Edit endpoint configuration.
- If the console allows:
- Change Endpoint Type to Private.
- Select the VCN, subnet where the endpoint will be hosted.
- Confirm and save.
- If you cannot change endpoint type (option is disabled):
- Plan to create a new cluster with a Private endpoint and migrate workloads.
3.2 Lock down network access to the private endpoint
The private endpoint lives in a subnet; access is controlled by:
- Network Security Groups (NSGs) attached to the OKE API endpoint
- Or Security Lists on the subnet
Steps (using NSGs is recommended):
- In the Console, go to Networking → Virtual Cloud Networks.
- Open the VCN used by your OKE cluster.
- Go to Network Security Groups and locate the NSG associated with the Kubernetes API endpoint (name often includes the cluster name and “cp”/“api”).
- Edit the Ingress Rules:
- Remove any rule with source
0.0.0.0/0or overly broad CIDRs. - Add rules that allow TCP 6443 (Kubernetes API) only from:
- Your admin/bastion subnet CIDR (e.g.,
10.0.10.0/24), or - A specific host subnet or NSG.
- Your admin/bastion subnet CIDR (e.g.,
- Remove any rule with source
- If instead security lists are used:
- In the VCN, open Subnets → endpoint subnet.
- Edit the Security List:
- Restrict inbound rule for port 6443 to only the required internal CIDRs.
4. Ensure access path for admins and automation
After restricting:
- If using private endpoint:
- Use a bastion host inside the VCN, or
- Use OCI Bastion service, or
- Connect over VPN / FastConnect so your admin machine is in an allowed CIDR.
- Update any CI/CD runners to come from the allowed CIDRs/subnets.
5. Verify restriction
- From an allowed network:
- Download kubeconfig from the Console (cluster page → Access Cluster → Local access / Cloud Shell, etc.).
- Run:
kubectl get namespaces– should succeed.
- From a disallowed IP/subnet:
- Kubernetes API calls should time out or be refused.
If you share whether your current OKE endpoint is public or private, I can give a concrete, minimal rule set to apply for your case.
Using CLI
Below are two common remediations for “OCI OKE access to Kubernetes control plane should be restricted” using OCI CLI:
- A) Restrict a public OKE endpoint to specific IPs
- B) Switch to a private OKE endpoint (or ensure it’s private and protected via NSGs)
Pick whichever matches your architecture.
0. Prerequisites
Make sure:
ociCLI is installed and configured (oci setup configdone).- You know your:
cluster-id- Compartment OCID
- Subnet OCID for the control plane endpoint
- NSG OCIDs (recommended for additional restriction)
1. Get current cluster configuration
oci ce cluster get --cluster-id <CLUSTER_OCID> \
--query 'data."endpoint-config"' --raw-output
Look for:
isPublicIpEnabledsubnetIdnsgIdspublicEndpointConfig.allowedPublicIpAddresses
If isPublicIpEnabled = true and allowedPublicIpAddresses is null or contains 0.0.0.0/0, it’s effectively open.
A) Restrict a Public OKE Control Plane Endpoint
1. Decide allowed IP CIDRs
Example: only allow your corporate public IP and a jump host:
203.0.113.10/32
198.51.100.20/32
2. (Recommended) Create / identify NSGs
Create an NSG for the OKE control plane subnet if you don’t have one:
oci network nsg create \
--compartment-id <COMPARTMENT_OCID> \
--vcn-id <VCN_OCID> \
--display-name "oke-control-plane-nsg"
Add rules to only allow your trusted sources to port 6443:
oci network nsg rule add \
--network-security-group-id <NSG_OCID> \
--security-rules '[
{
"direction": "INGRESS",
"protocol": "6",
"source": "203.0.113.10/32",
"tcpOptions": { "destinationPortRange": { "min": 6443, "max": 6443 } }
},
{
"direction": "INGRESS",
"protocol": "6",
"source": "198.51.100.20/32",
"tcpOptions": { "destinationPortRange": { "min": 6443, "max": 6443 } }
}
]'
3. Update the OKE cluster endpoint config (restrict IPs)
oci ce cluster update \
--cluster-id <CLUSTER_OCID> \
--force \
--endpoint-config '{
"isPublicIpEnabled": true,
"subnetId": "<CONTROL_PLANE_SUBNET_OCID>",
"nsgIds": ["<NSG_OCID>"],
"publicEndpointConfig": {
"allowedPublicIpAddresses": [
"203.0.113.10/32",
"198.51.100.20/32"
]
}
}'
Wait for the work request to finish:
oci ce cluster get --cluster-id <CLUSTER_OCID> \
--query 'data."lifecycle-state"' --raw-output
Confirm:
oci ce cluster get --cluster-id <CLUSTER_OCID> \
--query 'data."endpoint-config"' --raw-output
B) Use a Private OKE Control Plane Endpoint
This limits access to VCN/internal networks and NSGs.
1. Ensure you have a private subnet for control plane
- Subnet should be private (no internet gateway route).
- Attach a suitable NSG.
2. Update cluster to use private endpoint
oci ce cluster update \
--cluster-id <CLUSTER_OCID> \
--force \
--endpoint-config '{
"isPublicIpEnabled": false,
"subnetId": "<PRIVATE_CONTROL_PLANE_SUBNET_OCID>",
"nsgIds": ["<NSG_OCID>"]
}'
Again, check state and config:
oci ce cluster get --cluster-id <CLUSTER_OCID> \
--query 'data."endpoint-config"' --raw-output
Ensure:
isPublicIpEnabledisfalse- NSGs are in place and only allow required internal sources on port 6443.
3. Validate kubectl Access
After any change, from your allowed location:
oci ce cluster create-kubeconfig \
--cluster-id <CLUSTER_OCID> \
--file $HOME/.kube/config \
--region <REGION> \
--token-version 2.0.0 \
--kube-endpoint PUBLIC_ENDPOINT # or PRIVATE_ENDPOINT if private
Then:
kubectl get ns
If you need, tell me whether your cluster is currently public or private and I can give the exact JSON payload for your case.
Using Python
To restrict access to the Kubernetes control plane in OCI OKE, you generally want to:
- Disable the public API endpoint for the cluster (make the control plane private), or
- At minimum, ensure the API endpoint is only reachable from controlled private subnets/NSGs.
Below is how to do this with Python using the OCI SDK.
1. Prerequisites
ociSDK installed:pip install oci- A valid OCI config file (e.g.
~/.oci/config) and a profile with permissions to update OKE clusters:OCI CLI-style credentials, or instance principal/session auth if you prefer (code can be adapted).
2. High‑level steps
- Identify the OKE cluster OCID.
- Decide:
- Which private subnet will host the control plane endpoint.
- Which Network Security Groups (NSGs) will control traffic to that subnet.
- Update the OKE cluster’s endpoint configuration to:
- Disable public IP access (
is_public_ip_enabled=False). - Point to the correct private subnet and NSGs.
- Disable public IP access (
3. Python example: Make control plane private and restrict via NSGs
import oci
from oci.container_engine import ContainerEngineClient
from oci.container_engine.models import UpdateClusterDetails, UpdateClusterEndpointConfigDetails
# ------------------------------------------------------------------
# CONFIGURATION
# ------------------------------------------------------------------
# Path to OCI config and profile name
CONFIG_FILE = "~/.oci/config"
PROFILE = "DEFAULT"
# The OCID of your OKE cluster
CLUSTER_ID = "ocid1.cluster.oc1...."
# Private subnet OCID where the control plane endpoint should live
PRIVATE_SUBNET_ID = "ocid1.subnet.oc1...."
# One or more NSG OCIDs that strictly control who can reach the control plane
# These NSGs should contain ONLY the required ingress rules (e.g., from bastion, CI/CD, etc.)
CONTROL_PLANE_NSG_IDS = [
"ocid1.networksecuritygroup.oc1...."
]
# ------------------------------------------------------------------
# MAIN
# ------------------------------------------------------------------
def main():
# Load config
config = oci.config.from_file(CONFIG_FILE, PROFILE)
ce_client = ContainerEngineClient(config)
# (Optional) show current cluster endpoint config
current_cluster = ce_client.get_cluster(CLUSTER_ID).data
print("Current endpoint config:", current_cluster.endpoint_config)
# Build new endpoint configuration:
# - Disable public IP for control plane (private endpoint only)
# - Place endpoint in PRIVATE_SUBNET_ID
# - Attach restrictive NSGs
endpoint_config_update = UpdateClusterEndpointConfigDetails(
is_public_ip_enabled=False, # core restriction
subnet_id=PRIVATE_SUBNET_ID, # private subnet
nsg_ids=CONTROL_PLANE_NSG_IDS # controlled NSGs
)
update_details = UpdateClusterDetails(
endpoint_config=endpoint_config_update
)
# Call update_cluster
response = ce_client.update_cluster(
cluster_id=CLUSTER_ID,
update_cluster_details=update_details
)
work_request_id = response.headers.get("opc-work-request-id")
print(f"Update submitted. Work request: {work_request_id}")
# (Optional) wait for completion
work_request_client = oci.work_requests.WorkRequestClient(config)
oci.wait_until(
work_request_client,
work_request_client.get_work_request(work_request_id),
'status',
'SUCCEEDED'
)
print("Cluster endpoint update completed.")
if __name__ == "__main__":
main()
4. NSG rules (conceptual)
On the NSGs you pass in CONTROL_PLANE_NSG_IDS, ensure rules:
- Ingress:
- Allow TCP 6443 (Kubernetes API) and any other required ports
- Source: only:
- Bastion hosts
- Admin subnets
- CI/CD runners
- On‑prem IPs via VPN/DRG
- No
0.0.0.0/0or broad public CIDRs.
Example ingress rule concept (configured in OCI console or via Python SDK):
- Source:
10.0.10.0/24(bastion subnet) - Protocol: TCP
- Port: 6443
5. Notes / edge cases
- If the cluster was created as public endpoint only in some older setups, you might need to:
- Ensure a private subnet exists with route to your workers/bastion; then
- Apply the
UpdateClusterEndpointConfigDetailsas shown.
- After making the endpoint private, your
kubectlclients must reach that private subnet (via VPN, FastConnect, bastion, etc.).
If you share how your cluster is currently configured (public vs private, subnets, etc.), I can tailor the Python snippet and NSG rules more precisely.
Using Terraform
resource "oci_containerengine_cluster" "OKE_CLUSTER" {
# Substitute:
# - OKE_CLUSTER with your cluster name
# - COMPARTMENT_OCID with the target compartment OCID
name = "OKE_CLUSTER"
compartment_id = "COMPARTMENT_OCID"
kubernetes_version = "K8S_VERSION"
vcn_id = oci_core_vcn.OKE_VCN.id
# Restrict the Kubernetes API endpoint to a private subnet and NSG
endpoint_config {
subnet_id = oci_core_subnet.OKE_API_SUBNET.id
is_public_ip_enabled = false
nsg_ids = [oci_core_network_security_group.OKE_API_NSG.id]
}
options {
service_lb_subnet_ids = [oci_core_subnet.OKE_LB_SUBNET.id]
}
}
# Subnet where the Kubernetes API (control plane endpoint) lives
resource "oci_core_subnet" "OKE_API_SUBNET" {
# Substitute:
# - OKE_API_SUBNET with your subnet name
# - OKE_VCN with your VCN resource name
# - CIDR_FOR_API_SUBNET with the subnet CIDR
compartment_id = "COMPARTMENT_OCID"
vcn_id = oci_core_vcn.OKE_VCN.id
cidr_block = "CIDR_FOR_API_SUBNET"
display_name = "OKE_API_SUBNET"
prohibit_public_ip_on_vnic = true
dns_label = "okeapi"
# Attach the NSG that enforces allowed sources to the control-plane endpoint
nsg_ids = [oci_core_network_security_group.OKE_API_NSG.id]
}
# NSG that protects the Kubernetes API endpoint
resource "oci_core_network_security_group" "OKE_API_NSG" {
# Substitute:
# - OKE_API_NSG with your NSG name
compartment_id = "COMPARTMENT_OCID"
vcn_id = oci_core_vcn.OKE_VCN.id
display_name = "OKE_API_NSG"
}
# Ingress rule: allow Kubernetes API (TCP 6443) only from approved network(s)
resource "oci_core_network_security_group_security_rule" "OKE_API_INGRESS" {
# Substitute:
# - ALLOWED_CIDR with your bastion/office/VPN CIDR (e.g., "203.0.113.0/24")
# OR use source_type = "NETWORK_SECURITY_GROUP" and source = OTHER_NSG_OCID
network_security_group_id = oci_core_network_security_group.OKE_API_NSG.id
direction = "INGRESS"
protocol = "6" # TCP
source = "ALLOWED_CIDR"
source_type = "CIDR_BLOCK"
tcp_options {
destination_port_range {
min = 6443
max = 6443
}
}
}
# (Example) Allow egress to worker nodes and required OCI services as needed
resource "oci_core_network_security_group_security_rule" "OKE_API_EGRESS" {
network_security_group_id = oci_core_network_security_group.OKE_API_NSG.id
direction = "EGRESS"
protocol = "all"
destination = "0.0.0.0/0"
destination_type = "CIDR_BLOCK"
}
Changing a public control-plane endpoint to private and attaching NSGs is an in-place update of the oci_containerengine_cluster endpoint configuration; worker nodes and node pools are not recreated, but expect a short control-plane reconfiguration period.
Verification: terraform plan should show an in-place update to oci_containerengine_cluster.OKE_CLUSTER changing endpoint_config.is_public_ip_enabled from true to false, plus creation of the oci_core_network_security_group and oci_core_network_security_group_security_rule resources (and the API subnet if it is new).