Skip to main content

Ensure Kubernetes Secrets Are Encrypted

More Info:

Where etcd encryption is used, appropriate providers should be configured.

Risk Level

Medium

Address

Security

Compliance Standards

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS AKS
  • CIS Critical Security Controls v8
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • 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
  • Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework
  • UK NCSC Cyber Assessment Framework

Triage and Remediation

Remediation

Manual Steps
  1. Identify whether the API server is managed or self-hosted

    • On any machine with access to the cloud console, confirm if this AKS cluster uses a fully managed control plane (standard AKS) or if you have direct control plane node access (e.g., custom/on-prem).
    • If you do not have SSH or file access to /etc/kubernetes/manifests/kube-apiserver.yaml, you must rely on cloud-provider documentation and settings to determine if secret encryption at rest is enabled and how it is configured.
  2. Collect the current API server configuration (self-hosted / direct node access only)

    • On every control plane node with SSH access, display the API server manifest:
      sudo cat /etc/kubernetes/manifests/kube-apiserver.yaml
    • Save a copy for review:
      sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/kube-apiserver.yaml.backup
  3. Assess whether encryption at rest is enabled and how

    • In the manifest you collected, look for the --encryption-provider-config flag in the kube-apiserver container command:
      • If absent, etcd encryption for Secrets is not configured.
      • If present, note the absolute path to the encryption config file (for example: /etc/kubernetes/encryption-config.yaml).
    • On the control plane node, if you have file access, inspect that file:
      sudo cat /etc/kubernetes/encryption-config.yaml
    • Confirm:
      • kind: EncryptionConfiguration
      • A resources stanza including at least secrets
      • providers ordered from strongest to weakest (e.g., aescbc or kms before identity).
  4. Decide required changes based on policy and provider capabilities

    • If encryption is not enabled (no --encryption-provider-config or no secrets resource): decide whether your security policy requires etcd-level encryption in addition to disk-level encryption already provided by the cloud provider.
    • If encryption is enabled but weakly configured (e.g., only identity, or strong provider appears after identity, or keys are too old / not rotated): decide whether to:
      • Introduce or prioritize a strong provider (aescbc or cloud KMS)
      • Rotate encryption keys and re-encrypt existing data
    • If using a managed control plane with no manifest access: review your provider’s settings (console/CLI/IaC) for “Secrets encryption at rest” / “KMS integration” and compare them with your policy (required providers, key type, rotation policy).
  5. Apply configuration changes via the cloud provider or control-plane configuration mechanism

    • For managed AKS clusters, use only supported methods (Azure Portal, az aks CLI, or IaC like ARM/Bicep/Terraform) to:
      • Enable secret encryption at rest if currently disabled.
      • Configure or update the encryption provider (e.g., Azure Key Vault-backed keys) as per your policy.
    • For self-hosted clusters with node access, update kube-apiserver and the encryption config file according to Kubernetes documentation and your policy; ensure secrets is listed and providers are ordered correctly.
    • Be aware that editing /etc/kubernetes/manifests/kube-apiserver.yaml on control-plane nodes will restart the API server pod.
  6. Verify the resulting state

    • For self-hosted clusters:
      sudo cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep -- '--encryption-provider-config'
      sudo cat /etc/kubernetes/encryption-config.yaml
      Confirm that the flag is present and secrets are covered by a strong provider with correct ordering.
    • For managed AKS clusters: re-check the cloud provider console/CLI/IaC outputs to confirm that secret encryption at rest is enabled and that the configured provider and keys match your intended design.
Using kubectl

kubectl cannot configure encryption at rest for Kubernetes Secrets, because this setting lives in the managed control plane (for AKS) rather than in API objects you can edit. Review and implement the required changes using the cloud provider’s portal/CLI/IaC as described in the Manual Steps section.

Automation
#!/usr/bin/env bash
#
# CIS AKS 5.3.1 – Automation helper
# Purpose: Collect evidence about Kubernetes secret encryption at rest
# Scope: Any machine with kubectl access and (optionally) cloud CLI access
#
# Requirements:
# - kubectl configured with cluster-admin permissions
# - For AKS: az CLI (optional, best-effort check)
# - For EKS: aws CLI (optional, best-effort check)
# - For GKE: gcloud CLI (optional, best-effort check)
#
# NOTE: This script does NOT change any configuration. It only reports
# state so that you can perform a manual review.

set -euo pipefail

echo "=== 1) Basic cluster and API server information (kubectl) ==="
kubectl version --short || echo "WARN: kubectl version failed"
echo

echo "--- Server configuration summary ---"
kubectl get componentstatuses 2>/dev/null || echo "INFO: componentstatuses API not available (newer clusters)"
kubectl get pods -n kube-system -o wide | grep -iE 'apiserver|control' || true
echo

echo "=== 2) Managed control plane – best-effort provider checks (if configured) ==="

# Detect provider via kube context name / server URL (best-effort heuristic)
CTX="$(kubectl config current-context || echo '')"
SERVER="$(kubectl config view -o jsonpath='{.clusters[?(@.name=="'"$(kubectl config view -o jsonpath='{.current-context}')"'")].cluster.server}' 2>/dev/null || echo '')"

echo "Current context : ${CTX}"
echo "API server URL : ${SERVER}"
echo

# AKS
if command -v az >/dev/null 2>&1; then
echo "--- Attempting AKS encryption check via az CLI (best effort) ---"
# This requires you to substitute correct resource group and cluster name
echo "NOTE: This is a template. Replace <RESOURCE_GROUP> and <CLUSTER_NAME> with actual values."
echo "Example command to run manually for each AKS cluster:"
echo " az aks show -g <RESOURCE_GROUP> -n <CLUSTER_NAME> --query 'securityProfile' -o json"
echo
fi

# EKS
if command -v aws >/dev/null 2>&1; then
echo "--- Attempting EKS encryption check via aws CLI (best effort) ---"
echo "NOTE: This is a template. Replace <REGION> and <CLUSTER_NAME> with actual values."
echo "Example command to run manually for each EKS cluster:"
echo " aws eks describe-cluster --region <REGION> --name <CLUSTER_NAME> \\"
echo " --query 'cluster.encryptionConfig' -o json"
echo
fi

# GKE
if command -v gcloud >/dev/null 2>&1; then
echo "--- Attempting GKE encryption check via gcloud CLI (best effort) ---"
echo "NOTE: This is a template. Replace <PROJECT_ID>, <LOCATION>, and <CLUSTER_NAME>."
echo "Example commands to run manually for each GKE cluster:"
echo " gcloud container clusters describe <CLUSTER_NAME> \\"
echo " --project=<PROJECT_ID> --region=<LOCATION> \\"
echo " --format='json(databaseEncryption,masterAuthorizedNetworksConfig)'"
echo
fi

echo "=== 3) Secrets sampling – show how they are currently stored (for manual test) ==="
NAMESPACE="kube-system"
TEST_SECRET_NAME="cis-aks-5-3-1-encryption-test-$(date +%s)"

echo "--- Creating a test secret in namespace: ${NAMESPACE} ---"
kubectl create secret generic "${TEST_SECRET_NAME}" \
-n "${NAMESPACE}" \
--from-literal=example-key='SensitiveTestValue123!' 1>/dev/null

echo "Created secret: ${NAMESPACE}/${TEST_SECRET_NAME}"
echo

echo "--- Retrieving test secret via API (kubectl) ---"
kubectl get secret "${TEST_SECRET_NAME}" -n "${NAMESPACE}" -o yaml

echo
echo "The value above SHOULD be base64-encoded in the 'data' field; this is normal API behavior"
echo "(this does NOT prove at-rest encryption in etcd)."
echo

echo "=== 4) Guidance for etcd-level verification (MANUAL) ==="
cat <<'EOF'

To truly verify encryption at rest of Secrets, you must inspect how they are
stored in etcd on the control plane. For a managed control plane, direct etcd
access is typically not available; instead you must rely on:

- Provider-specific configuration for secret / etcd encryption
- Control plane API or console/portal settings
- Documentation / configuration of "encryption at rest" features

What indicates a PROBLEM:

- Provider configuration / describe-cluster output shows:
* No encryptionConfig (EKS) or
* databaseEncryption.state != 'ENCRYPTED' (GKE) or
* no keyVaultKms or similar key configuration (AKS)
- Provider docs for the configured SKU / tier do not guarantee
encryption of Kubernetes Secrets at rest.
- For self-managed control planes (not this AKS CIS control, but in general):
* The kube-apiserver manifest lacks an --encryption-provider-config flag, or
* The referenced encryption config file exists but does not list 'secrets'
under any encryption provider.

Since this check is MANUAL, you must:

1) Review your cloud provider encryption-at-rest configuration for the cluster.
2) Confirm that Kubernetes Secrets are covered (not only disks / snapshots).
3) Document which KMS/key is used, and any exclusions.

EOF

echo "=== 5) Cleanup test secret ==="
kubectl delete secret "${TEST_SECRET_NAME}" -n "${NAMESPACE}" 1>/dev/null
echo "Deleted test secret: ${NAMESPACE}/${TEST_SECRET_NAME}"
echo
echo "Done. Review the provider-specific output as described above to decide compliance for CIS AKS 5.3.1."

How to interpret output for problems

  • If provider-specific describe/show commands (ak/eks/gke templates shown by the script) indicate:
    • no encryption configuration, or
    • encryption is DISABLED/DECRYPTED/empty, or
    • Secrets are not explicitly covered,

then the cluster likely does not meet the intent of “Ensure Kubernetes Secrets Are Encrypted” and needs a design/ configuration change through the cloud provider control plane, not an automated script fix.

Additional Reading: