Kubernetes

Decode and Decrypt Kubernetes Secrets

Kubernetes Secrets store sensitive data like passwords, tokens, and TLS certificates. A common misconception is that Secrets are encrypted – they are not. By default, Kubernetes stores Secrets as base64-encoded strings in etcd, which is encoding, not encryption. Anyone with API access can decode them instantly. This guide covers every method to decode Kubernetes Secrets, plus how to actually encrypt them at rest.

Original content from computingforgeeks.com - post 67841

We will walk through decoding Secrets using kubectl, base64, jsonpath, and jq. We also cover TLS certificate secrets, Docker registry secrets, editing secrets in place, enabling encryption at rest with EncryptionConfiguration, and integrating external secret managers. For the official reference on how Kubernetes handles Secrets, see the Kubernetes Secrets documentation.

Prerequisites

Before you begin, make sure you have the following in place:

  • A running Kubernetes cluster (any version 1.24+)
  • kubectl installed and configured with cluster access
  • jq installed for JSON processing – available via apt install jq or dnf install jq
  • openssl installed for TLS certificate inspection
  • RBAC permissions to read Secrets in the target namespace (or cluster-admin for all namespaces)

Step 1: View Secrets in a Namespace

Start by listing all Secrets in your target namespace. This gives you the Secret names, types, and the number of data keys each one holds.

kubectl get secrets -n default

The output shows each Secret with its type and number of stored keys:

NAME                  TYPE                                  DATA   AGE
db-credentials        Opaque                                2      5d
app-tls               kubernetes.io/tls                     2      3d
registry-auth         kubernetes.io/dockerconfigjson        1      7d
default-token-x9kzl   kubernetes.io/service-account-token   3      30d

To see Secrets across all namespaces, use the --all-namespaces flag:

kubectl get secrets --all-namespaces

To inspect a specific Secret and see its keys (without the actual values), use describe:

kubectl describe secret db-credentials -n default

The describe output shows the key names and the byte size of each value, but not the values themselves:

Name:         db-credentials
Namespace:    default
Type:         Opaque

Data
====
username:  5 bytes
password:  12 bytes

Step 2: Decode Kubernetes Secrets with kubectl and base64

The most straightforward way to decode a Secret is to get the base64-encoded value with kubectl and pipe it through base64 --decode. First, retrieve the Secret in YAML format to see the encoded data:

kubectl get secret db-credentials -n default -o yaml

The output includes the base64-encoded values under the data field:

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: default
type: Opaque
data:
  username: YWRtaW4=
  password: UEBzc3cwcmQxMjM=

Now decode each value individually with base64:

echo "YWRtaW4=" | base64 --decode

The decoded username value is returned as plain text:

admin

Decode the password the same way:

echo "UEBzc3cwcmQxMjM=" | base64 --decode

The decoded password is returned in plain text:

P@ssw0rd123

Step 3: Decode Secrets with jsonpath

Using jsonpath output formatting, you can extract and decode a specific key in a single command without needing to copy-paste encoded values. This is faster for one-off lookups.

kubectl get secret db-credentials -n default -o jsonpath='{.data.username}' | base64 --decode

This returns the decoded username directly:

admin

For the password key:

kubectl get secret db-credentials -n default -o jsonpath='{.data.password}' | base64 --decode

The decoded password is printed to stdout:

P@ssw0rd123

If the key name contains dots or special characters, wrap it in bracket notation:

kubectl get secret my-secret -n default -o jsonpath='{.data.ca\.crt}' | base64 --decode

Step 4: Decode All Secret Keys at Once with jq

When a Secret has multiple keys, decoding them one by one is tedious. Use jq to decode every key in a single command. This is especially useful for Secrets with many data entries.

kubectl get secret db-credentials -n default -o json | jq -r '.data | to_entries[] | "\(.key): \(.value | @base64d)"'

All key-value pairs are decoded and printed in a readable format:

username: admin
password: P@ssw0rd123

To decode all Secrets in a namespace at once, combine with a loop. This is helpful during debugging or audit sessions:

for secret in $(kubectl get secrets -n default -o jsonpath='{.items[*].metadata.name}'); do
  echo "=== $secret ==="
  kubectl get secret "$secret" -n default -o json | jq -r '.data // {} | to_entries[] | "\(.key): \(.value | @base64d)"'
  echo ""
done

Step 5: Decode TLS Certificate Secrets

TLS Secrets (type kubernetes.io/tls) store a certificate and private key under tls.crt and tls.key. You can decode and inspect the certificate details using openssl. If you manage TLS in your cluster with cert-manager on Kubernetes, these commands are essential for troubleshooting certificate issues.

Extract and decode the certificate:

kubectl get secret app-tls -n default -o jsonpath='{.data.tls\.crt}' | base64 --decode > /tmp/tls.crt

View the certificate details – subject, issuer, validity dates, and SANs:

openssl x509 -in /tmp/tls.crt -text -noout

The output shows the full certificate information including the issuer, expiry date, and subject alternative names:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 01:23:45:67:89:ab:cd:ef
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = Let's Encrypt Authority X3
        Validity
            Not Before: Mar 10 00:00:00 2026 GMT
            Not After : Jun  8 00:00:00 2026 GMT
        Subject: CN = app.example.com
        ...
        X509v3 Subject Alternative Name:
            DNS:app.example.com, DNS:www.app.example.com

To quickly check just the expiry date without the full certificate dump, use this shorter form. For more ways to check SSL certificate expiration with OpenSSL, see our dedicated guide.

kubectl get secret app-tls -n default -o jsonpath='{.data.tls\.crt}' | base64 --decode | openssl x509 -enddate -noout

This returns a single line with the expiry date:

notAfter=Jun  8 00:00:00 2026 GMT

Extract the private key if needed (handle with care – this is the actual private key):

kubectl get secret app-tls -n default -o jsonpath='{.data.tls\.key}' | base64 --decode

Clean up the temporary file when done:

rm -f /tmp/tls.crt

Step 6: Decode Docker Registry Secrets

Docker registry Secrets (type kubernetes.io/dockerconfigjson) store container registry authentication credentials. These are used when pulling images from private registries. If you use a private registry like Harbor, you may have created these secrets when adding Harbor registry pull secrets to Kubernetes.

The data is stored under the key .dockerconfigjson and is double-encoded – the entire JSON config is base64-encoded, and within it, the auth field is another base64-encoded string of username:password.

First, decode the outer layer to see the JSON structure:

kubectl get secret registry-auth -n default -o jsonpath='{.data.\.dockerconfigjson}' | base64 --decode | jq .

The decoded JSON shows the registry URL and the still-encoded auth token:

{
  "auths": {
    "https://registry.example.com": {
      "username": "deploy-user",
      "password": "registry-token-abc123",
      "auth": "ZGVwbG95LXVzZXI6cmVnaXN0cnktdG9rZW4tYWJjMTIz"
    }
  }
}

To decode the auth field (which is username:password in base64):

kubectl get secret registry-auth -n default -o jsonpath='{.data.\.dockerconfigjson}' | base64 --decode | jq -r '.auths[].auth' | base64 --decode

The fully decoded credentials are displayed as username:password:

deploy-user:registry-token-abc123

Step 7: Edit Secrets in Place

Sometimes you need to update a Secret value directly. You can edit Secrets in place using kubectl edit, but remember that values must be base64-encoded when you save them.

Open the Secret in your default editor:

kubectl edit secret db-credentials -n default

This opens the Secret YAML in your editor. To change the password, first encode the new value:

echo -n "NewP@ssw0rd456" | base64

The encoded value is returned – copy this to paste into the editor:

TmV3UEBzc3cwcmQ0NTY=

Replace the old base64 value in the editor with the new one, save, and exit. The Secret updates immediately.

Alternatively, use kubectl patch for a non-interactive update. This is better for scripting and automation:

kubectl patch secret db-credentials -n default -p '{"data":{"password":"TmV3UEBzc3cwcmQ0NTY="}}'

Or replace the entire Secret from a literal value using kubectl create secret with the --dry-run flag piped to kubectl apply:

kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=NewP@ssw0rd456 \
  -n default --dry-run=client -o yaml | kubectl apply -f -

Verify the updated value:

kubectl get secret db-credentials -n default -o jsonpath='{.data.password}' | base64 --decode

The new password should be returned:

NewP@ssw0rd456

Important: Pods that already mounted this Secret will not pick up the change automatically unless they use Secrets as environment variables (which require a pod restart) or as volume mounts (which Kubernetes updates after a short delay, typically 1-2 minutes).

Step 8: Encrypt Secrets at Rest with EncryptionConfiguration

Base64 encoding provides zero security. Anyone with access to etcd or the Kubernetes API can read your Secrets in plain text. To actually protect Secret data, enable encryption at rest. This encrypts Secrets before they are written to etcd. The full upstream documentation is available at Encrypting Secret Data at Rest.

Create an encryption configuration file on each control plane node. First, generate a 32-byte encryption key:

head -c 32 /dev/urandom | base64

Save the generated key – you will use it in the configuration file below.

Create the encryption configuration file:

sudo vi /etc/kubernetes/encryption-config.yaml

Add the following configuration, replacing the secret value with the key you generated:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: YOUR_BASE64_ENCODED_KEY_HERE
      - identity: {}

The aescbc provider encrypts Secrets with AES-CBC. The identity provider at the end allows reading existing unencrypted Secrets. The order matters – the first provider is used for encryption, and all listed providers are tried for decryption.

Set restrictive permissions on the file:

sudo chmod 600 /etc/kubernetes/encryption-config.yaml

Update the kube-apiserver configuration to use this encryption config. Edit the API server manifest:

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

Add the following flag to the command section of the kube-apiserver container spec:

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

Also add a volume mount for the encryption config file under volumeMounts:

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

And the corresponding volume under volumes:

- name: encryption-config
  hostPath:
    path: /etc/kubernetes/encryption-config.yaml
    type: File

The API server restarts automatically after saving the manifest (since it is a static pod). Wait for it to come back up and verify it is running:

kubectl get pods -n kube-system -l component=kube-apiserver

Existing Secrets are still stored unencrypted. To encrypt all existing Secrets, re-write them by running:

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

Verify a Secret is encrypted in etcd by reading it directly (requires etcd access). If you see encrypted data instead of base64 values, encryption at rest is working. For more on etcd management, see our guide on installing etcd on Ubuntu.

Step 9: Use External Secret Managers

For production environments, storing secrets directly in Kubernetes – even with encryption at rest – has limitations. You cannot audit access, rotate secrets automatically, or manage secrets across multiple clusters from one place. External secret managers solve these problems.

The most common external secret managers used with Kubernetes are:

  • HashiCorp Vault – the most widely adopted secret manager. Supports dynamic secrets, automatic rotation, fine-grained access policies, and audit logging. Can be deployed inside or outside the cluster
  • AWS Secrets Manager – managed service for AWS environments. Integrates with EKS through IAM Roles for Service Accounts (IRSA)
  • Azure Key Vault – managed service for Azure/AKS environments. Integrates through Workload Identity
  • Google Secret Manager – managed service for GCP/GKE environments. Integrates through Workload Identity Federation

The External Secrets Operator is the standard Kubernetes controller for syncing secrets from these external providers into native Kubernetes Secrets. It watches ExternalSecret custom resources and creates corresponding Kubernetes Secrets.

Here is an example ExternalSecret resource that pulls a database password from HashiCorp Vault:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: default
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
    - secretKey: username
      remoteRef:
        key: secret/data/production/db
        property: username
    - secretKey: password
      remoteRef:
        key: secret/data/production/db
        property: password

The External Secrets Operator fetches the values from Vault every hour (per refreshInterval) and creates a native Kubernetes Secret named db-credentials. Your pods consume it like any other Secret – no application changes needed.

For Vault-specific setup, including deploying Vault on Kubernetes with Helm and Terraform, see our guide on installing a Vault cluster on GKE.

Kubernetes Secret Types Reference

This table lists the built-in Secret types in Kubernetes, their expected data keys, and common use cases:

Secret TypeData KeysUse Case
OpaqueAny user-defined keysGeneral-purpose secrets (passwords, API keys, tokens)
kubernetes.io/tlstls.crt, tls.keyTLS certificates for Ingress and services
kubernetes.io/dockerconfigjson.dockerconfigjsonPrivate container registry authentication
kubernetes.io/basic-authusername, passwordBasic authentication credentials
kubernetes.io/ssh-authssh-privatekeySSH private key authentication
kubernetes.io/service-account-tokentoken, ca.crt, namespaceService account API tokens (auto-created)
bootstrap.kubernetes.io/tokentoken-id, token-secretBootstrap tokens for node joining

Conclusion

Kubernetes Secrets are base64-encoded by default – not encrypted. You now have the tools to decode them using kubectl, base64, jsonpath, and jq, including specialized types like TLS certificates and Docker registry credentials. For production clusters, enable encryption at rest with EncryptionConfiguration as a minimum, and consider external secret managers like HashiCorp Vault or your cloud provider’s secret management service for proper access control, auditing, and automatic rotation.

Restrict Secret access through Kubernetes RBAC policies and audit who reads Secrets using Kubernetes audit logging. Never store Secrets in Git repositories or CI/CD pipeline logs.

Related Articles

Containers How To Manage Docker Containers & Images in Linux Containers Using pfSense as Load Balancer for Kubernetes API port 6443 Cloud How to deploy Redis StatefulSet Cluster in Kubernetes Containers How To Install Snap on Ubuntu or Debian

Leave a Comment

Press ESC to close