Controller Manager Should Use Individual Service Account
More Info:
Verifies that --use-service-account-credentials is set to true so each controller uses its own service account, enabling least-privilege RBAC for control loops.
Risk Level
High
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On every control plane node, back up the existing manifest:
sudo cp -a /etc/kubernetes/manifests/kube-controller-manager.yaml /etc/kubernetes/manifests/kube-controller-manager.yaml.backup.$(date +%s) -
Open the controller manager manifest for editing:
sudo vi /etc/kubernetes/manifests/kube-controller-manager.yaml -
In the
spec.containers[0].commandsection, ensure the following flag is present and set to true (add it if missing):- --use-service-account-credentials=trueExample snippet:
spec:containers:- name: kube-controller-managercommand:- kube-controller-manager- --use-service-account-credentials=true# ...other existing flags... -
Save and exit the editor. The kube-controller-manager static pod will be automatically restarted by the kubelet because the manifest under
/etc/kubernetes/manifestswas modified. -
Wait 30–60 seconds, then confirm the controller manager process is running with the correct flag:
ps -ef | grep kube-controller-manager | grep -v grep -
Verify that the output includes
--use-service-account-credentials=truein the kube-controller-manager command line.
Using kubectl
kubectl cannot modify the kube-controller-manager static pod manifest or its process flags, so this setting cannot be fixed via the Kubernetes API. To remediate, edit /etc/kubernetes/manifests/kube-controller-manager.yaml directly on every control plane node as described in the Manual Steps section.
Automation
#!/usr/bin/env bash
#
# Remediation: Ensure kube-controller-manager uses --use-service-account-credentials=true
# Scope: Run on every control plane node
# Idempotent: Yes – safe to re-run
set -euo pipefail
MANIFEST="/etc/kubernetes/manifests/kube-controller-manager.yaml"
BACKUP_DIR="/etc/kubernetes/manifests/backup-controller-manager-sa-creds"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
echo "=== Ensuring kube-controller-manager uses individual service account credentials ==="
# 1) Preconditions
if [ "$(id -u)" -ne 0 ]; then
echo "ERROR: This script must be run as root (to edit ${MANIFEST})." >&2
exit 1
fi
if [ ! -f "${MANIFEST}" ]; then
echo "ERROR: Manifest not found: ${MANIFEST}" >&2
exit 1
fi
# 2) Backup current manifest once per run (kept with timestamp)
mkdir -p "${BACKUP_DIR}"
cp "${MANIFEST}" "${BACKUP_DIR}/kube-controller-manager.yaml.${TIMESTAMP}"
echo "Backup created at ${BACKUP_DIR}/kube-controller-manager.yaml.${TIMESTAMP}"
# 3) Normalize line endings to avoid sed issues
dos2unix "${MANIFEST}" >/dev/null 2>&1 || true
# 4) Ensure the flag exists and is set to true
#
# Logic:
# - If --use-service-account-credentials is already present, force to =true.
# - Else, add it as a new - --use-service-account-credentials=true line
# under the args: section (preferred) or after the kube-controller-manager
# command line if args: is absent.
echo "Patching ${MANIFEST} to set --use-service-account-credentials=true"
# If flag exists anywhere, set it to true in-place
if grep -q -- "--use-service-account-credentials" "${MANIFEST}"; then
# Handle cases like:
# - --use-service-account-credentials=false
# - --use-service-account-credentials
sed -i -E \
's|(--use-service-account-credentials)(=[^"[:space:]]*)?|\1=true|g' \
"${MANIFEST}"
else
# Flag not present; add it.
# Prefer to inject under args: if present.
if grep -qE '^[[:space:]]*args:' "${MANIFEST}"; then
# Insert after the "args:" line, indented with two spaces and dash.
# This keeps yaml structure valid in common kubeadm layouts.
awk '
/^[[:space:]]*args:[[:space:]]*$/ && inserted == 0 {
print $0
print " - --use-service-account-credentials=true"
inserted=1
next
}
{ print $0 }
END {
if (inserted == 0) {
# fallback if args: pattern somehow not matched
print " args:"
print " - --use-service-account-credentials=true"
}
}
' "${MANIFEST}" > "${MANIFEST}.tmp"
mv "${MANIFEST}.tmp" "${MANIFEST}"
else
# No args: section; append an args block near the end of the container spec.
# This is a best-effort generic insertion that keeps yaml valid in most
# standard kubeadm-generated manifests.
awk '
BEGIN { inserted=0 }
/image:.*kube-controller-manager/ && inserted == 0 {
print $0
next
}
/command:|args:/ && inserted == 0 { seen_block=1 }
/- kube-controller-manager/ && inserted == 0 { seen_cmd=1 }
/^[[:space:]]*resources:/ && inserted == 0 && seen_cmd == 1 {
print " args:"
print " - --use-service-account-credentials=true"
inserted=1
}
{ print $0 }
END {
if (inserted == 0) {
print " args:"
print " - --use-service-account-credentials=true"
}
}
' "${MANIFEST}" > "${MANIFEST}.tmp"
mv "${MANIFEST}.tmp" "${MANIFEST}"
fi
fi
echo "Manifest updated. kube-controller-manager static pod will be restarted automatically by kubelet."
# 5) Wait for kube-controller-manager to restart and apply new args
echo "Waiting for kube-controller-manager process to reflect new flag..."
# Wait up to 120 seconds for the flag to appear
END_TIME=$((SECONDS + 120))
while [ ${SECONDS} -lt ${END_TIME} ]; do
if /bin/ps -ef | grep kube-controller-manager | grep -v grep | grep -q -- "--use-service-account-credentials=true"; then
break
fi
sleep 5
done
# 6) Verification (adapted from audit command)
echo "=== Verification ==="
/bin/ps -ef | grep kube-controller-manager | grep -v grep || {
echo "ERROR: kube-controller-manager process not found." >&2
exit 1
}
if /bin/ps -ef | grep kube-controller-manager | grep -v grep | grep -q -- "--use-service-account-credentials=true"; then
echo "PASS: kube-controller-manager is running with --use-service-account-credentials=true"
exit 0
else
echo "FAIL: kube-controller-manager is NOT running with --use-service-account-credentials=true" >&2
echo "Inspect ${MANIFEST} and kubelet logs for errors." >&2
exit 1
fi