Skip to main content

Kubelet Read Only Port Secured

More Info:

The kubelet read-only port exposes cluster data without authentication and should be disabled by setting readOnlyPort to 0.

Risk Level

High

Address

Security

Compliance Standards

  • CIS AKS

Triage and Remediation

Remediation

Manual Steps
  1. On every worker node, check how kubelet is started:

    /bin/ps -fC kubelet

    Inspect the output to see whether --config=/var/lib/kubelet/config.yaml is used and whether a --read-only-port flag is present.

  2. If using the kubelet config file /var/lib/kubelet/config.yaml (common with kubeadm), edit it on every worker node and set readOnlyPort to 0:

    sudo sed -i 's/^[[:space:]]*readOnlyPort:.*/readOnlyPort: 0/' /var/lib/kubelet/config.yaml

    If readOnlyPort is not present, add it under the top-level fields (align indentation with other keys):

    sudo sed -i '1ireadOnlyPort: 0' /var/lib/kubelet/config.yaml
  3. If kubelet is using a systemd unit flag instead of (or in addition to) the config file, edit /etc/systemd/system/kubelet.service.d/10-kubeadm.conf on every worker node and ensure --read-only-port=0 is set in the kubelet arguments (either in KUBELET_SYSTEM_PODS_ARGS or the relevant KUBELET_* variable):

    sudo sed -i 's/--read-only-port=[0-9]\+/--read-only-port=0/g' /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

    If no --read-only-port is present, append it to the kubelet options line, for example:

    sudo sed -i 's#\(KUBELET_CONFIG_ARGS=".*\)"#\1 --read-only-port=0"#' /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
  4. Reload systemd and restart kubelet on every worker node (this will temporarily disrupt workloads on that node while kubelet restarts):

    sudo systemctl daemon-reload
    sudo systemctl restart kubelet.service
  5. Verify on every worker node that kubelet no longer exposes a nonzero read-only port:

    /bin/ps -fC kubelet

    Confirm that:

    • There is no --read-only-port flag with a value other than 0, and
    • If present as a flag, it is --read-only-port=0.
      If relying solely on the config file, you may additionally confirm the setting:
    grep -i 'readOnlyPort' /var/lib/kubelet/config.yaml
Using kubectl

kubectl cannot change the kubelet’s readOnlyPort setting because it is controlled by host-level configuration on each worker node (/var/lib/kubelet/config.yaml and/or the systemd unit kubelet.service). To remediate this finding, make the changes directly on every worker node as described in the Manual Steps section.

Automation
#!/usr/bin/env bash
#
# Harden kubelet read-only port on this worker node.
# - Sets readOnlyPort: 0 in /var/lib/kubelet/config.yaml (YAML-aware)
# - Ensures any --read-only-port flag is set to 0
# - Restarts kubelet and verifies
#
# Run on: every worker node (as root)
# Safe to re-run (idempotent).

set -euo pipefail

KUBELET_CONFIG="/var/lib/kubelet/config.yaml"
SYSTEMD_DROPIN="/etc/systemd/system/kubelet.service.d/10-kubeadm.conf"
BACKUP_SUFFIX=".$(date +%Y%m%d%H%M%S).bak"

backup_file() {
local f="$1"
if [ -f "$f" ] && [ ! -f "$f$BACKUP_SUFFIX" ]; then
cp -p "$f" "$f$BACKUP_SUFFIX"
fi
}

ensure_readonlyport_in_config() {
if [ ! -f "$KUBELET_CONFIG" ]; then
echo "WARN: $KUBELET_CONFIG not found, skipping config.yaml edit"
return
fi

backup_file "$KUBELET_CONFIG"

# If key exists at top level, replace its value; otherwise append it.
if grep -Eq '^[[:space:]]*readOnlyPort:' "$KUBELET_CONFIG"; then
sed -i 's/^[[:space:]]*readOnlyPort:.*/readOnlyPort: 0/' "$KUBELET_CONFIG"
else
# Ensure file ends with a newline
sed -i -e '$a\' "$KUBELET_CONFIG"
printf '\nreadOnlyPort: 0\n' >> "$KUBELET_CONFIG"
fi
}

ensure_readonlyport_flag() {
if [ ! -f "$SYSTEMD_DROPIN" ]; then
echo "WARN: $SYSTEMD_DROPIN not found, skipping systemd drop-in edit"
return
fi

backup_file "$SYSTEMD_DROPIN"

# Ensure Environment line exists
if ! grep -q '^Environment=' "$SYSTEMD_DROPIN"; then
echo 'Environment="KUBELET_SYSTEM_PODS_ARGS="' >> "$SYSTEMD_DROPIN"
fi

# Normalize to one Environment line (no-op if already single)
# Then ensure it contains --read-only-port=0 exactly once.
# 1) Remove any existing --read-only-port flag
sed -i 's/--read-only-port=[^" ]*//g' "$SYSTEMD_DROPIN"
# 2) Trim double spaces left behind
sed -i 's/ / /g' "$SYSTEMD_DROPIN"
# 3) Append --read-only-port=0 inside the Environment value
perl -pi -e '
if (/^Environment="KUBELET_SYSTEM_PODS_ARGS="(.*)"\s*$/) {
my $v = $1;
$v =~ s/\s+$//;
$_ = "Environment=\"KUBELET_SYSTEM_PODS_ARGS=$v --read-only-port=0\"\n";
}
' "$SYSTEMD_DROPIN"
}

restart_kubelet() {
systemctl daemon-reload
systemctl restart kubelet.service
}

verify() {
echo "Verification: checking kubelet process flags and config..."
/bin/ps -fC kubelet || {
echo "FAIL: kubelet process not found after restart"
return 1
}

# Check no non-zero read-only-port flag
if /bin/ps -fC kubelet | grep -q -- '--read-only-port='; then
if /bin/ps -fC kubelet | grep -q -- '--read-only-port=0'; then
echo "OK: kubelet started with --read-only-port=0"
else
echo "FAIL: kubelet running with non-zero --read-only-port"
/bin/ps -fC kubelet
return 1
fi
else
echo "OK: kubelet has no --read-only-port flag (uses config.yaml)"
fi

if [ -f "$KUBELET_CONFIG" ]; then
if grep -Eq '^[[:space:]]*readOnlyPort:[[:space:]]*0[[:space:]]*$' "$KUBELET_CONFIG"; then
echo "OK: $KUBELET_CONFIG has readOnlyPort: 0"
else
echo "WARN: $KUBELET_CONFIG does not show readOnlyPort: 0"
grep -n 'readOnlyPort' "$KUBELET_CONFIG" || true
fi
fi

echo "Current kubelet command line:"
/bin/ps -fC kubelet
}

main() {
ensure_readonlyport_in_config
ensure_readonlyport_flag
restart_kubelet
verify
}

main "$@"