Skip to main content

Ensure Encryption Provider Config Argument Is Appropriate

More Info:

Encrypt etcd key-value store.

Risk Level

Low

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. Create the EncryptionConfig file (on every control plane node)

    sudo mkdir -p /etc/kubernetes/encryption
    sudo chmod 700 /etc/kubernetes/encryption

    cat << 'EOF' | sudo tee /etc/kubernetes/encryption/encryption-config.yaml >/dev/null
    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
    - resources:
    - secrets
    - configmaps
    - persistentvolumeclaims
    providers:
    - aescbc:
    keys:
    - name: key1
    secret: REPLACE_WITH_BASE64_32_BYTE_KEY
    - identity: {}
    EOF
    sudo chmod 600 /etc/kubernetes/encryption/encryption-config.yaml
  2. Generate a strong AES key and insert it into the config (on every control plane node)

    KEY=$(head -c 32 /dev/urandom | base64)
    sudo sed -i "s/REPLACE_WITH_BASE64_32_BYTE_KEY/${KEY}/" /etc/kubernetes/encryption/encryption-config.yaml
  3. Edit the kube-apiserver static pod manifest to add the flag (on every control plane node)
    Open the manifest in an editor:

    sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml

    Under spec.containers[0].command, add this line as its own list item (or update it if present):

    - --encryption-provider-config=/etc/kubernetes/encryption/encryption-config.yaml

    Save and exit. Editing this file will automatically restart the kube-apiserver container.

  4. (If needed) Mount the directory into the kube-apiserver pod (on every control plane node)
    In the same /etc/kubernetes/manifests/kube-apiserver.yaml, ensure you have:
    Under spec.volumes:

    - name: encryption-config
    hostPath:
    path: /etc/kubernetes/encryption
    type: DirectoryOrCreate

    Under spec.containers[0].volumeMounts:

    - name: encryption-config
    mountPath: /etc/kubernetes/encryption
    readOnly: true

    Save the file; kubelet will restart the kube-apiserver again if modified.

  5. Optionally re-encrypt existing stored data (on any machine with kubectl access)
    This requires a deliberate operational decision (downtime risk, backup/restore planning). To prepare and review impact:

    kubectl get secrets --all-namespaces
    kubectl get configmaps --all-namespaces
    kubectl get pvc --all-namespaces

    Plan a maintenance window and follow the official documentation (kube-apiserver --encryption-provider-config-automatic-reload / kube-apiserver re-encryption procedures) before forcing re-encryption of existing resources.

  6. Verify the kube-apiserver is running with the encryption-provider-config flag (on every control plane node)

    /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--encryption-provider-config=/etc/kubernetes/encryption/encryption-config.yaml'

    The command should return the kube-apiserver process line containing the --encryption-provider-config argument with the expected path.

Using kubectl

kubectl cannot modify the kube-apiserver static pod manifest or its --encryption-provider-config flag; this setting is controlled directly on each control plane node in /etc/kubernetes/manifests/kube-apiserver.yaml. To remediate this finding, follow the guidance in the Manual Steps section on those nodes.

Automation
#!/usr/bin/env bash
#
# Automation: Configure --encryption-provider-config on kube-apiserver
#
# Run on: every control plane node (as root)
# Usage: sudo ./configure-apiserver-encryption.sh
#
# This script:
# - Creates an EncryptionConfig file if missing (simple aes-gcm key)
# - Binds it into the kube-apiserver static pod if not already present
# - Adds/updates --encryption-provider-config flag
# - Is idempotent and safe to re-run
# - Verifies kube-apiserver is running with the flag set
#
# NOTE: Editing /etc/kubernetes/manifests/kube-apiserver.yaml will restart the
# kube-apiserver static pod automatically via kubelet.

set -euo pipefail

APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
ENCRYPTION_CONFIG_PATH="/etc/kubernetes/pki/encryption-config.yaml"
BACKUP_SUFFIX="$(date +%Y%m%d%H%M%S)"

# ---------- helper functions ----------

backup_file() {
local file="$1"

if [ ! -f "$file" ]; then
echo "ERROR: Required file not found: $file" >&2
exit 1
fi

# Only back up once per run; keep original if already backed up earlier
local backup="${file}.bak.${BACKUP_SUFFIX}"
cp -p "$file" "$backup"
echo "Backup created: $backup"
}

ensure_dir() {
local dir="$1"
if [ ! -d "$dir" ]; then
mkdir -p "$dir"
chmod 0700 "$dir"
fi
}

# Generate a random 32-byte base64 key (deterministic per run, but safe)
generate_encryption_key() {
openssl rand -base64 32
}

# ---------- 1. Create EncryptionConfig file if missing ----------

create_encryption_config_if_missing() {
if [ -f "$ENCRYPTION_CONFIG_PATH" ]; then
echo "EncryptionConfig already exists at $ENCRYPTION_CONFIG_PATH; leaving as is."
return 0
fi

echo "Creating new EncryptionConfig at $ENCRYPTION_CONFIG_PATH"

ensure_dir "$(dirname "$ENCRYPTION_CONFIG_PATH")"

local key
key="$(generate_encryption_key)"

cat > "$ENCRYPTION_CONFIG_PATH" <<EOF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
providers:
- aescbc:
keys:
- name: key1
secret: ${key}
- identity: {}
EOF

chmod 0600 "$ENCRYPTION_CONFIG_PATH"
echo "EncryptionConfig created with aes-cbc provider for secrets and configmaps."
}

# ---------- 2. Ensure volume & volumeMount for EncryptionConfig ----------

ensure_apiserver_volume_and_mount() {
local manifest="$APISERVER_MANIFEST"
local vol_name="encryption-config"
local vol_mount_path
vol_mount_path="$(dirname "$ENCRYPTION_CONFIG_PATH")"

# Ensure volume definition exists
if grep -q "name: ${vol_name}" "$manifest"; then
echo "Volume '${vol_name}' already defined in kube-apiserver manifest."
else
echo "Adding volume '${vol_name}' to kube-apiserver manifest."
backup_file "$manifest"

# Insert under 'volumes:'; if not found, append at end
if grep -qE '^[[:space:]]*volumes:' "$manifest"; then
# Append a new volume entry after the 'volumes:' line
awk -v VNAME="$vol_name" -v VPATH="$vol_mount_path" '
BEGIN {added=0}
/^[[:space:]]*volumes:/ {
print
print " - name: " VNAME
print " hostPath:"
print " path: " VPATH
print " type: DirectoryOrCreate"
added=1
next
}
{print}
END {
if (added == 0) {
# If volumes: section not matched above (edge case)
print "volumes:"
print " - name: " VNAME
print " hostPath:"
print " path: " VPATH
print " type: DirectoryOrCreate"
}
}
' "$manifest" > "${manifest}.tmp" && mv "${manifest}.tmp" "$manifest"
else
# No volumes: section at all; append a minimal one
{
cat "$manifest"
echo ""
echo "volumes:"
echo " - name: ${vol_name}"
echo " hostPath:"
echo " path: ${vol_mount_path}"
echo " type: DirectoryOrCreate"
} > "${manifest}.tmp"
mv "${manifest}.tmp" "$manifest"
fi
fi

# Ensure volumeMount exists in container spec
if grep -q "mountPath: ${vol_mount_path}" "$manifest"; then
echo "VolumeMount for '${vol_name}' at '${vol_mount_path}' already present."
else
echo "Adding volumeMount for '${vol_name}' to kube-apiserver container."
backup_file "$manifest"

# Insert under 'volumeMounts:' for the kube-apiserver container
awk -v VNAME="$vol_name" -v VPATH="$vol_mount_path" '
BEGIN {inContainer=0; inVolumeMounts=0; added=0}
/name: kube-apiserver/ && $1 ~ /name:/ {
print
inContainer=1
next
}
inContainer == 1 && /^[[:space:]]*volumeMounts:/ {
print
print " - mountPath: " VPATH
print " name: " VNAME
print " readOnly: true"
inVolumeMounts=1
added=1
next
}
inContainer == 1 && inVolumeMounts == 0 && /^[[:space:]]*- command:/ {
# No volumeMounts section; insert one before command
print " volumeMounts:"
print " - mountPath: " VPATH
print " name: " VNAME
print " readOnly: true"
inVolumeMounts=1
added=1
print
next
}
inContainer == 1 && /^[[:space:]]*image:/ {
# End of container header without volumeMounts; add before image
if (added == 0) {
print " volumeMounts:"
print " - mountPath: " VPATH
print " name: " VNAME
print " readOnly: true"
added=1
}
print
next
}
/^[[:space:]]*-/ && !/name: kube-apiserver/ && inContainer==1 {
# Start of another container; stop container context
inContainer=0
}
{print}
' "$manifest" > "${manifest}.tmp" && mv "${manifest}.tmp" "$manifest"
fi
}

# ---------- 3. Ensure --encryption-provider-config flag ----------

ensure_encryption_flag() {
local manifest="$APISERVER_MANIFEST"
local flag="--encryption-provider-config=${ENCRYPTION_CONFIG_PATH}"

if grep -q -- "$flag" "$manifest"; then
echo "kube-apiserver manifest already contains ${flag}"
return 0
fi

echo "Adding/updating ${flag} in kube-apiserver manifest."
backup_file "$manifest"

# If an old encryption-provider-config is present, replace it
if grep -q -- "--encryption-provider-config=" "$manifest"; then
sed -E "s#--encryption-provider-config=[^[:space:]]*#${flag}#g" \
"$manifest" > "${manifest}.tmp"
mv "${manifest}.tmp" "$manifest"
return 0
fi

# Otherwise, add it into the existing command list
awk -v FLAG="$flag" '
BEGIN {inContainer=0; inCommand=0; added=0}
/name: kube-apiserver/ && $1 ~ /name:/ {
print
inContainer=1
next
}
inContainer == 1 && /^[[:space:]]*command:/ {
print
inCommand=1
next
}
inContainer == 1 && inCommand == 1 && /^[[:space:]]*-/ {
# Append flag after the last existing command entry
lastLine=$0
# Look ahead: if next non-empty non-comment line is still a command item, just print
# We handle after the loop by appending FLAG as a new item if not added.
print
next
}
inContainer == 1 && inCommand == 1 && !/^[[:space:]]*-/ {
# command list ended; insert FLAG before this line
if (added == 0) {
print " - " FLAG
added=1
}
inCommand=0
print
next
}
/^[[:space:]]*-/ && !/name: kube-apiserver/ && inContainer==1 {
inContainer=0
}
{print}
END {
if (inCommand == 1 && added == 0) {
# command block hit EOF; append FLAG as a new item
print " - " FLAG
}
}
' "$manifest" > "${manifest}.tmp" && mv "${manifest}.tmp" "$manifest"
}

# ---------- 4. Main ----------

if [ "$(id -u)" -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

create_encryption_config_if_missing
ensure_apiserver_volume_and_mount
ensure_encryption_flag

echo "Changes applied. kubelet will automatically restart kube-apiserver static pod if manifest changed."
echo "Waiting 30 seconds for kube-apiserver to restart (if needed)..."
sleep 30

# ---------- 5. Verification ----------

echo "Verification: checking kube-apiserver process for --encryption-provider-config flag"
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--encryption-provider-config=${ENCRYPTION_CONFIG_PATH}"; then
echo "SUCCESS: kube-apiserver is running with --encryption-provider-config=${ENCRYPTION_CONFIG_PATH}"
exit 0
else
echo "WARNING: kube-apiserver process does not yet show --encryption-provider-config=${ENCRYPTION_CONFIG_PATH}" >&2
echo "Run the following manually to inspect the current process:"
echo " /bin/ps -ef | grep kube-apiserver | grep -v grep"
exit 1
fi

Additional Reading: