Tools: Secrets Management in Modern DevOps: Vault, IRSA, External Secrets When to Use Each

Tools: Secrets Management in Modern DevOps: Vault, IRSA, External Secrets When to Use Each

The Secrets Management Anti-Patterns (and Their Blast Radius)

AWS IRSA (IAM Roles for Service Accounts)

GCP Workload Identity Equivalent

HashiCorp Vault: When You Need More Than Cloud-Native

The Core Vault Capability

External Secrets Operator: The Kubernetes-Native Abstraction Layer

AWS Secrets Manager Example

When ESO Is NOT Enough

Sealed Secrets: Simple Offline Encryption for GitOps

Why Teams Love Sealed Secrets

Secrets Rotation: The Missing Piece Most Implementations Skip

Audit Logging: Knowing Who Accessed What and When

Multi-Cloud Secrets: Managing Credentials Across AWS, Azure, and GCP

Common Secrets Management Mistakes Secrets management failures rarely begin with malicious intent. They begin with expediency. An engineer hardcodes an API key “temporarily.” A .env file gets committed accidentally. A production database password gets shared in Slack during an outage because “we’ll rotate it later.” Eventually those shortcuts accumulate into a sprawling credential catastrophe hidden beneath otherwise competent infrastructure. The uncomfortable truth is that poor secrets hygiene exists everywhere: The issue is rarely ignorance. It is architectural ambiguity. Modern DevOps teams now face multiple competing approaches: Choosing incorrectly creates operational fragility. Choosing well dramatically improves both security and developer experience. This guide explains when to use each model, where each one fails, and how to evolve from common anti-patterns toward a production-grade secrets architecture without detonating existing workloads. Before discussing solutions, understand the failure modes. Because nearly every modern secrets architecture exists to solve one of these disasters. Anti-Pattern 1: Hardcoded Secrets in Source Code This is not merely bad practice. It is operationally radioactive. Even if deleted later. Anti-Pattern 2: Shared Credentials Shared credentials eliminate accountability entirely. Anti-Pattern 3: Long-Lived Cloud Access Keys Static credentials eventually leak. The question is timing, not probability. Anti-Pattern 4: Kubernetes Secrets Misunderstood as Encryption Base64 encoding is not encryption. This surprises people alarmingly often. Kubernetes Secrets require additional controls: Otherwise they become plaintext credential storage with better branding. Understanding the Modern Secrets Management Stack Modern secrets management generally falls into four categories Each solves different problems. IRSA / Workload Identity: Cloud-Native Secretless Authentication This is the most important architectural shift in modern cloud security Instead of giving workloads access keys No static secrets required. Pods authenticate using Kubernetes service accounts mapped to IAM roles. Kubernetes Service Account Pods automatically receive temporary credentials. Why IRSA Is Excellent This should be the default model for AWS-native workloads. Equivalent concept. Different implementation. Azure Workload Identity Azure now supports federated workload identity similarly. The industry is converging on identity federation rather than credential distribution. When IRSA / Workload Identity Is NOT Enough Cloud-native identity works beautifully for cloud APIs. It becomes weaker when dealing with: This is where Vault becomes valuable. Vault solves problems identity federation alone cannot. Especially dynamic secrets. Vault does not merely store secrets. It generates them dynamically. Massive security improvement. Vault Kubernetes Authentication Pods authenticate automatically via Kubernetes identity. Dynamic Database Credentials Credentials expire automatically after one hour. When Vault Is the Right Choice Use Vault when you need Vault is operationally heavier. Vault is powerful because it solves hard problems. Hard problems come with operational complexity. External Secrets Operator (ESO) is one of the cleanest Kubernetes-native abstractions available today. Instead of storing secrets directly in Kubernetes ESO is often the best abstraction for Kubernetes workloads. ESO synchronises secrets. It does not generate dynamic credentials. You still need Vault or equivalent systems. Sealed Secrets solve a specific problem elegantly Sealed Secret Workflow Only the cluster controller can decrypt it. Where Sealed Secrets Fall Short Excellent for smaller GitOps environments. Less ideal for enterprise-scale secret orchestration. This is the most neglected part of secrets management. Teams store secrets securely but never rotate them. Which defeats half the purpose. Vault Dynamic Rotation No manual rotation required. AWS Secrets Manager Rotation Example Lambda rotation Common Rotation Failure Mode Applications caching credentials indefinitely. Applications must reload credentials gracefully. Secrets access without auditing is operational blindness. Every secret request becomes traceable. IRSA requests appear in CloudTrail automatically. This is one reason identity federation is so operationally attractive. Critical Audit Questions You should always answer: Without auditability, incident response becomes guesswork. Migration Playbook: Moving from Hard-Coded to Vault in 4 Weeks Most organisations cannot migrate instantly. They need staged evolution. Week 2: Centralisation Without changing applications yet. Week 3: Kubernetes Integration Start consuming secrets dynamically. Week 4: Rotation and Cleanup Then delete legacy storage completely. Multi-cloud secrets management becomes operationally difficult quickly. Vault becomes particularly valuable when standardising identity across clouds. Recommended Enterprise Architecture Layered abstractions create operational flexibility. 1. Treating Kubernetes Secrets as Secure by Default 2. Never Rotating Credentials Static secrets become permanent liabilities. 3. Using Shared Accounts Breaks attribution entirely. 4. Giving Vault Excessive Permissions Vault should broker secrets. Not become root over everything. 5. Ignoring Audit Logs Visibility matters as much as encryption. Modern secrets management is no longer about hiding passwords. It is about distributing trust safely. The strongest DevOps environments increasingly follow several principles: IRSA and workload identity eliminate entire classes of cloud credential risk. Vault enables dynamic, short-lived infrastructure authentication. External Secrets Operator creates elegant Kubernetes-native integration. Sealed Secrets simplify GitOps encryption. Each tool has a legitimate role. The mistake is not choosing the wrong product. The mistake is assuming one tool solves every secrets problem equally well. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

API_KEY = "sk-prod-293847239847" API_KEY = "sk-prod-293847239847" API_KEY = "sk-prod-293847239847" prod-admin / password123 prod-admin / password123 prod-admin / password123 No attribution No least privilege No revocation granularity No attribution No least privilege No revocation granularity No attribution No least privilege No revocation granularity AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY echo "cGFzc3dvcmQ=" | base64 -d echo "cGFzc3dvcmQ=" | base64 -d echo "cGFzc3dvcmQ=" | base64 -d password Stop distributing credentials. Start distributing identity. Stop distributing credentials. Start distributing identity. Stop distributing credentials. Start distributing identity. Pod → authenticated identity → temporary credentials Pod → authenticated identity → temporary credentials Pod → authenticated identity → temporary credentials resource "aws_iam_role" "payment_service" { name = "payment-service-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Federated = aws_iam_openid_connect_provider.eks.arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${replace( aws_eks_cluster.main.identity[0].oidc[0].issuer, "https://", "" )}:sub" = "system:serviceaccount:payments:payment-service" } } }] }) } resource "aws_iam_role" "payment_service" { name = "payment-service-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Federated = aws_iam_openid_connect_provider.eks.arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${replace( aws_eks_cluster.main.identity[0].oidc[0].issuer, "https://", "" )}:sub" = "system:serviceaccount:payments:payment-service" } } }] }) } resource "aws_iam_role" "payment_service" { name = "payment-service-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Federated = aws_iam_openid_connect_provider.eks.arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${replace( aws_eks_cluster.main.identity[0].oidc[0].issuer, "https://", "" )}:sub" = "system:serviceaccount:payments:payment-service" } } }] }) } apiVersion: v1 kind: ServiceAccount metadata: name: payment-service namespace: payments annotations: eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT:role/payment-service-role apiVersion: v1 kind: ServiceAccount metadata: name: payment-service namespace: payments annotations: eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT:role/payment-service-role apiVersion: v1 kind: ServiceAccount metadata: name: payment-service namespace: payments annotations: eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT:role/payment-service-role Kubernetes Service Account ↔ Google Service Account Kubernetes Service Account ↔ Google Service Account Kubernetes Service Account ↔ Google Service Account Application requests PostgreSQL credentials ↓ Vault creates short-lived DB user ↓ Credentials expire automatically Application requests PostgreSQL credentials ↓ Vault creates short-lived DB user ↓ Credentials expire automatically Application requests PostgreSQL credentials ↓ Vault creates short-lived DB user ↓ Credentials expire automatically vault auth enable kubernetes vault auth enable kubernetes vault auth enable kubernetes vault write auth/kubernetes/role/payment-api \ bound_service_account_names=payment-service \ bound_service_account_namespaces=payments \ policies=payment-read \ ttl=1h vault write auth/kubernetes/role/payment-api \ bound_service_account_names=payment-service \ bound_service_account_namespaces=payments \ policies=payment-read \ ttl=1h vault write auth/kubernetes/role/payment-api \ bound_service_account_names=payment-service \ bound_service_account_namespaces=payments \ policies=payment-read \ ttl=1h vault read database/creds/payment-role vault read database/creds/payment-role vault read database/creds/payment-role { "username": "v-token-abc123", "password": "generated-secret", "lease_duration": 3600 } { "username": "v-token-abc123", "password": "generated-secret", "lease_duration": 3600 } { "username": "v-token-abc123", "password": "generated-secret", "lease_duration": 3600 } Kubernetes Secret ← synced from → Vault / AWS Secrets Manager / GCP Secret Manager Kubernetes Secret ← synced from → Vault / AWS Secrets Manager / GCP Secret Manager Kubernetes Secret ← synced from → Vault / AWS Secrets Manager / GCP Secret Manager helm install external-secrets external-secrets/external-secrets helm install external-secrets external-secrets/external-secrets helm install external-secrets external-secrets/external-secrets apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: payment-api-secret spec: refreshInterval: 1h secretStoreRef: name: aws-secret-store kind: SecretStore target: name: payment-api-secret data: - secretKey: api-key remoteRef: key: prod/payment-api property: api_key apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: payment-api-secret spec: refreshInterval: 1h secretStoreRef: name: aws-secret-store kind: SecretStore target: name: payment-api-secret data: - secretKey: api-key remoteRef: key: prod/payment-api property: api_key apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: payment-api-secret spec: refreshInterval: 1h secretStoreRef: name: aws-secret-store kind: SecretStore target: name: payment-api-secret data: - secretKey: api-key remoteRef: key: prod/payment-api property: api_key How do you store encrypted secrets safely in Git? How do you store encrypted secrets safely in Git? How do you store encrypted secrets safely in Git? kubectl create secret generic app-secret kubectl create secret generic app-secret kubectl create secret generic app-secret kubeseal --format yaml kubeseal --format yaml kubeseal --format yaml apiVersion: bitnami.com/v1alpha1 kind: SealedSecret apiVersion: bitnami.com/v1alpha1 kind: SealedSecret apiVersion: bitnami.com/v1alpha1 kind: SealedSecret Generate → use → expire automatically Generate → use → expire automatically Generate → use → expire automatically RotationRules: AutomaticallyAfterDays: 30 RotationRules: AutomaticallyAfterDays: 30 RotationRules: AutomaticallyAfterDays: 30 Secret rotated ↓ Application breaks Secret rotated ↓ Application breaks Secret rotated ↓ Application breaks vault audit enable file file_path=/var/log/vault_audit.log vault audit enable file file_path=/var/log/vault_audit.log vault audit enable file file_path=/var/log/vault_audit.log Kubernetes Workload ↓ IRSA / Workload Identity ↓ Vault / Cloud Secret Manager ↓ External Secrets Operator ↓ Application Runtime Kubernetes Workload ↓ IRSA / Workload Identity ↓ Vault / Cloud Secret Manager ↓ External Secrets Operator ↓ Application Runtime Kubernetes Workload ↓ IRSA / Workload Identity ↓ Vault / Cloud Secret Manager ↓ External Secrets Operator ↓ Application Runtime Identity over credentials Temporary over permanent Dynamic over static Automated over manual Auditable over opaque Identity over credentials Temporary over permanent Dynamic over static Automated over manual Auditable over opaque Identity over credentials Temporary over permanent Dynamic over static Automated over manual Auditable over opaque - Enterprises - Government systems - Fortune 500 infrastructure - Cloud-native identity systems - Kubernetes secret abstractions - External Secrets Operator - Sealed Secrets - Workload identity federation - Dynamic credentials - Git history preserves it - Forks replicate it - CI logs may expose it - Developers clone it locally - Backups persist it indefinitely - Automation tools - Contractors - GitHub Actions - Kubernetes Secrets - Terraform variables - Encryption at rest - Admission policies - Audit logging - No static AWS keys - Automatic credential rotation - IAM-native permissions - Short-lived credentials - Excellent auditability - Third-party APIs - Cross-cloud systems - Legacy applications - Dynamic credential issuance - Multi-cluster secret orchestration - HA clustering - Storage backend - Unseal process - Disaster recovery - Performance replication - Kubernetes-native - GitOps-friendly - Central secret backend - Automatic refresh - Cleaner operational model - Dynamic DB users - Certificate issuance - Secret leasing - PKI workflows - GitOps-compatible - Easy onboarding - No external dependency - Static secrets only - No automatic rotation - Kubernetes-scoped - No dynamic credential issuance - Who accessed this secret? - From which workload? - Was it expected? - Was it anomalous? - Hardcoded credentials - Kubernetes Secrets - Shared accounts - AWS Secrets Manager - GCP Secret Manager - Vault Agent Injector - Old credentials - Shared passwords - Long-lived tokens