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

# Ensure Clusters Are Created With Private Nodes

### More Info:

Provision worker nodes without public IP addresses (private nodes) so that node network exposure to the internet is minimized.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS EKS

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Review current endpoint and node IP configuration (CLI)**
           * On any machine with AWS CLI access:
             ```bash theme={null}
             aws eks describe-cluster \
               --region us-east-1 \
               --name my-cluster \
               --query 'cluster.resourcesVpcConfig'
             ```
           * Note:
             * `endpointPublicAccess` / `endpointPrivateAccess` settings.
             * `publicAccessCidrs`.
             * Whether worker nodes (in your nodegroups/Auto Scaling Groups) are launched in public subnets with public IPs (see next step).

        2. **Inspect nodegroup networking and IP assignment (CLI)**
           * List nodegroups:
             ```bash theme={null}
             aws eks list-nodegroups \
               --region us-east-1 \
               --cluster-name my-cluster
             ```
           * For each nodegroup:
             ```bash theme={null}
             aws eks describe-nodegroup \
               --region us-east-1 \
               --cluster-name my-cluster \
               --nodegroup-name <NODEGROUP_NAME> \
               --query 'nodegroup.subnets'
             ```
           * For each subnet ID returned, check if it is public (has a route to an Internet Gateway) and whether instances launched there get public IPs:
             ```bash theme={null}
             aws ec2 describe-subnets \
               --region us-east-1 \
               --subnet-ids <SUBNET_ID>
             ```
           * In the console, confirm whether “Auto-assign public IPv4 address” is enabled for these subnets or on the Launch Template used by the nodegroup.

        3. **Decide target posture: private nodes with restricted API endpoint**
           * Determine:
             * Which subnets will host worker nodes (should be private subnets with NAT access, no Internet Gateway route).
             * Which IP ranges (e.g., your bastion or office IPs) require access to the public EKS endpoint, if any.
           * Document desired:
             * Nodegroups only in private subnets without public IP assignment.
             * `endpointPrivateAccess=true`.
             * `endpointPublicAccess` set to `false` or `true` with tightly scoped `publicAccessCidrs`.

        4. **Reconfigure cluster endpoint access (CLI or console)**
           * If you must keep some public endpoint access but restrict it:
             ```bash theme={null}
             aws eks update-cluster-config \
               --region us-east-1 \
               --name my-cluster \
               --resources-vpc-config \
               endpointPublicAccess=true,endpointPrivateAccess=true,publicAccessCidrs="203.0.113.5/32"
             ```
           * If you can rely only on private access (ensure network connectivity and tooling first):
             ```bash theme={null}
             aws eks update-cluster-config \
               --region us-east-1 \
               --name my-cluster \
               --resources-vpc-config \
               endpointPublicAccess=false,endpointPrivateAccess=true
             ```
           * Validate change:
             ```bash theme={null}
             aws eks describe-cluster \
               --region us-east-1 \
               --name my-cluster \
               --query 'cluster.resourcesVpcConfig'
             ```

        5. **Migrate or create nodegroups without public IPs (console or IaC)**
           * In the AWS console or your IaC, for each nodegroup:
             * Ensure it uses only private subnets (no direct route to an Internet Gateway).
             * Ensure instances do **not** auto-assign public IPv4 addresses (via subnet setting or Launch Template network interface configuration).
           * If current nodegroups use public subnets or public IPs, create replacement nodegroups in private subnets, cordon/drain old nodes, then delete the old nodegroups.

        6. **Verify that nodes are private and exposure is minimized (CLI)**
           * On any machine with kubectl access:
             ```bash theme={null}
             kubectl get nodes -o wide
             ```
             Confirm node INTERNAL-IP values are RFC1918/private ranges and no EXTERNAL-IP is assigned.
           * From AWS CLI, confirm worker instances have no public IPs:
             ```bash theme={null}
             aws ec2 describe-instances \
               --region us-east-1 \
               --filters "Name=tag:eks:cluster-name,Values=my-cluster" \
               --query 'Reservations[].Instances[].PublicIpAddress'
             ```
             Ensure the output is `null` for all instances, and that `describe-cluster` still shows the intended `resourcesVpcConfig`.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot be used to create or convert EKS worker nodes to private nodes, because this setting is managed at the AWS control-plane / VPC configuration layer (console, CLI, or IaC), not via Kubernetes API objects. Use the cloud provider configuration as described in the Manual Steps section to review and adjust your cluster’s networking and node exposure.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Report EKS cluster API endpoint exposure and node public IP usage.
        # Requirements:
        #   - aws CLI configured with at least read-only EKS/EC2/VPC permissions
        #   - kubectl configured (used only to verify the cluster is reachable)
        #
        # Usage:
        #   ./eks_private_nodes_report.sh <region> <cluster-name>
        #
        # Run from: any machine with aws + kubectl access

        set -euo pipefail

        REGION="${1:-}"
        CLUSTER_NAME="${2:-}"

        if [[ -z "$REGION" || -z "$CLUSTER_NAME" ]]; then
          echo "Usage: $0 <region> <cluster-name>" >&2
          exit 1
        fi

        echo "=== Verifying kubectl access to cluster '${CLUSTER_NAME}' in region '${REGION}' ==="
        aws eks update-kubeconfig --region "$REGION" --name "$CLUSTER_NAME" >/dev/null
        kubectl get nodes -o wide || {
          echo "ERROR: Unable to reach cluster with kubectl." >&2
          exit 1
        }

        echo
        echo "=== EKS Cluster Endpoint Configuration (control plane) ==="
        aws eks describe-cluster \
          --region "$REGION" \
          --name "$CLUSTER_NAME" \
          --query 'cluster.resourcesVpcConfig.{endpointPublicAccess:endpointPublicAccess,endpointPrivateAccess:endpointPrivateAccess,publicAccessCidrs:publicAccessCidrs}' \
          --output table

        echo
        echo "=== Interpretation (control plane) ==="
        cat <<'EOF'
        - endpointPublicAccess = true  AND publicAccessCidrs includes 0.0.0.0/0
            -> RISK: API server is exposed to the entire internet.
        - endpointPublicAccess = true  AND publicAccessCidrs is a broad range (e.g. /0, /8, /16)
            -> RISK: API server is exposed to large address ranges.
        - endpointPrivateAccess = false
            -> RISK: No private endpoint; all access must go through the public endpoint.

        Recommended pattern (per control intent):
        - endpointPrivateAccess = true
        - endpointPublicAccess = false
          OR
        - endpointPublicAccess = true with tightly scoped publicAccessCidrs to specific admin IPs/VPN.
        EOF

        echo
        echo "=== Nodegroup Public IP Configuration (whether nodes may get public IPs) ==="
        aws eks list-nodegroups \
          --region "$REGION" \
          --cluster-name "$CLUSTER_NAME" \
          --query 'nodegroups' \
          --output text | tr '\t' '\n' | while read -r NG; do
          [[ -z "$NG" ]] && continue
          echo
          echo "Nodegroup: $NG"
          aws eks describe-nodegroup \
            --region "$REGION" \
            --cluster-name "$CLUSTER_NAME" \
            --nodegroup-name "$NG" \
            --query 'nodegroup.{subnets:subnets,remoteAccess:remoteAccess,launchTemplate:launchTemplate}' \
            --output json

          echo "  -> Auto-assign public IP on subnets (indicates potential for public nodes):"
          SUBNETS=$(aws eks describe-nodegroup \
            --region "$REGION" \
            --cluster-name "$CLUSTER_NAME" \
            --nodegroup-name "$NG" \
            --query 'nodegroup.subnets' \
            --output text | tr '\t' '\n')

          for SN in $SUBNETS; do
            MAP_PUBLIC_IP=$(aws ec2 describe-subnets \
              --region "$REGION" \
              --subnet-ids "$SN" \
              --query 'Subnets[0].MapPublicIpOnLaunch' \
              --output text)
            echo "    Subnet $SN: MapPublicIpOnLaunch=${MAP_PUBLIC_IP}"
          done
        done

        echo
        echo "=== Interpretation (nodegroups/subnets) ==="
        cat <<'EOF'
        - For any subnet with MapPublicIpOnLaunch = true:
            -> RISK: EC2 instances (including worker nodes) in this subnet will receive public IPs
               unless overridden by the launch template or other settings.
        - To meet the intent of "private nodes":
            -> Worker nodes should be in subnets with MapPublicIpOnLaunch = false (private subnets),
               behind NAT gateways or similar, with NO direct public IPs assigned.

        You must review:
        - Whether any nodegroup subnets have MapPublicIpOnLaunch = true.
        - Whether that configuration is acceptable for your risk profile.
        EOF

        echo
        echo "=== (Optional) Live Node Public IP Check via EC2 ==="
        echo "Gathering node instance IDs from cluster..."
        NODE_INSTANCE_IDS=$(kubectl get nodes -o jsonpath='{range .items[*]}{.spec.providerID}{"\n"}{end}' \
          | sed -n 's|^aws:///[^/]*/||p' | sort -u)

        if [[ -z "$NODE_INSTANCE_IDS" ]]; then
          echo "No node instance IDs found from kubectl; skipping EC2 IP check."
          exit 0
        fi

        echo "Checking public IPs for current worker nodes..."
        aws ec2 describe-instances \
          --region "$REGION" \
          --instance-ids $NODE_INSTANCE_IDS \
          --query 'Reservations[].Instances[].{InstanceId:InstanceId,PrivateIpAddress:PrivateIpAddress,PublicIpAddress:PublicIpAddress,SubnetId:SubnetId}' \
          --output table

        cat <<'EOF'

        Interpretation (live nodes):
        - Any node with a non-empty PublicIpAddress
            -> RISK: That worker node is directly reachable from the internet (subject to security groups).
        - For strict "private nodes":
            -> All worker nodes should show PublicIpAddress as blank/null.

        This script only reports state. Decisions and remediation (changing endpoint access,
        moving nodegroups to private subnets, adjusting subnet attributes, or recreating
        nodegroups) must be made manually based on your security requirements.
        EOF
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
