Skip to main content

API Server Should Disable Service Account Extend Token

More Info:

Verifies that --service-account-extend-token-expiration is set to false so bound service account tokens are not automatically extended to long lifetimes, reducing token exposure.

Risk Level

High

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. On every control plane node, back up the existing manifest before editing:

    sudo cp -a /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.bak
  2. On every control plane node, open the kube-apiserver static pod manifest for editing:

    sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
  3. In the container command: section, either add or modify the flag so it is present exactly as:

    - --service-account-extend-token-expiration=false

    Ensure there is no other --service-account-extend-token-expiration flag with a different value in the file, then save and exit.
    Note: editing this file will cause the kube-apiserver static pod to be restarted automatically by the kubelet.

  4. Wait 30–60 seconds for the kube-apiserver pod to restart, then confirm the apiserver container is running (on any control plane node):

    sudo crictl ps | grep kube-apiserver

    (If crictl is not available, use sudo docker ps | grep kube-apiserver on nodes using Docker.)

  5. On every control plane node, verify the running process includes the correct flag value:

    /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--service-account-extend-token-expiration=false'

    Ensure this command returns at least one line and that there is no line showing --service-account-extend-token-expiration=true.

Using kubectl

kubectl cannot modify the kube-apiserver static pod manifest or its process flags, so this setting cannot be fixed via the Kubernetes API. To remediate, you must edit /etc/kubernetes/manifests/kube-apiserver.yaml directly on every control plane node; follow the Manual Steps section for the exact host-level changes.

Automation
#!/usr/bin/env bash
#
# Remediate CIS Kubernetes 1.2.30:
# Ensure --service-account-extend-token-expiration is set to false
# Target: every control plane node
#
# Usage: run as root on each control plane node.
# sudo bash ./fix-service-account-extend-token-expiration.sh

set -euo pipefail

APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
BACKUP_DIR="/etc/kubernetes/manifests/backup-cis-1.2.30"
PARAM_NAME="service-account-extend-token-expiration"
DESIRED_VALUE="false"

echo "[INFO] Starting remediation for CIS 1.2.30 on this control plane node"

if [[ $EUID -ne 0 ]]; then
echo "[ERROR] This script must be run as root." >&2
exit 1
fi

if [[ ! -f "${APISERVER_MANIFEST}" ]]; then
echo "[ERROR] kube-apiserver manifest not found at ${APISERVER_MANIFEST}" >&2
exit 1
fi

mkdir -p "${BACKUP_DIR}"

TS="$(date +%Y%m%d-%H%M%S)"
BACKUP_FILE="${BACKUP_DIR}/kube-apiserver.yaml.${TS}"
cp "${APISERVER_MANIFEST}" "${BACKUP_FILE}"
echo "[INFO] Backed up ${APISERVER_MANIFEST} to ${BACKUP_FILE}"

# Function to ensure the desired flag is present and set to false
ensure_flag() {
local file="$1"
local param="$2"
local value="$3"

# If line with parameter exists, replace it to ensure correct value
if grep -q -- "--${param}=" "${file}"; then
# If already correctly set, do nothing
if grep -q -- "--${param}=${value}" "${file}"; then
echo "[INFO] --${param} already set to ${value}; no change needed"
return 0
fi
echo "[INFO] Updating existing --${param} flag to ${value}"
# Replace the value while preserving YAML formatting
# This substitution changes only the flag value, not indentation or other content
sed -i "s#--${param}=[^\"']*#--${param}=${value}#g" "${file}"
else
echo "[INFO] Adding --${param}=${value} flag under kube-apiserver command section"
# Insert the flag after the kube-apiserver command line in the manifest.
# We look for the line containing 'kube-apiserver' in the container command list.
# This is a heuristic but is safe and idempotent: we only add if missing.
awk -v p="--${param}=${value}" '
# Once we see kube-apiserver line inside the command list, we remember and
# insert the flag on the following line (with same indentation plus two spaces).
/kube-apiserver/ && in_cmd == 0 {
print $0
# capture indentation of this line
indent = match($0, /[^ ]/)-1
pad = sprintf("%*s", indent+2, "")
print pad "- " p
in_cmd = 1
next
}
{ print $0 }
' "${file}" > "${file}.tmp"

mv "${file}.tmp" "${file"
}
}

ensure_flag "${APISERVER_MANIFEST}" "${PARAM_NAME}" "${DESIRED_VALUE}"

echo "[INFO] Changes applied to ${APISERVER_MANIFEST}."
echo "[INFO] Because this is a static pod manifest, the kube-apiserver pod will be restarted automatically by the kubelet."

# Wait for kube-apiserver process to reflect updated flag
echo "[INFO] Waiting for kube-apiserver to restart with new flags..."
SLEEP_SECS=5
MAX_WAIT_SECS=180
elapsed=0

while true; do
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--${PARAM_NAME}=${DESIRED_VALUE}"; then
echo "[INFO] Verification successful: kube-apiserver is running with --${PARAM_NAME}=${DESIRED_VALUE}"
break
fi
if (( elapsed >= MAX_WAIT_SECS )); then
echo "[ERROR] Timed out waiting for kube-apiserver to start with --${PARAM_NAME}=${DESIRED_VALUE}" >&2
echo "[INFO] Current kube-apiserver process flags:" >&2
/bin/ps -ef | grep kube-apiserver | grep -v grep || true
exit 1
fi
sleep "${SLEEP_SECS}"
elapsed=$((elapsed + SLEEP_SECS))
done

echo "[INFO] CIS 1.2.30 remediation completed on this control plane node."