OCI OKE Kubelet Servers Should Serve Only HTTPS Traffic
More Info:
Kubelet HTTP endpoints transmit credentials and pod data in cleartext and bypass authentication. Force HTTPS-only listeners so all kubelet traffic is encrypted and authenticated.
Risk Level
Critical
Address
Compliance, Security
Compliance Standards
- CIS OKE
Triage and Remediation
- Remediation
Remediation
Using Console
To ensure OKE kubelets only serve HTTPS (and disable the insecure/read‑only HTTP port), you must configure the kubelet options on the node pool. This is done from the OCI Console at the node pool level.
A. Check current node pool kubelet settings
- Sign in to the OCI Console.
- Go to Developer Services → Kubernetes Clusters (OKE).
- Select your compartment and then your cluster.
- In the cluster details page, open the Node Pools tab.
- Click the node pool you want to harden.
- Look for Kubelet configuration or Kubelet security settings (the exact label depends on OKE version).
- If you see options like “Enable Kubelet read-only port” or “Enable anonymous access”, and they are enabled, the kubelet can serve HTTP traffic on the insecure/read‑only port (typically 10255).
If these settings are already disabled, the kubelet is serving only HTTPS, and you’re done.
B. Update the node pool so kubelet uses HTTPS only
In most OKE versions, kubelet insecure/read‑only port settings are immutable for existing nodes. The secure practice is to create a new node pool with hardened kubelet settings, then drain and delete the old one.
1. Create a new hardened node pool
- From the same cluster details page, go to Node Pools → Create node pool.
- Configure:
- Compartment, Name, Kubernetes version, Subnet(s), Shape, etc., to match your existing worker configuration (or your desired new config).
- Scroll to Kubelet configuration / Kubelet security settings:
- Uncheck / Disable:
- Enable Kubelet read-only port (or similar wording).
- This disables the HTTP read-only port (typically 10255).
- Any anonymous access setting (e.g.,
--anonymous-auth=falseis desired).
- Enable Kubelet read-only port (or similar wording).
- Ensure:
- Only the secure kubelet port (10250) is enabled.
- TLS remains enabled (default in OKE).
- Uncheck / Disable:
- Complete the rest of the wizard and click Create.
- Wait until the new node pool status is Active and nodes are Ready in the Kubernetes cluster (
kubectl get nodes).
2. Move workloads to the new node pool
-
Cordon and drain old nodes (from your workstation with kubeconfig):
kubectl get nodes -o wide# For each old node:kubectl cordon <old-node-name>kubectl drain <old-node-name> --ignore-daemonsets --delete-emptydir-data -
Make sure your deployments / node selectors / taints allow scheduling onto the new node pool.
- If you use nodePoolId / oci.oraclecloud.com/metadata or other labels, update them or add labels to the new nodes and adjust selectors.
-
Confirm pods are running on the new nodes:
kubectl get pods -A -o wide
3. Delete the old node pool
- In the OCI Console, return to the Node Pools list for the cluster.
- Confirm that workloads are running on the new node pool only.
- Select the old node pool → Delete.
- Wait for the deletion to complete.
C. (Optional) Network-level enforcement
To further ensure no HTTP access to kubelet:
- Identify the security lists/NSGs attached to the worker subnets.
- In Networking → Virtual Cloud Networks → [Your VCN] → Subnets / NSGs:
- Confirm there are no ingress rules allowing traffic to:
- Port 10255 (kubelet read‑only, HTTP).
- Allow only:
- Port 10250 from control plane CIDRs (as required by OKE), not from the public internet.
- Confirm there are no ingress rules allowing traffic to:
After the new node pool is in place with kubelet read‑only port disabled and network rules tightened, all kubelet traffic will be restricted to HTTPS (TLS on 10250) only.
Using CLI
In OKE, kubelets already serve HTTPS on port 10250. The usual “HTTP only” risk comes from the legacy read-only HTTP port (10255). The practical remediation is to ensure kubelet’s readOnlyPort is disabled (set to 0) across all worker nodes. With managed nodes you do this via the node pool (so new/replaced nodes come up correctly), using the OCI CLI.
Below is a concise, CLI‑only way to do that.
1. Prepare a cloud‑init script to harden kubelet
Create a file kubelet-hardening.yaml:
#cloud-config
runcmd:
# Disable kubelet read-only port (HTTP)
- |
KUBELET_CONFIG="/var/lib/kubelet/config.yaml"
if [ -f "$KUBELET_CONFIG" ]; then
# If readOnlyPort exists, set it to 0; otherwise add it
if grep -q "^readOnlyPort:" "$KUBELET_CONFIG"; then
sed -i 's/^readOnlyPort:.*/readOnlyPort: 0/' "$KUBELET_CONFIG"
else
echo "readOnlyPort: 0" >> "$KUBELET_CONFIG"
fi
fi
# Optional: ensure kubelet only listens on localhost for non‑TLS, if present
if grep -q "^address:" "$KUBELET_CONFIG"; then
sed -i 's/^address:.*/address: 127.0.0.1/' "$KUBELET_CONFIG"
fi
# Restart kubelet to apply config
systemctl daemon-reload || true
systemctl restart kubelet || true
Base64‑encode it for use as user_data:
BASE64_USER_DATA=$(base64 -w0 kubelet-hardening.yaml) # -w0 for single line (Linux)
On macOS:
BASE64_USER_DATA=$(base64 kubelet-hardening.yaml)
2. Identify your node pool
COMPARTMENT_OCID="<your_compartment_ocid>"
CLUSTER_OCID="<your_cluster_ocid>"
oci ce node-pool list \
--compartment-id "$COMPARTMENT_OCID" \
--cluster-id "$CLUSTER_OCID" \
--all
Copy the id of the node pool you want to harden:
NODE_POOL_OCID="<your_node_pool_ocid>"
3. Merge user_data into node pool metadata via OCI CLI
- Fetch current node pool JSON:
oci ce node-pool get \
--node-pool-id "$NODE_POOL_OCID" \
--query "data" \
--raw-output > nodepool.json
- Extract existing node metadata (if any):
CURRENT_METADATA=$(jq -r '.nodeConfigDetails.metadata // {}' nodepool.json)
- Create a new metadata JSON that includes/overrides
user_data:
NEW_METADATA=$(jq -n \
--argjson meta "$CURRENT_METADATA" \
--arg ud "$BASE64_USER_DATA" \
'$meta + {user_data: $ud}')
- Save it:
echo "$NEW_METADATA" > new-metadata.json
- Update the node pool:
oci ce node-pool update \
--node-pool-id "$NODE_POOL_OCID" \
--node-config-details '{ "metadata": '"$(cat new-metadata.json)"' }' \
--force
This ensures all new or replaced nodes in this pool run the hardening script on boot, disabling the HTTP port.
4. Recycle nodes so the change takes effect
For existing nodes, the new user_data only runs on (re)provisioning. Use oci ce node-pool or oci compute instance to rotate nodes:
List nodes in the node pool:
oci ce node-pool get \
--node-pool-id "$NODE_POOL_OCID" \
--query "data.nodes[*].id" \
--raw-output
Then, one by one (to avoid downtime):
NODE_OCID="<one_node_ocid>"
# Option A: delete the node from the node pool; OKE recreates it with new config
oci ce node delete \
--node-pool-id "$NODE_POOL_OCID" \
--node-id "$NODE_OCID" \
--force
Repeat for all nodes, or use your usual rolling‑update procedure.
5. Verify kubelet only serves HTTPS
SSH to a node and verify:
# No HTTP on 10255
sudo netstat -tulnp | grep 10255 || echo "No HTTP port 10255"
# HTTPS on 10250 (secured)
sudo netstat -tulnp | grep 10250
Optionally check /var/lib/kubelet/config.yaml:
grep -E "readOnlyPort|address" /var/lib/kubelet/config.yaml
# Expect: readOnlyPort: 0 (and/or address: 127.0.0.1 if set)
At that point, kubelet will only be serving over HTTPS (port 10250), and the insecure HTTP port is disabled.
Using Python
To ensure kubelet only serves HTTPS in OCI OKE, the practical remediation you can do yourself is to block/disable HTTP (read-only kubelet port 10255) at the network level using OCI Security Lists or NSGs via the OCI Python SDK.
Below is a step‑by‑step outline and a Python example.
1. Understand what needs to be blocked
Kubelet typically uses:
- Port 10250 (HTTPS) – secure kubelet API (keep this, but restrict who can access it).
- Port 10255 (HTTP, read-only) – insecure kubelet API (must be blocked).
On OKE managed worker nodes, you usually cannot directly edit kubelet flags, so the standard hardening approach is to block 10255 via network rules.
2. Identify the subnet / NSG / security list used by worker nodes
- Go to your OKE cluster in OCI Console.
- Check each Node Pool → Subnets used for worker nodes.
- For each subnet, note:
- Its OCID.
- Whether it uses:
- NSGs (preferred), or
- Security Lists.
You will modify whichever is in use.
3. Python SDK setup
Install SDK if needed:
pip install oci
Ensure ~/.oci/config is set up (with tenancy, user, fingerprint, private_key, region, and profile).
4. Example: Block kubelet HTTP (10255) on a Security List via Python
This example:
- Reads an existing security list.
- Removes any ingress on TCP/10255 (and optionally any unrestricted 10250).
- Updates the security list.
import oci
# CONFIG
PROFILE_NAME = "DEFAULT"
SECURITY_LIST_OCID = "ocid1.securitylist.oc1..xxxxxxxx" # replace with your security list OCID
BLOCK_HTTPS_FROM_INTERNET = False # if True, also restrict 10250
config = oci.config.from_file("~/.oci/config", PROFILE_NAME)
network_client = oci.core.VirtualNetworkClient(config)
# Get current security list
sec_list = network_client.get_security_list(SECURITY_LIST_OCID).data
new_ingress_rules = []
for rule in sec_list.ingress_security_rules:
# Keep non-TCP rules
if rule.protocol != "6": # "6" == TCP
new_ingress_rules.append(rule)
continue
port_range = getattr(rule.tcp_options, "destination_port_range", None)
port_min = getattr(port_range, "min", None)
port_max = getattr(port_range, "max", None)
# Drop any rule that allows port 10255
if port_min == 10255 and port_max == 10255:
continue
# Optionally tighten 10250 if exposed broadly (e.g., 0.0.0.0/0)
if BLOCK_HTTPS_FROM_INTERNET and port_min == 10250 and port_max == 10250:
# Example: keep only if not from 0.0.0.0/0
if rule.source == "0.0.0.0/0":
continue
# Otherwise keep rule
new_ingress_rules.append(rule)
# Prepare update details
update_details = oci.core.models.UpdateSecurityListDetails(
display_name=sec_list.display_name,
ingress_security_rules=new_ingress_rules,
egress_security_rules=sec_list.egress_security_rules,
freeform_tags=sec_list.freeform_tags,
defined_tags=sec_list.defined_tags,
)
# Update security list
response = network_client.update_security_list(
security_list_id=SECURITY_LIST_OCID,
update_security_list_details=update_details
)
print("Updated security list:", response.data.id)
5. Example: Block kubelet HTTP (10255) on an NSG via Python
If your worker nodes use NSGs:
import oci
PROFILE_NAME = "DEFAULT"
NSG_OCID = "ocid1.networksecuritygroup.oc1..xxxxxxxx" # replace with your NSG OCID
BLOCK_HTTPS_FROM_INTERNET = False
config = oci.config.from_file("~/.oci/config", PROFILE_NAME)
network_client = oci.core.VirtualNetworkClient(config)
# List NSG security rules
rules_response = network_client.list_network_security_group_security_rules(
network_security_group_id=NSG_OCID
)
rules = rules_response.data
rules_to_delete = []
for rule in rules:
# Ingress only
if rule.direction != "INGRESS":
continue
if rule.protocol != "6": # TCP
continue
port_range = getattr(rule.tcp_options, "destination_port_range", None)
port_min = getattr(port_range, "min", None)
port_max = getattr(port_range, "max", None)
# Delete any rule allowing 10255
if port_min == 10255 and port_max == 10255:
rules_to_delete.append(rule.id)
continue
# Optionally tighten 10250
if BLOCK_HTTPS_FROM_INTERNET and port_min == 10250 and port_max == 10250:
if rule.source == "0.0.0.0/0":
rules_to_delete.append(rule.id)
# Delete selected rules
for rule_id in rules_to_delete:
network_client.delete_network_security_group_security_rule(
security_rule_id=rule_id
)
print("Deleted NSG rule:", rule_id)
6. Verify
- From a pod or external host, try:
curl http://<worker-node-ip>:10255/healthz→ should fail.curl https://<worker-node-ip>:10250/healthz(with proper auth/cert) → should work but only from allowed sources.
- Optionally run your security scanner again to confirm remediation.
If you share whether your worker nodes are using Security Lists or NSGs (and how 10255 is currently allowed), I can tailor the Python snippet exactly to your setup.
Using Terraform
# There is currently no kubelet HTTP/HTTPS toggle exposed on the
# `oci_containerengine_node_pool` (OKE node pool) Terraform resource
# or in the underlying OKE API.
# As of the current Oracle Cloud Infrastructure provider version, you
# CANNOT remediate "Kubelet Servers Should Serve Only HTTPS Traffic"
# directly via Terraform on OKE:
# - `oci_containerengine_node_pool` has no arguments to:
# - disable the insecure kubelet (HTTP) port, or
# - force HTTPS-only listeners, or
# - pass kubelet CLI flags like `--read-only-port=0` or `--anonymous-auth=false`.
#
# - OKE manages kubelet configuration internally; there is no supported
# API surface to override kubelet flags per node pool.
# To address this finding you must use OKE/OCI console or operational controls
# outside of Terraform, such as:
# - Rely on OKE’s managed kubelet configuration (where HTTP is not exposed
# externally in standard configurations), and/or
# - Restrict node access via network security rules (NSGs/security lists),
# ensuring kubelet ports are not reachable except from trusted control
# plane components.
# No `terraform plan` change can flip kubelet to HTTPS-only on OKE node pools
# because the provider does not expose that setting.