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
Remediation
Manual Steps
-
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.
-
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
- On every control plane node with SSH access, display the API server manifest:
-
Assess whether encryption at rest is enabled and how
- In the manifest you collected, look for the
--encryption-provider-configflag in thekube-apiservercontainer 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
resourcesstanza including at leastsecrets providersordered from strongest to weakest (e.g.,aescbcorkmsbeforeidentity).
- In the manifest you collected, look for the
-
Decide required changes based on policy and provider capabilities
- If encryption is not enabled (no
--encryption-provider-configor nosecretsresource): 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 afteridentity, or keys are too old / not rotated): decide whether to:- Introduce or prioritize a strong provider (
aescbcor cloud KMS) - Rotate encryption keys and re-encrypt existing data
- Introduce or prioritize a strong provider (
- 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).
- If encryption is not enabled (no
-
Apply configuration changes via the cloud provider or control-plane configuration mechanism
- For managed AKS clusters, use only supported methods (Azure Portal,
az aksCLI, 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-apiserverand the encryption config file according to Kubernetes documentation and your policy; ensuresecretsis listed and providers are ordered correctly. - Be aware that editing
/etc/kubernetes/manifests/kube-apiserver.yamlon control-plane nodes will restart the API server pod.
- For managed AKS clusters, use only supported methods (Azure Portal,
-
Verify the resulting state
- For self-hosted clusters:
Confirm that the flag is present andsudo cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep -- '--encryption-provider-config'sudo cat /etc/kubernetes/encryption-config.yaml
secretsare 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.
- For self-hosted clusters:
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/showcommands (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.