> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Using Console" defaultOpen="true">
        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**

        1. Sign in to the **OCI Console**.
        2. Go to **Developer Services → Kubernetes Clusters (OKE)**.
        3. Select your **compartment** and then your **cluster**.
        4. In the cluster details page, open the **Node Pools** tab.
        5. Click the **node pool** you want to harden.
        6. 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

        1. From the same **cluster details** page, go to **Node Pools → Create node pool**.
        2. Configure:
           * **Compartment**, **Name**, **Kubernetes version**, **Subnet(s)**, **Shape**, etc., to match your existing worker configuration (or your desired new config).
        3. 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=false` is desired).
           * Ensure:
             * Only the **secure kubelet port** (10250) is enabled.
             * TLS remains enabled (default in OKE).
        4. Complete the rest of the wizard and click **Create**.
        5. 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

        1. **Cordon and drain** old nodes (from your workstation with kubeconfig):

           ```bash theme={null}
           kubectl get nodes -o wide
           # For each old node:
           kubectl cordon <old-node-name>
           kubectl drain <old-node-name> --ignore-daemonsets --delete-emptydir-data
           ```

        2. 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.

        3. Confirm pods are running on the new nodes:

           ```bash theme={null}
           kubectl get pods -A -o wide
           ```

        ***

        ### 3. Delete the old node pool

        1. In the OCI Console, return to the **Node Pools** list for the cluster.
        2. Confirm that workloads are running on the new node pool only.
        3. Select the **old node pool → Delete**.
        4. Wait for the deletion to complete.

        ***

        **C. (Optional) Network-level enforcement**

        To further ensure no HTTP access to kubelet:

        1. Identify the **security lists/NSGs** attached to the worker subnets.
        2. 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.

        ***

        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.
      </Accordion>

      <Accordion title="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`:

        ```yaml theme={null}
        #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`:

        ```bash theme={null}
        BASE64_USER_DATA=$(base64 -w0 kubelet-hardening.yaml)   # -w0 for single line (Linux)
        ```

        On macOS:

        ```bash theme={null}
        BASE64_USER_DATA=$(base64 kubelet-hardening.yaml)
        ```

        ***

        ## 2. Identify your node pool

        ```bash theme={null}
        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:

        ```bash theme={null}
        NODE_POOL_OCID="<your_node_pool_ocid>"
        ```

        ***

        ## 3. Merge user\_data into node pool metadata via OCI CLI

        1. Fetch current node pool JSON:

        ```bash theme={null}
        oci ce node-pool get \
          --node-pool-id "$NODE_POOL_OCID" \
          --query "data" \
          --raw-output > nodepool.json
        ```

        2. Extract existing node metadata (if any):

        ```bash theme={null}
        CURRENT_METADATA=$(jq -r '.nodeConfigDetails.metadata // {}' nodepool.json)
        ```

        3. Create a new metadata JSON that includes/overrides `user_data`:

        ```bash theme={null}
        NEW_METADATA=$(jq -n \
          --argjson meta "$CURRENT_METADATA" \
          --arg ud "$BASE64_USER_DATA" \
          '$meta + {user_data: $ud}')
        ```

        4. Save it:

        ```bash theme={null}
        echo "$NEW_METADATA" > new-metadata.json
        ```

        5. Update the node pool:

        ```bash theme={null}
        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:

        ```bash theme={null}
        oci ce node-pool get \
          --node-pool-id "$NODE_POOL_OCID" \
          --query "data.nodes[*].id" \
          --raw-output
        ```

        Then, one by one (to avoid downtime):

        ```bash theme={null}
        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:

        ```bash theme={null}
        # 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`:

        ```bash theme={null}
        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.
      </Accordion>

      <Accordion title="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

        1. Go to your OKE cluster in OCI Console.
        2. Check each **Node Pool → Subnets** used for worker nodes.
        3. 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:

        ```bash theme={null}
        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.

        ```python theme={null}
        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**:

        ```python theme={null}
        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

        1. 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.
        2. 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.
      </Accordion>

      <Accordion title="Using Terraform">
        ```hcl theme={null}
        # 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.
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
