> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Ensure Kubernetes Secrets Are Encrypted Using Keys Managed In Cloud KMS

### More Info:

Enable application-layer secrets encryption so Kubernetes Secrets are encrypted at rest using a Cloud KMS key you control. This protects secret data beyond Googles default encryption.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS GKE

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify the GKE cluster(s) and current encryption status**\
           Run on any machine with `gcloud` configured:
           ```bash theme={null}
           gcloud container clusters list --project <cluster_project_id>
           gcloud container clusters describe <cluster_name> \
             --location <location> \
             --project <cluster_project_id> \
             --format json | jq '.databaseEncryption'
           ```
           Review whether `state` is `"ENCRYPTED"` and a `keyName` is present. If `state` is `"DECRYPTED"` or `keyName` is empty, application‑layer secrets encryption with Cloud KMS is not enabled.

        2. **Decide the KMS project, location, and key structure**\
           With your security/ops team, determine:
           * Which project will own the KMS key (`<key_project_id>`).
           * Region (`<location>`) that matches or is appropriate for your cluster.
           * Key ring name (`<ring_name>`) and key name (`<key_name>`).\
             Document whether you will use a dedicated key per cluster, per environment, or shared across clusters, considering key‑rotation and access‑control policies.

        3. **Create or verify the Cloud KMS key**\
           If a suitable key does not already exist, create one (run on any machine with `gcloud`):
           ```bash theme={null}
           gcloud kms keyrings create <ring_name> \
             --location <location> \
             --project <key_project_id>

           gcloud kms keys create <key_name> \
             --location <location> \
             --keyring <ring_name> \
             --purpose encryption \
             --project <key_project_id>
           ```
           If using an existing key, confirm it is of purpose `ENCRYPT_DECRYPT` and in the correct region:
           ```bash theme={null}
           gcloud kms keys describe <key_name> \
             --location <location> \
             --keyring <ring_name> \
             --project <key_project_id>
           ```

        4. **Grant the GKE service agent access to the KMS key**\
           First, identify the Kubernetes Engine Service Agent for the cluster project (run on any machine with `gcloud`):
           ```bash theme={null}
           gcloud beta services identity create \
             --service container.googleapis.com \
             --project <cluster_project_id> \
             --quiet \
             --format 'value(email)'
           ```
           Then grant it `roles/cloudkms.cryptoKeyEncrypterDecrypter` on the key:
           ```bash theme={null}
           gcloud kms keys add-iam-policy-binding <key_name> \
             --location <location> \
             --keyring <ring_name> \
             --member serviceAccount:<service_account_name> \
             --role roles/cloudkms.cryptoKeyEncrypterDecrypter \
             --project <key_project_id>
           ```
           Replace `<service_account_name>` with the email printed by the previous command.

        5. **Enable application‑layer secrets encryption on the cluster**\
           For a new cluster (decide whether to recreate vs update an existing cluster):
           ```bash theme={null}
           gcloud container clusters create <cluster_name> \
             --cluster-version=latest \
             --zone <zone> \
             --database-encryption-key projects/<key_project_id>/locations/<location>/keyRings/<ring_name>/cryptoKeys/<key_name> \
             --project <cluster_project_id>
           ```
           To enable on an existing cluster:
           ```bash theme={null}
           gcloud container clusters update <cluster_name> \
             --zone <zone> \
             --database-encryption-key projects/<key_project_id>/locations/<location>/keyRings/<ring_name>/cryptoKeys/<key_name> \
             --project <cluster_project_id>
           ```
           Assess operational impact: cluster control‑plane reconfiguration may temporarily affect control‑plane availability; coordinate during a maintenance window if needed.

        6. **Verify encryption is enabled and using the intended key**\
           After the update/recreate completes, run (any machine with `gcloud`):
           ```bash theme={null}
           gcloud container clusters describe <cluster_name> \
             --location <location> \
             --project <cluster_project_id> \
             --format json | jq '.databaseEncryption'
           ```
           Confirm that `state` is `"ENCRYPTED"` and that `keyName` matches\
           `projects/<key_project_id>/locations/<location>/keyRings/<ring_name>/cryptoKeys/<key_name>`.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot configure application-layer secrets encryption or Cloud KMS keys, because this setting is managed entirely in the GKE control plane via the Google Cloud Console, `gcloud` CLI, or IaC. To remediate this finding, follow the guidance in the Manual Steps section for making the change at the cloud provider configuration level.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Check GKE clusters for Application-layer Secrets Encryption with Cloud KMS.
        #
        # REQUIREMENTS:
        # - Runs on: any machine with gcloud, jq, and access to the relevant projects.
        # - Auth: 'gcloud auth login' (or service account) and 'gcloud config set project' as needed.
        #
        # USAGE:
        #   ./check_gke_kms_encryption.sh                          # use current gcloud project
        #   GCP_PROJECTS="proj-a proj-b" ./check_gke_kms_encryption.sh
        #   GCP_PROJECTS="$(gcloud projects list --format='value(projectId)')" ./check_gke_kms_encryption.sh
        #
        # OUTPUT:
        #   Columns:
        #     PROJECT           GKE project ID
        #     CLUSTER          GKE cluster name
        #     LOCATION         Cluster location (zone or region)
        #     ENABLED          true/false if databaseEncryption is enabled
        #     KEY_RESOURCE     Full KMS key resource, or "-" if not set
        #     STATE            OK / NOT_ENCRYPTED / MISCONFIGURED
        #
        # INTERPRETATION (what indicates a problem):
        #   - STATE=NOT_ENCRYPTED
        #       => Application-layer Secrets Encryption is NOT enabled. This fails CIS GKE 5.3.1.
        #   - STATE=MISCONFIGURED
        #       => 'databaseEncryption' block exists but is missing key fields
        #          (e.g., keyName or state). Requires manual review.
        #   - STATE=OK
        #       => Application-layer Secrets Encryption is enabled and has a KMS key configured.
        #
        set -euo pipefail

        # ANSI colors for readability (can be disabled by setting NO_COLOR=1)
        if [[ "${NO_COLOR:-}" == "1" ]]; then
          RED=""; GREEN=""; YELLOW=""; RESET=""
        else
          RED="$(printf '\033[31m')"
          GREEN="$(printf '\033[32m')"
          YELLOW="$(printf '\033[33m')"
          RESET="$(printf '\033[0m')"
        fi

        PROJECTS=()
        if [[ -n "${GCP_PROJECTS:-}" ]]; then
          # Space-separated list from env var
          read -r -a PROJECTS <<< "${GCP_PROJECTS}"
        else
          # Use current gcloud project
          CURRENT_PROJECT="$(gcloud config get-value project 2>/dev/null || true)"
          if [[ -z "${CURRENT_PROJECT}" ]]; then
            echo "ERROR: No projects specified and no current gcloud project set." >&2
            echo "       Set GCP_PROJECTS or run: gcloud config set project <project_id>" >&2
            exit 1
          fi
          PROJECTS=("${CURRENT_PROJECT}")
        fi

        echo "PROJECT,CLUSTER,LOCATION,ENABLED,KEY_RESOURCE,STATE"  # CSV header

        for PROJECT in "${PROJECTS[@]}"; do
          CLUSTERS_JSON="$(gcloud container clusters list \
              --project "${PROJECT}" \
              --format=json 2>/dev/null || true)"

          if [[ -z "${CLUSTERS_JSON}" || "${CLUSTERS_JSON}" == "[]" ]]; then
            # No clusters in this project; continue silently.
            continue
          fi

          echo "${CLUSTERS_JSON}" | jq -c '.[]' | while read -r CLUSTER; do
            NAME="$(echo "${CLUSTER}" | jq -r '.name')"
            LOCATION="$(echo "${CLUSTER}" | jq -r '.location')"

            # Describe cluster to inspect databaseEncryption
            DESCRIBE_JSON="$(gcloud container clusters describe "${NAME}" \
                --location "${LOCATION}" \
                --project "${PROJECT}" \
                --format=json 2>/dev/null || true)"

            if [[ -z "${DESCRIBE_JSON}" ]]; then
              STATE="MISCONFIGURED"
              ENABLED="unknown"
              KEY_RESOURCE="-"
              printf '%s,"%s",%s,%s,%s,%s%s%s\n' \
                "${PROJECT}" "${NAME}" "${LOCATION}" "${ENABLED}" "${KEY_RESOURCE}" \
                "${YELLOW}" "${STATE}" "${RESET}"
              continue
            fi

            DB_ENCRYPTION="$(echo "${DESCRIBE_JSON}" | jq -c '.databaseEncryption // {}')"
            RAW_STATE="$(echo "${DB_ENCRYPTION}" | jq -r '.state // empty')"
            KEY_NAME="$(echo "${DB_ENCRYPTION}" | jq -r '.keyName // empty')"

            if [[ -z "${RAW_STATE}" ]]; then
              # No state field at all => not configured
              ENABLED="false"
              KEY_RESOURCE="-"
              STATE="NOT_ENCRYPTED"
              COLOR="${RED}"
            elif [[ "${RAW_STATE}" != "ENCRYPTED" ]]; then
              # State present but not ENCRYPTED (e.g., DECRYPTED or UNKNOWN)
              ENABLED="false"
              KEY_RESOURCE="${KEY_NAME:-"-"}"
              STATE="NOT_ENCRYPTED"
              COLOR="${RED}"
            else
              # State == ENCRYPTED, verify keyName present
              if [[ -z "${KEY_NAME}" ]]; then
                ENABLED="true"
                KEY_RESOURCE="-"
                STATE="MISCONFIGURED"
                COLOR="${YELLOW}"
              else
                ENABLED="true"
                KEY_RESOURCE="${KEY_NAME}"
                STATE="OK"
                COLOR="${GREEN}"
              fi
            fi

            printf '%s,"%s",%s,%s,"%s",%s%s%s\n' \
              "${PROJECT}" "${NAME}" "${LOCATION}" "${ENABLED}" "${KEY_RESOURCE}" \
              "${COLOR}" "${STATE}" "${RESET}"
          done
        done
        ```

        **What indicates a problem in the script output**

        * Any row with `STATE=NOT_ENCRYPTED`:
          * The `databaseEncryption` block is missing, or
          * `.databaseEncryption.state` is not `ENCRYPTED`.
          * This means Kubernetes Secrets are **not** protected with Cloud KMS as required.
        * Any row with `STATE=MISCONFIGURED`:
          * `state` is `ENCRYPTED` but `keyName` is missing, or the cluster could not be described.
          * Requires manual review to ensure a valid Cloud KMS key is configured.
        * Only rows with `STATE=OK` meet the requirement for CIS GKE 5.3.1.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
