Ensure Read Only Port Is Secured
More Info:
Disable the read-only port.
Risk Level
Medium
Address
Security
Compliance Standards
- APRA CPS 234 (Australia)
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS Critical Security Controls v8
- CIS EKS
- CMMC 2.0
- CSA Cloud Controls Matrix v4
- DPDPA
- Digital Operational Resilience Act (EU)
- Essential 8
- ISO/IEC 27017
- ISO/IEC 27018
- ISO/IEC 27701
- KSA PDPL
- MAS Technology Risk Management (Singapore)
- MITRE ATT&CK (Cloud)
- NIS2 Directive
- NIST SP 800-171
- NYDFS 23 NYCRR 500
- SWIFT Customer Security Controls Framework
- Sarbanes-Oxley IT General Controls
- UK NCSC Cyber Assessment Framework
Triage and Remediation
- Remediation
Remediation
Manual Steps
- On every worker node, check how kubelet is configured:
ps -fC kubelet
If you see a --config flag (for example --config=/var/lib/kubelet/config.yaml), use the config file method (step 2). If you see --read-only-port on the command line or no --config flag, use the systemd arguments method (step 3).
- If using the kubelet config file on a worker node, edit
/var/lib/kubelet/config.yamland setreadOnlyPortto 0 (create the key if missing), for example:
sudo sed -i 's/^[[:space:]]*readOnlyPort:.*$/readOnlyPort: 0/' /var/lib/kubelet/config.yaml || \
sudo grep -q '^[[:space:]]*readOnlyPort:' /var/lib/kubelet/config.yaml || \
echo 'readOnlyPort: 0' | sudo tee -a /var/lib/kubelet/config.yaml
- If using executable arguments on a worker node, edit
/etc/systemd/system/kubelet.service.d/10-kubelet-args.confto ensure--read-only-port=0is present in the kubelet arguments, for example:
sudo sed -i 's/--read-only-port=[0-9]\+//g' /etc/systemd/system/kubelet.service.d/10-kubelet-args.conf
sudo grep -q -- '--read-only-port=0' /etc/systemd/system/kubelet.service.d/10-kubelet-args.conf || \
echo 'KUBELET_ARGS="$KUBELET_ARGS --read-only-port=0"' | sudo tee -a /etc/systemd/system/kubelet.service.d/10-kubelet-args.conf
- On every worker node where you changed configuration, reload systemd and restart kubelet (this will briefly disrupt pods on that node):
sudo systemctl daemon-reload
sudo systemctl restart kubelet.service
sudo systemctl status kubelet -l
- Verify on every worker node that kubelet is running without a nonzero read-only port:
ps -fC kubelet
Confirm there is no --read-only-port flag with a value other than 0, and if a config file is in use, ensure readOnlyPort: 0 is set in /var/lib/kubelet/config.yaml.
Using kubectl
kubectl cannot modify kubelet process flags or its host-level configuration file /var/lib/kubelet/config.yaml (or /etc/kubernetes/kubelet/kubelet-config.json). This setting must be changed directly on every worker node’s OS; see the Manual Steps section for how to update the kubelet configuration and restart the service.
Automation
#!/usr/bin/env bash
#
# Remediation: Disable Kubelet read-only port on every worker node
# Scope: Run on every worker node (over SSH, via Ansible shell, or similar)
#
# This script:
# - Updates kubelet config file (/var/lib/kubelet/config.yaml) if present
# - Updates systemd drop-in kubelet args if present
# - Sets readOnlyPort/--read-only-port to 0
# - Restarts kubelet if changes were made
# - Verifies that kubelet is no longer listening on a read-only port
#
# Safe to re-run: idempotent updates and no-op if already compliant.
set -euo pipefail
changed=0
backup_file() {
local f="$1"
if [ -f "$f" ]; then
if [ ! -f "${f}.bak" ]; then
cp -p "$f" "${f}.bak"
fi
fi
}
update_kubelet_config_yaml() {
local cfg="/var/lib/kubelet/config.yaml"
[ -f "$cfg" ] || return 0
backup_file "$cfg"
# Ensure readOnlyPort is set to 0 using yq if available; fall back to sed/awk.
if command -v yq >/dev/null 2>&1; then
local tmp
tmp="$(mktemp)"
yq -Y '.readOnlyPort = 0' "$cfg" >"$tmp"
if ! cmp -s "$cfg" "$tmp"; then
mv "$tmp" "$cfg"
changed=1
else
rm -f "$tmp"
fi
else
# Minimal YAML edit: handle cases with/without existing readOnlyPort
if grep -qE '^\s*readOnlyPort\s*:' "$cfg"; then
# Replace existing value with 0
if ! grep -qE '^\s*readOnlyPort\s*:\s*0\b' "$cfg"; then
sed -i -E 's/^\s*readOnlyPort\s*:.*/readOnlyPort: 0/' "$cfg"
changed=1
fi
else
# Append at end of file
echo "" >>"$cfg"
echo "readOnlyPort: 0" >>"$cfg"
changed=1
fi
fi
}
update_systemd_kubelet_args() {
local dropin="/etc/systemd/system/kubelet.service.d/10-kubelet-args.conf"
[ -f "$dropin" ] || return 0
backup_file "$dropin"
# Normalize: ensure KUBELET_ARGS exists
if ! grep -q '^KUBELET_ARGS=' "$dropin"; then
echo 'KUBELET_ARGS=""' >>"$dropin"
fi
# Remove any existing --read-only-port flags
local tmp
tmp="$(mktemp)"
# shellcheck disable=SC2001
sed 's/--read-only-port[= ][^ "]*//g' "$dropin" >"$tmp"
# Ensure single space normalization (optional)
if ! grep -q -- '--read-only-port=0' "$tmp"; then
# Append the flag to KUBELET_ARGS line
awk '
/^KUBELET_ARGS=/ {
# Remove trailing quote, append flag, re-add quote
sub(/^KUBELET_ARGS="/, "", $0);
sub(/"$/, "", $0);
if (length($0) == 0) {
print "KUBELET_ARGS=\"--read-only-port=0\"";
} else {
print "KUBELET_ARGS=\"" $0 " --read-only-port=0\"";
}
next;
}
{ print }
' "$tmp" >"${tmp}.new"
mv "${tmp}.new" "$tmp"
fi
if ! cmp -s "$dropin" "$tmp"; then
mv "$tmp" "$dropin"
changed=1
else
rm -f "$tmp"
fi
}
restart_kubelet_if_needed() {
if [ "$changed" -eq 1 ]; then
systemctl daemon-reload
systemctl restart kubelet.service
fi
}
verify() {
echo "Verification on this node:"
echo "1) kubelet process flags:"
/bin/ps -fC kubelet || true
echo
echo "2) Effective kubelet config (if API is available):"
if command -v kubectl >/dev/null 2>&1; then
kubectl get node "$(hostname)" -o yaml 2>/dev/null | grep -i readOnlyPort || true
fi
echo
echo "3) Listening ports for kubelet read-only (ensure none except 0):"
if command -v ss >/dev/null 2>&1; then
ss -lntp | grep kubelet || true
else
netstat -lntp 2>/dev/null | grep kubelet || true
fi
}
main() {
update_kubelet_config_yaml
update_systemd_kubelet_args
restart_kubelet_if_needed
verify
}
main "$@"