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.
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+)
kubectlinstalled and configured with cluster accessjqinstalled for JSON processing – available viaapt install jqordnf install jqopensslinstalled 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 Type | Data Keys | Use Case |
|---|---|---|
Opaque | Any user-defined keys | General-purpose secrets (passwords, API keys, tokens) |
kubernetes.io/tls | tls.crt, tls.key | TLS certificates for Ingress and services |
kubernetes.io/dockerconfigjson | .dockerconfigjson | Private container registry authentication |
kubernetes.io/basic-auth | username, password | Basic authentication credentials |
kubernetes.io/ssh-auth | ssh-privatekey | SSH private key authentication |
kubernetes.io/service-account-token | token, ca.crt, namespace | Service account API tokens (auto-created) |
bootstrap.kubernetes.io/token | token-id, token-secret | Bootstrap 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.