Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Kubernetes Secrets Management Done Right

By Kokil Thapa | Last reviewed: August 2026

Storing sensitive credentials as standard Kubernetes Secrets is a security liability because they are merely base64-encoded and stored unencrypted in etcd by default. Achieving Kubernetes Secrets Management Done Right requires integrating an external secret store, enabling encryption at rest on the API server, and enforcing strict role-based access controls to prevent credential leakage. This guide covers the exact architecture and configuration needed to secure production workloads in 2026, whether you are running on managed cloud infrastructure or self-hosted clusters.

For teams building scalable and efficient systems, treating secrets as immutable infrastructure artifacts rather than mutable configuration is the first step toward operational maturity. The native Kubernetes Secret resource was designed for convenience, not high-security compliance, which is why production environments must layer additional controls to meet audit and regulatory requirements.

Why Are Native Kubernetes Secrets Insufficient for Production?

The default Kubernetes Secret resource provides obfuscation, not security. When you run kubectl create secret generic db-pass --from-literal=password=mysecret, the value is base64-encoded and written directly to etcd. Anyone with read access to the etcd datastore, or any pod with permission to list secrets in that namespace, can retrieve the plaintext credential instantly. In my experience working on production Laravel applications deployed to Kubernetes, relying solely on native secrets creates three critical vulnerabilities that fail security audits immediately.

Native K8s Secret Riskskubectl create secretBase64 Encoded Onlyetcd DatastoreUNENCRYPTED AT RESTAny Pod / UserWith List PermissionCritical Vulnerabilities• No encryption key rotation • Audit logs incomplete • Version history exposed• Namespace-wide access too broad • No automatic secret rotation
Native Kubernetes Secrets expose credentials through unencrypted etcd storage and overly permissive RBAC defaults

First, there is no built-in mechanism for automatic rotation. If a database password leaks, every deployment manifest, CI/CD variable, and local developer environment must be updated manually. Second, version control systems often accidentally capture these manifests during development, creating permanent credential exposure in git history. Third, audit logging for secret access is minimal in vanilla Kubernetes; you cannot easily determine who accessed a production database credential last Tuesday at 3 AM.

This is why Kubernetes Secrets Management Done Right always involves decoupling the secret lifecycle from the cluster lifecycle. The cluster should only ever hold short-lived, automatically synced references to authoritative secrets stored in a dedicated secrets manager. For teams evaluating current cybersecurity trends, this separation of concerns is now considered baseline hygiene, not advanced hardening.

How Do You Implement External Secrets Operator with Vault?

External Secrets Operator (ESO) has become the de facto standard for bridging Kubernetes and external secret stores in 2026. It runs as a controller inside your cluster, polls your external provider, and creates native Kubernetes Secrets that stay synchronized. This approach gives you the security of an external vault with the compatibility of native secret consumption in pods.

Install and Configure ESO with Helm

helm repo add external-secrets https://charts.external-secrets.io
helm repo update

helm install external-secrets \
  external-secrets/external-secrets \
  -n external-secrets \
  --create-namespace \
  --set installCRDs=true \
  --set webhook.port=9443

After installation, configure a SecretStore resource that authenticates to HashiCorp Vault using Kubernetes service account tokens. This eliminates static credentials entirely:

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.internal:8200"
      path: "secret/data"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "app-role"
          serviceAccountRef:
            name: "eso-vault-auth"
            namespace: "external-secrets"

Create ExternalSecret Resources for Application Credentials

Define an ExternalSecret that maps Vault paths to Kubernetes Secret keys. ESO handles synchronization, refresh intervals, and error handling automatically:

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

On a real client project running a legal-tech portal, we configured ESO to sync payment gateway credentials from Vault every 15 minutes. When the finance team rotated API keys in Vault, all running pods received updated credentials within the refresh window without redeployment. This eliminated the manual coordination that previously caused 30-minute maintenance windows for every credential rotation.

External Secrets Operator ArchitectureHashiCorp VaultAuthoritative SourceDB CredentialsAPI KeysESO ControllerSync Every 15m–1hK8s AuthK8s NamespaceEphemeral Secretslaravel-db-secretpayment-api-secretPods mount native K8s Secrets — zero code changes required
External Secrets Operator synchronizes credentials from Vault to ephemeral Kubernetes Secrets without exposing long-lived tokens

The key insight is that application code never knows it's consuming externally managed secrets. Your Laravel .env file still references DB_PASSWORD from the environment, and volume mounts work identically. ESO makes Kubernetes Secrets Management Done Right transparent to developers while enforcing security policy at the infrastructure level.

How Do You Enable Encryption at Rest for Kubernetes Secrets?

Even with External Secrets Operator, the synchronized Kubernetes Secrets exist in etcd. Without encryption at rest, a compromised etcd node exposes everything. Enabling EncryptionConfiguration is mandatory for any cluster handling PII, financial data, or regulated workloads. This is especially relevant when securing servers that handle Nepali legal or financial documents where data residency and protection requirements apply.

Generate Encryption Key and Configuration

head -c 32 /dev/urandom | base64

# Create encryption config on control plane
cat > /etc/kubernetes/encryption-config.yaml <<EOF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: YOUR_BASE64_KEY_HERE
      - identity: {}
EOF

Configure API Server to Use Encryption

Edit the kube-apiserver manifest to mount and reference the encryption configuration:

# In /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
  containers:
  - command:
    - kube-apiserver
    - --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
    volumeMounts:
    - name: encryption-config
      mountPath: /etc/kubernetes/encryption-config.yaml
      readOnly: true
  volumes:
  - name: encryption-config
    hostPath:
      path: /etc/kubernetes/encryption-config.yaml
      type: File

After the API server restarts, all new secrets are encrypted. Existing secrets remain unencrypted until rewritten. Force re-encryption across the cluster:

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

A common mistake is forgetting that the identity provider must appear after aescbc in the providers list. This ordering tells the API server to encrypt with AES-CBC but still allow reading unencrypted secrets during migration. Reversing the order breaks decryption. Always validate encryption status by reading a secret directly from etcd using etcdctl and confirming the value is ciphertext.

Encryption ProviderPerformance ImpactKey Rotation SupportRecommended Use Case
aescbcModerate (~5% overhead)Manual rotation requiredSelf-managed clusters, broad compatibility
aesgcmLow (~2% overhead)Manual rotation requiredHigh-throughput API servers
KMS v2 (AWS/GCP/Azure)Network latency dependentAutomatic via cloud KMSManaged Kubernetes, compliance workloads
identityNoneN/ADevelopment only, never production

What RBAC Policies Restrict Secret Access Effectively?

Encryption protects data at rest, but RBAC prevents unauthorized access through the API. Most clusters grant far more secret access than necessary. Implementing least-privilege RBAC is where Kubernetes Secrets Management Done Right moves from theoretical to operational reality.

Audit Current Secret Permissions

Before tightening policies, understand current access patterns:

# Find all roles granting secret access
kubectl get clusterroles,roles --all-namespaces -o json | \
  jq '.items[] | select(.rules[]?.resources[]? == "secrets") | 
  {name: .metadata.name, namespace: .metadata.namespace, verbs: .rules[].verbs}'

# Check who can list secrets cluster-wide
kubectl auth can-i --list --as=system:serviceaccount:default:my-app | grep secrets

Create Namespace-Scoped Secret Roles

Never grant cluster-wide secret access. Create specific roles per application:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: laravel-secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["laravel-db-secret", "laravel-cache-secret"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: laravel-pod-secret-access
  namespace: production
subjects:
- kind: ServiceAccount
  name: laravel-app
  namespace: production
roleRef:
  kind: Role
  name: laravel-secret-reader
  apiGroup: rbac.authorization.k8s.io

Note the resourceNames field. This restricts access to specific secrets only. Without it, the service account can read every secret in the namespace, defeating the purpose of isolation. On a multi-tenant legal platform I worked on, we enforced this pattern strictly: each microservice could only access its own credentials, and shared secrets were explicitly named and audited quarterly.

RBAC Secret Access Decision FlowPod Requests SecretCorrect Namespace?NODENIEDYESresourceNames Match?NODENIEDYESALLOWEDAlways specify resourceNames — namespace-only roles are insufficient
RBAC decision flow demonstrating namespace, resource name, and verb checks for Kubernetes secret access

Block Secret Listing Entirely

Most applications need to get specific secrets but never need to list them. Listing exposes secret names and metadata even if values aren't returned. Remove list and watch verbs from all application-facing roles unless absolutely required. Audit logs should flag any service account attempting to list secrets as a potential compromise indicator.

How Do You Rotate Secrets Without Downtime?

Secret rotation is where most implementations fail operationally. Applications cache credentials in memory, connection pools hold stale passwords, and rolling restarts cause brief authentication failures. Getting rotation right requires coordinating the secret store, ESO sync interval, and application behavior.

  1. Update the source secret in Vault/Cloud provider first. Never edit the Kubernetes Secret directly; ESO will overwrite it on next sync.
  2. Wait for ESO refresh interval to elapse or trigger immediate sync: kubectl annotate externalsecret laravel-db-credentials force-sync=$(date +%s) --overwrite
  3. Verify the Kubernetes Secret updated: kubectl get secret laravel-db-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
  4. Gracefully restart affected pods to pick up new credentials: kubectl rollout restart deployment/laravel-app -n production
  5. Monitor application logs for authentication errors during the transition window.

For databases supporting multiple simultaneous passwords, configure Vault to maintain both old and new credentials during a grace period. This eliminates the race condition between secret update and pod restart. Applications like Laravel with persistent database connections benefit from connection pool recycling configurations that detect credential changes without full process restart.

In practice, teams using DevOps automation should script this entire rotation workflow. Manual rotation inevitably leads to skipped steps, forgotten pods, and 3 AM incident calls. Automate the verification step especially: if the new credential doesn't authenticate successfully in a test query, halt the rotation and alert before proceeding.

Kubernetes Secrets Management Done Right Requires Layered Defense

Implementing Kubernetes Secrets Management Done Right is not a single tool installation but a layered security posture combining external secret stores, encryption at rest, granular RBAC, and automated rotation workflows. Start with External Secrets Operator connected to your existing vault infrastructure, enable API server encryption immediately, and audit RBAC permissions quarterly. These three actions eliminate the majority of secret-related vulnerabilities seen in production clusters today.

If your team needs help designing or auditing a secrets management architecture that meets compliance requirements without slowing development velocity, reach out to discuss your specific infrastructure. Secure secret handling is foundational to trustworthy systems, and getting it right early prevents costly remediation later.

Frequently Asked Questions

Never store plaintext secrets in Git. Use external secret stores like HashiCorp Vault or AWS Secrets Manager synced via External Secrets Operator, ensuring encryption at rest and strict RBAC on namespace access.

Enable EncryptionConfiguration on the API server using aescbc or secretbox providers. This encrypts etcd data transparently. Without this, secrets are base64-encoded only, offering zero protection against direct etcd access.

ConfigMaps hold non-sensitive configuration; Secrets hold sensitive data with stricter RBAC, optional encryption at rest, and different handling in audit logs. Never put passwords or keys in ConfigMaps.

Git history is permanent and often cloned widely. Even if deleted later, secrets remain recoverable. In my experience auditing production systems, leaked credentials in Git cause most preventable breaches. Always use .gitignore and external injection methods instead.

It syncs secrets from external providers like Vault, AWS SM, or GCP Secret Manager into native Kubernetes Secrets automatically. This keeps source control clean, enables rotation without redeployment, and centralizes policy enforcement across clusters while maintaining standard kubectl workflows.

Follow least privilege strictly. Grant get/list/watch on secrets only to specific service accounts needing them. Avoid cluster-wide roles. On legal-tech portals I have built, we restrict secret access per namespace to isolate tenant data and limit blast radius during compromises.

Not natively. Pods cache mounted secrets at startup. Use External Secrets Operator with requeue intervals plus application-level reloading, or trigger rolling restarts via CI after rotation. For critical apps, implement config watchers or sidecar refresh patterns to avoid downtime during credential updates.

Enable API server audit logging with RequestResponse level for secrets resources. Ship logs to SIEM. Filter by verbs like get, list, watch. Set alerts for unusual service account access patterns. Regular audits catch misconfigured RBAC before attackers exploit overly broad permissions in production environments.

Only for small teams without external secret infrastructure. Sealed Secrets encrypts secrets client-side for safe Git storage but lacks rotation, centralized policy, and multi-cluster sync. For anything beyond hobby projects, External Secrets Operator or Vault provides better operational safety and compliance readiness.

Namespace-per-environment with separate ExternalSecret resources pointing to environment-specific paths in your provider. Never reuse secrets across dev/staging/prod. Use naming conventions like /app/prod/db-password. This isolation prevents staging credentials from leaking into production during accidental misconfigurations or developer errors.

The API server rejects it. Kubernetes enforces a hard 1MB limit per Secret object. Split large payloads into multiple secrets, compress binary data, or store bulky assets externally (S3, Vault transit) and reference them. Hitting this limit usually signals architectural issues worth resolving properly.

Inventory all secrets via kubectl get secrets -A. Import to your chosen provider. Create ExternalSecret manifests. Update deployments to reference new sources. Test thoroughly in staging first. Delete old native secrets only after confirming sync works. Budget two to three weeks for medium-sized clusters based on past migrations.

Minimally for read-heavy workloads since decrypted values are cached in memory. Write operations incur CPU overhead proportional to key size and algorithm. Benchmark with your actual load. On production e-commerce systems, aescbc with AES-NI hardware adds negligible latency under typical API traffic patterns.

Self-hosted Vault costs Rs 8,000–15,000/month (~USD 60–110) for HA on cloud VMs. Managed AWS Secrets Manager runs ~USD 0.40 per secret monthly plus API calls. External Secrets Operator is free open-source. Factor engineering time for setup and maintenance when budgeting for Nepal-based projects.

Logging secret values accidentally, mounting secrets as environment variables visible in process lists, granting default service account access, skipping encryption at rest, and forgetting to rotate after team member offboarding. Each mistake undermines other controls. Defense requires layered discipline, not just tooling.

Share this article

Quick Contact Options
Choose how you want to connect me: