Skip to content

Secrets Management

This guide provides comprehensive secrets management strategies for Temporal.io deployments, ensuring secure storage, access, and rotation of sensitive information including database credentials, API keys, certificates, and encryption keys.

Overview

Secrets management in Temporal.io encompasses: - Database Credentials: PostgreSQL and Elasticsearch authentication - TLS Certificates: Server and client certificates for secure communication - API Keys: Authentication tokens and service credentials - Encryption Keys: Data encryption and JWT signing keys - External Service Credentials: Third-party service authentication

Architecture

graph TB
    subgraph "External Secret Stores"
        VAULT[HashiCorp Vault]
        AWS_SM[AWS Secrets Manager]
        AZURE_KV[Azure Key Vault]
        GCP_SM[GCP Secret Manager]
    end

    subgraph "Kubernetes Secret Management"
        ESO[External Secrets Operator]
        SEALED_SECRETS[Sealed Secrets]
        K8S_SECRETS[Kubernetes Secrets]
    end

    subgraph "Temporal Cluster"
        FRONTEND[Frontend Service]
        HISTORY[History Service]
        MATCHING[Matching Service]
        WORKER[Worker Service]
    end

    subgraph "Data Stores"
        POSTGRES[(PostgreSQL)]
        ELASTICSEARCH[(Elasticsearch)]
    end

    subgraph "Certificate Management"
        CERT_MANAGER[Cert Manager]
        CA_CERTS[CA Certificates]
        TLS_CERTS[TLS Certificates]
    end

    VAULT --> ESO
    AWS_SM --> ESO
    AZURE_KV --> ESO
    GCP_SM --> ESO

    ESO --> K8S_SECRETS
    SEALED_SECRETS --> K8S_SECRETS
    CERT_MANAGER --> K8S_SECRETS

    K8S_SECRETS --> FRONTEND
    K8S_SECRETS --> HISTORY
    K8S_SECRETS --> MATCHING
    K8S_SECRETS --> WORKER

    FRONTEND --> POSTGRES
    HISTORY --> POSTGRES
    HISTORY --> ELASTICSEARCH

    CA_CERTS --> TLS_CERTS
    TLS_CERTS --> FRONTEND
    TLS_CERTS --> HISTORY
    TLS_CERTS --> MATCHING
    TLS_CERTS --> WORKER

External Secrets Operator Integration

1. Installation and Configuration

External Secrets Operator Setup

#!/bin/bash
# scripts/install-external-secrets.sh

set -euo pipefail

NAMESPACE="external-secrets-system"
VERSION="v0.9.11"

log() {
    echo -e "\033[0;32m[$(date +'%Y-%m-%d %H:%M:%S')] $1\033[0m"
}

error() {
    echo -e "\033[0;31m[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1\033[0m"
    exit 1
}

log "Installing External Secrets Operator..."

# Create namespace
kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f -

# Install using Helm
helm repo add external-secrets https://charts.external-secrets.io
helm repo update

helm install external-secrets external-secrets/external-secrets \
    --namespace "$NAMESPACE" \
    --version "$VERSION" \
    --set installCRDs=true \
    --set webhook.port=9443 \
    --set certController.create=true

# Wait for deployment
kubectl wait deployment external-secrets -n "$NAMESPACE" --for=condition=Available --timeout=300s
kubectl wait deployment external-secrets-webhook -n "$NAMESPACE" --for=condition=Available --timeout=300s
kubectl wait deployment external-secrets-cert-controller -n "$NAMESPACE" --for=condition=Available --timeout=300s

log "✓ External Secrets Operator installed successfully"

2. HashiCorp Vault Integration

Vault Secret Store Configuration

# k8s/external-secrets/vault-secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: temporal
spec:
  provider:
    vault:
      server: "https://vault.company.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "temporal-secrets-reader"
          serviceAccountRef:
            name: "temporal-vault-auth"

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: temporal-vault-auth
  namespace: temporal
  annotations:
    vault.hashicorp.com/role: "temporal-secrets-reader"

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: temporal-vault-auth
rules:
- apiGroups: [""]
  resources: ["serviceaccounts/token"]
  verbs: ["create"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: temporal-vault-auth
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: temporal-vault-auth
subjects:
- kind: ServiceAccount
  name: temporal-vault-auth
  namespace: temporal

Vault Policy Configuration

# vault/policies/temporal-secrets.hcl
path "secret/data/temporal/*" {
  capabilities = ["read"]
}

path "secret/metadata/temporal/*" {
  capabilities = ["list", "read"]
}

path "pki/issue/temporal" {
  capabilities = ["create", "update"]
}

path "database/creds/temporal-readonly" {
  capabilities = ["read"]
}

path "database/creds/temporal-readwrite" {
  capabilities = ["read"]
}

Vault Kubernetes Auth Setup

#!/bin/bash
# scripts/setup-vault-auth.sh

set -euo pipefail

VAULT_ADDR="https://vault.company.com"
KUBERNETES_HOST="https://kubernetes.default.svc.cluster.local"
VAULT_SA_NAME="temporal-vault-auth"
NAMESPACE="temporal"

log() {
    echo -e "\033[0;32m[$(date +'%Y-%m-%d %H:%M:%S')] $1\033[0m"
}

# Get service account token
SA_JWT_TOKEN=$(kubectl get secret $(kubectl get sa $VAULT_SA_NAME -n $NAMESPACE -o jsonpath='{.secrets[0].name}') -n $NAMESPACE -o jsonpath='{.data.token}' | base64 --decode)
SA_CA_CRT=$(kubectl get secret $(kubectl get sa $VAULT_SA_NAME -n $NAMESPACE -o jsonpath='{.secrets[0].name}') -n $NAMESPACE -o jsonpath='{.data.ca\.crt}' | base64 --decode)

# Configure Vault Kubernetes auth
vault auth enable kubernetes

vault write auth/kubernetes/config \
    token_reviewer_jwt="$SA_JWT_TOKEN" \
    kubernetes_host="$KUBERNETES_HOST" \
    kubernetes_ca_cert="$SA_CA_CRT"

# Create role for temporal
vault write auth/kubernetes/role/temporal-secrets-reader \
    bound_service_account_names="$VAULT_SA_NAME" \
    bound_service_account_namespaces="$NAMESPACE" \
    policies="temporal-secrets" \
    ttl=24h

log "✓ Vault Kubernetes authentication configured"

3. AWS Secrets Manager Integration

AWS Secret Store Configuration

# k8s/external-secrets/aws-secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secrets-manager
  namespace: temporal
spec:
  provider:
    aws:
      service: SecretsManager
      region: us-west-2
      auth:
        serviceAccount:
          name: temporal-aws-secrets

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: temporal-aws-secrets
  namespace: temporal
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/temporal-secrets-manager-role

---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-parameter-store
spec:
  provider:
    aws:
      service: ParameterStore
      region: us-west-2
      auth:
        serviceAccount:
          name: temporal-aws-secrets

IAM Role Configuration

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": [
        "arn:aws:secretsmanager:us-west-2:ACCOUNT_ID:secret:temporal/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameter",
        "ssm:GetParameters",
        "ssm:GetParametersByPath"
      ],
      "Resource": [
        "arn:aws:ssm:us-west-2:ACCOUNT_ID:parameter/temporal/*"
      ]
    }
  ]
}

Database Secrets Management

1. PostgreSQL Credentials

Database Secret Configuration

# k8s/external-secrets/database-secrets.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: temporal-postgres-credentials
  namespace: temporal
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: temporal-postgres-credentials
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        username: "{{ .username }}"
        password: "{{ .password }}"
        host: "{{ .host }}"
        port: "{{ .port }}"
        database: "{{ .database }}"
        connection-string: "postgres://{{ .username }}:{{ .password }}@{{ .host }}:{{ .port }}/{{ .database }}?sslmode=require"
        visibility-connection-string: "postgres://{{ .username }}:{{ .password }}@{{ .host }}:{{ .port }}/{{ .visibility_database }}?sslmode=require"
  data:
  - secretKey: username
    remoteRef:
      key: temporal/database
      property: username
  - secretKey: password
    remoteRef:
      key: temporal/database
      property: password
  - secretKey: host
    remoteRef:
      key: temporal/database
      property: host
  - secretKey: port
    remoteRef:
      key: temporal/database
      property: port
  - secretKey: database
    remoteRef:
      key: temporal/database
      property: database
  - secretKey: visibility_database
    remoteRef:
      key: temporal/database
      property: visibility_database

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

2. Dynamic Database Credentials with Vault

Vault Database Engine Configuration

#!/bin/bash
# scripts/setup-vault-database.sh

set -euo pipefail

VAULT_ADDR="https://vault.company.com"
DB_HOST="postgres.company.com"
DB_PORT="5432"

log() {
    echo -e "\033[0;32m[$(date +'%Y-%m-%d %H:%M:%S')] $1\033[0m"
}

# Enable database secrets engine
vault secrets enable database

# Configure PostgreSQL connection
vault write database/config/temporal-postgres \
    plugin_name=postgresql-database-plugin \
    connection_url="postgresql://{{username}}:{{password}}@${DB_HOST}:${DB_PORT}/postgres?sslmode=require" \
    allowed_roles="temporal-readonly,temporal-readwrite" \
    username="vault-admin" \
    password="$POSTGRES_VAULT_PASSWORD"

# Create readonly role
vault write database/roles/temporal-readonly \
    db_name=temporal-postgres \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
        GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
    default_ttl="1h" \
    max_ttl="24h"

# Create readwrite role
vault write database/roles/temporal-readwrite \
    db_name=temporal-postgres \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
        GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
    default_ttl="1h" \
    max_ttl="24h"

log "✓ Vault database engine configured for PostgreSQL"

Dynamic Database Secret

# k8s/external-secrets/dynamic-database-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: temporal-dynamic-db-credentials
  namespace: temporal
spec:
  refreshInterval: 30m
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: temporal-dynamic-db-credentials
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        username: "{{ .username }}"
        password: "{{ .password }}"
        connection-string: "postgres://{{ .username }}:{{ .password }}@postgres.company.com:5432/temporal?sslmode=require"
  data:
  - secretKey: username
    remoteRef:
      key: database/creds/temporal-readwrite
      property: username
  - secretKey: password
    remoteRef:
      key: database/creds/temporal-readwrite
      property: password

TLS Certificate Secrets

1. Certificate Secret Management

TLS Certificate Secrets

# k8s/external-secrets/tls-certificates.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: temporal-server-tls-certs
  namespace: temporal
spec:
  refreshInterval: 6h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: temporal-server-tls
    creationPolicy: Owner
    template:
      type: kubernetes.io/tls
      data:
        tls.crt: "{{ .server_cert }}"
        tls.key: "{{ .server_key }}"
        ca.crt: "{{ .ca_cert }}"
  data:
  - secretKey: server_cert
    remoteRef:
      key: temporal/tls/server
      property: certificate
  - secretKey: server_key
    remoteRef:
      key: temporal/tls/server
      property: private_key
  - secretKey: ca_cert
    remoteRef:
      key: temporal/tls/ca
      property: certificate

---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: temporal-client-tls-certs
  namespace: temporal
spec:
  refreshInterval: 6h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: temporal-client-tls
    creationPolicy: Owner
    template:
      type: kubernetes.io/tls
  data:
  - secretKey: tls.crt
    remoteRef:
      key: temporal/tls/client
      property: certificate
  - secretKey: tls.key
    remoteRef:
      key: temporal/tls/client
      property: private_key
  - secretKey: ca.crt
    remoteRef:
      key: temporal/tls/ca
      property: certificate

2. Automated Certificate Generation with Vault PKI

Vault PKI Setup

#!/bin/bash
# scripts/setup-vault-pki.sh

set -euo pipefail

VAULT_ADDR="https://vault.company.com"

log() {
    echo -e "\033[0;32m[$(date +'%Y-%m-%d %H:%M:%S')] $1\033[0m"
}

# Enable PKI secrets engine
vault secrets enable -path=pki pki

# Tune the TTL
vault secrets tune -max-lease-ttl=87600h pki

# Generate root certificate
vault write -field=certificate pki/root/generate/internal \
    common_name="Company Root CA" \
    ttl=87600h > ca_cert.pem

# Configure URLs
vault write pki/config/urls \
    issuing_certificates="$VAULT_ADDR/v1/pki/ca" \
    crl_distribution_points="$VAULT_ADDR/v1/pki/crl"

# Enable intermediate PKI
vault secrets enable -path=pki_int pki
vault secrets tune -max-lease-ttl=43800h pki_int

# Generate intermediate CSR
vault write -format=json pki_int/intermediate/generate/internal \
    common_name="Company Intermediate CA" \
    | jq -r '.data.csr' > pki_intermediate.csr

# Sign intermediate certificate
vault write -format=json pki/root/sign-intermediate \
    csr=@pki_intermediate.csr \
    format=pem_bundle ttl="43800h" \
    | jq -r '.data.certificate' > intermediate.cert.pem

# Import signed certificate
vault write pki_int/intermediate/set-signed certificate=@intermediate.cert.pem

# Create role for temporal certificates
vault write pki_int/roles/temporal \
    allowed_domains="company.com,temporal.company.com" \
    allow_subdomains=true \
    max_ttl="720h"

log "✓ Vault PKI configured successfully"

Dynamic Certificate Generation

# k8s/external-secrets/dynamic-certificates.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: temporal-dynamic-server-cert
  namespace: temporal
spec:
  refreshInterval: 24h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: temporal-dynamic-server-tls
    creationPolicy: Owner
    template:
      type: kubernetes.io/tls
      data:
        tls.crt: "{{ .certificate }}"
        tls.key: "{{ .private_key }}"
        ca.crt: "{{ .issuing_ca }}"
  data:
  - secretKey: certificate
    remoteRef:
      key: pki_int/issue/temporal
      property: certificate
  - secretKey: private_key
    remoteRef:
      key: pki_int/issue/temporal
      property: private_key
  - secretKey: issuing_ca
    remoteRef:
      key: pki_int/issue/temporal
      property: issuing_ca

API Keys and JWT Secrets

1. JWT Signing Keys

JWT Secret Configuration

# k8s/external-secrets/jwt-secrets.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: temporal-jwt-secrets
  namespace: temporal
spec:
  refreshInterval: 24h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: temporal-jwt-secrets
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        jwt-signing-key: "{{ .signing_key }}"
        jwt-public-key: "{{ .public_key }}"
        jwt-key-id: "{{ .key_id }}"
  data:
  - secretKey: signing_key
    remoteRef:
      key: temporal/jwt
      property: signing_key
  - secretKey: public_key
    remoteRef:
      key: temporal/jwt
      property: public_key
  - secretKey: key_id
    remoteRef:
      key: temporal/jwt
      property: key_id

---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: temporal-api-keys
  namespace: temporal
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: temporal-api-keys
    creationPolicy: Owner
  data:
  - secretKey: admin-api-key
    remoteRef:
      key: temporal/api-keys
      property: admin_key
  - secretKey: worker-api-key
    remoteRef:
      key: temporal/api-keys
      property: worker_key
  - secretKey: monitoring-api-key
    remoteRef:
      key: temporal/api-keys
      property: monitoring_key

2. External Service Credentials

Third-Party Service Secrets

# k8s/external-secrets/external-services.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: temporal-external-services
  namespace: temporal
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: temporal-external-services
    creationPolicy: Owner
  data:
  - secretKey: elasticsearch-username
    remoteRef:
      key: temporal/elasticsearch
      property: username
  - secretKey: elasticsearch-password
    remoteRef:
      key: temporal/elasticsearch
      property: password
  - secretKey: redis-password
    remoteRef:
      key: temporal/redis
      property: password
  - secretKey: smtp-username
    remoteRef:
      key: temporal/smtp
      property: username
  - secretKey: smtp-password
    remoteRef:
      key: temporal/smtp
      property: password
  - secretKey: webhook-signing-secret
    remoteRef:
      key: temporal/webhooks
      property: signing_secret

Sealed Secrets Alternative

1. Sealed Secrets Setup

Sealed Secrets Installation

#!/bin/bash
# scripts/install-sealed-secrets.sh

set -euo pipefail

NAMESPACE="kube-system"
VERSION="v0.24.0"

log() {
    echo -e "\033[0;32m[$(date +'%Y-%m-%d %H:%M:%S')] $1\033[0m"
}

log "Installing Sealed Secrets controller..."

# Install controller
kubectl apply -f "https://github.com/bitnami-labs/sealed-secrets/releases/download/${VERSION}/controller.yaml"

# Wait for deployment
kubectl wait deployment sealed-secrets-controller -n "$NAMESPACE" --for=condition=Available --timeout=300s

# Install kubeseal CLI
if ! command -v kubeseal &> /dev/null; then
    log "Installing kubeseal CLI..."
    wget "https://github.com/bitnami-labs/sealed-secrets/releases/download/${VERSION}/kubeseal-0.24.0-linux-amd64.tar.gz"
    tar xfz kubeseal-0.24.0-linux-amd64.tar.gz
    sudo install -m 755 kubeseal /usr/local/bin/kubeseal
    rm kubeseal kubeseal-0.24.0-linux-amd64.tar.gz
fi

log "✓ Sealed Secrets installed successfully"

Creating Sealed Secrets

#!/bin/bash
# scripts/create-sealed-secret.sh

set -euo pipefail

SECRET_NAME="$1"
NAMESPACE="$2"
KEY="$3"
VALUE="$4"

log() {
    echo -e "\033[0;32m[$(date +'%Y-%m-%d %H:%M:%S')] $1\033[0m"
}

# Create secret and seal it
kubectl create secret generic "$SECRET_NAME" \
    --from-literal="$KEY=$VALUE" \
    --dry-run=client -o yaml | \
    kubeseal -o yaml --namespace "$NAMESPACE" > "sealed-${SECRET_NAME}.yaml"

log "✓ Sealed secret created: sealed-${SECRET_NAME}.yaml"

2. Sealed Secret Examples

Database Credentials Sealed Secret

# k8s/sealed-secrets/database-credentials.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: temporal-postgres-credentials
  namespace: temporal
spec:
  encryptedData:
    username: AgBy...  # Encrypted username
    password: AgCj...  # Encrypted password
    host: AgAz...      # Encrypted host
    port: AgBw...      # Encrypted port
    database: AgDf...  # Encrypted database name
  template:
    metadata:
      name: temporal-postgres-credentials
      namespace: temporal
    type: Opaque

Secrets Rotation Automation

1. Automated Rotation Script

Secret Rotation Workflow

#!/bin/bash
# scripts/rotate-secrets.sh

set -euo pipefail

NAMESPACE="temporal"
VAULT_ADDR="https://vault.company.com"

log() {
    echo -e "\033[0;32m[$(date +'%Y-%m-%d %H:%M:%S')] $1\033[0m"
}

error() {
    echo -e "\033[0;31m[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1\033[0m"
    exit 1
}

# Function to rotate database password
rotate_database_password() {
    local secret_name="$1"

    log "Rotating database password for secret: $secret_name"

    # Generate new password
    local new_password=$(openssl rand -base64 32)

    # Update password in Vault
    vault kv patch secret/temporal/database password="$new_password"

    # Force refresh of ExternalSecret
    kubectl annotate externalsecret "$secret_name" -n "$NAMESPACE" \
        force-sync="$(date +%s)" --overwrite

    # Wait for secret to be updated
    kubectl wait externalsecret "$secret_name" -n "$NAMESPACE" \
        --for=condition=Ready --timeout=60s

    log "✓ Database password rotated successfully"
}

# Function to rotate JWT signing key
rotate_jwt_key() {
    local secret_name="$1"

    log "Rotating JWT signing key for secret: $secret_name"

    # Generate new RSA key pair
    local private_key=$(openssl genrsa 2048)
    local public_key=$(echo "$private_key" | openssl rsa -pubout)
    local key_id=$(openssl rand -hex 8)

    # Update keys in Vault
    vault kv patch secret/temporal/jwt \
        signing_key="$private_key" \
        public_key="$public_key" \
        key_id="$key_id"

    # Force refresh of ExternalSecret
    kubectl annotate externalsecret "$secret_name" -n "$NAMESPACE" \
        force-sync="$(date +%s)" --overwrite

    log "✓ JWT signing key rotated successfully"
}

# Function to restart deployments after secret rotation
restart_deployments() {
    local deployments=("temporal-frontend" "temporal-history" "temporal-matching" "temporal-worker")

    for deployment in "${deployments[@]}"; do
        log "Restarting deployment: $deployment"
        kubectl rollout restart deployment "$deployment" -n "$NAMESPACE"
    done

    # Wait for rollouts to complete
    for deployment in "${deployments[@]}"; do
        kubectl rollout status deployment "$deployment" -n "$NAMESPACE" --timeout=300s
    done

    log "✓ All deployments restarted successfully"
}

main() {
    log "Starting secrets rotation process..."

    # Check if we need to rotate based on age or policy
    secrets_to_rotate=(
        "temporal-postgres-credentials"
        "temporal-jwt-secrets"
    )

    for secret in "${secrets_to_rotate[@]}"; do
        case "$secret" in
            "temporal-postgres-credentials")
                rotate_database_password "$secret"
                ;;
            "temporal-jwt-secrets")
                rotate_jwt_key "$secret"
                ;;
        esac
    done

    # Restart deployments to pick up new secrets
    restart_deployments

    log "✓ Secrets rotation completed successfully"
}

main "$@"

2. Secrets Monitoring and Alerting

Secret Expiry Monitoring

# k8s/monitoring/secrets-monitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: external-secrets-metrics
  namespace: external-secrets-system
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: external-secrets
  endpoints:
  - port: metrics
    interval: 30s
    path: /metrics

---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: secrets-alerts
  namespace: temporal
spec:
  groups:
  - name: secrets-rotation
    rules:
    - alert: ExternalSecretSyncFailed
      expr: external_secrets_sync_calls_error > 0
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "External secret sync failed"
        description: "External secret {{ $labels.name }} in namespace {{ $labels.namespace }} failed to sync"

    - alert: SecretRotationOverdue
      expr: (time() - external_secrets_sync_calls_success_timestamp) > 86400 * 7
      for: 1h
      labels:
        severity: warning
      annotations:
        summary: "Secret rotation overdue"
        description: "Secret {{ $labels.name }} in namespace {{ $labels.namespace }} has not been rotated in over 7 days"

    - alert: DatabaseCredentialsExpiringSoon
      expr: |
        (
          external_secrets_sync_calls_success_timestamp{name="temporal-postgres-credentials"}
          + 86400 * 30  # 30 days
        ) - time() < 86400 * 7  # 7 days warning
      for: 1h
      labels:
        severity: critical
      annotations:
        summary: "Database credentials expiring soon"
        description: "Database credentials will expire within 7 days and need rotation"

Security Best Practices

1. Secret Access Control

RBAC for Secrets

# k8s/rbac/secrets-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: temporal
  name: temporal-secrets-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["external-secrets.io"]
  resources: ["externalsecrets", "secretstores"]
  verbs: ["get", "list", "watch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: temporal
  name: temporal-secrets-admin
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["*"]
- apiGroups: ["external-secrets.io"]
  resources: ["externalsecrets", "secretstores"]
  verbs: ["*"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: temporal-service-secrets
  namespace: temporal
subjects:
- kind: ServiceAccount
  name: temporal-server
  namespace: temporal
roleRef:
  kind: Role
  name: temporal-secrets-reader
  apiGroup: rbac.authorization.k8s.io

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: temporal-admin-secrets
  namespace: temporal
subjects:
- kind: Group
  name: temporal-admins
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: temporal-secrets-admin
  apiGroup: rbac.authorization.k8s.io

2. Secrets Encryption at Rest

EncryptionConfiguration

# k8s/encryption/secrets-encryption.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: <base64-encoded-secret>
  - identity: {}

3. Secrets Auditing

Audit Policy for Secrets

# k8s/audit/secrets-audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
  resources:
  - group: ""
    resources: ["secrets"]
  verbs: ["create", "update", "patch", "delete"]

- level: Request
  resources:
  - group: ""
    resources: ["secrets"]
  verbs: ["get", "list", "watch"]

- level: RequestResponse
  resources:
  - group: "external-secrets.io"
    resources: ["externalsecrets", "secretstores"]
  verbs: ["create", "update", "patch", "delete"]

This comprehensive secrets management guide provides enterprise-grade security for Temporal.io deployments with multiple secret store integrations, automated rotation, and comprehensive monitoring capabilities.