Skip to content

TLS Configuration

This guide provides comprehensive TLS configuration strategies for Temporal.io deployments, ensuring encrypted communication across all components including server-to-server, client-to-server, and database connections.

Overview

TLS security in Temporal.io encompasses: - Server TLS: Securing external client connections - mTLS: Mutual TLS for service-to-service communication - Database TLS: Encrypted database connections - Certificate Management: Automated certificate lifecycle - Certificate Rotation: Secure certificate renewal processes

Architecture

graph TB
    subgraph "Certificate Authority"
        ROOT_CA[Root CA]
        INTER_CA[Intermediate CA]
        CERT_MANAGER[Cert Manager]
    end

    subgraph "External Load Balancer"
        ALB[Application Load Balancer]
        TLS_TERM[TLS Termination]
    end

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

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

    subgraph "Clients"
        SDK_CLIENT[SDK Client]
        CLI_CLIENT[CLI Client]
        WEB_UI[Web UI]
        APP_WORKERS[Application Workers]
    end

    ROOT_CA --> INTER_CA
    INTER_CA --> CERT_MANAGER
    CERT_MANAGER --> ALB
    CERT_MANAGER --> FRONTEND
    CERT_MANAGER --> HISTORY
    CERT_MANAGER --> MATCHING
    CERT_MANAGER --> WORKER
    CERT_MANAGER --> POSTGRES
    CERT_MANAGER --> ELASTICSEARCH

    SDK_CLIENT -->|TLS| ALB
    CLI_CLIENT -->|TLS| ALB
    WEB_UI -->|TLS| ALB
    APP_WORKERS -->|mTLS| ALB

    ALB -->|mTLS| FRONTEND
    FRONTEND -->|mTLS| HISTORY
    FRONTEND -->|mTLS| MATCHING
    FRONTEND -->|mTLS| WORKER

    FRONTEND -->|TLS| POSTGRES
    HISTORY -->|TLS| POSTGRES
    HISTORY -->|TLS| ELASTICSEARCH

Certificate Management

1. Certificate Authority Setup

Root CA Configuration

#!/bin/bash
# scripts/setup-ca.sh

set -euo pipefail

CA_DIR="ca"
ROOT_CA_KEY="$CA_DIR/root-ca-key.pem"
ROOT_CA_CERT="$CA_DIR/root-ca-cert.pem"
INTER_CA_KEY="$CA_DIR/intermediate-ca-key.pem"
INTER_CA_CERT="$CA_DIR/intermediate-ca-cert.pem"
INTER_CA_CSR="$CA_DIR/intermediate-ca-csr.pem"

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
}

# Create CA directory
mkdir -p "$CA_DIR"
chmod 700 "$CA_DIR"

# Generate Root CA private key
if [[ ! -f "$ROOT_CA_KEY" ]]; then
    log "Generating Root CA private key..."
    openssl genrsa -aes256 -out "$ROOT_CA_KEY" 4096
    chmod 600 "$ROOT_CA_KEY"
fi

# Generate Root CA certificate
if [[ ! -f "$ROOT_CA_CERT" ]]; then
    log "Generating Root CA certificate..."
    cat > "$CA_DIR/root-ca.conf" << EOF
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no

[req_distinguished_name]
C = US
ST = California
L = San Francisco
O = Company Name
OU = IT Department
CN = Company Root CA

[v3_ca]
basicConstraints = critical,CA:TRUE
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
EOF

    openssl req -new -x509 -key "$ROOT_CA_KEY" -sha256 -days 3650 \
        -out "$ROOT_CA_CERT" -config "$CA_DIR/root-ca.conf"

    rm "$CA_DIR/root-ca.conf"
fi

# Generate Intermediate CA private key
if [[ ! -f "$INTER_CA_KEY" ]]; then
    log "Generating Intermediate CA private key..."
    openssl genrsa -aes256 -out "$INTER_CA_KEY" 4096
    chmod 600 "$INTER_CA_KEY"
fi

# Generate Intermediate CA certificate
if [[ ! -f "$INTER_CA_CERT" ]]; then
    log "Generating Intermediate CA certificate..."
    cat > "$CA_DIR/intermediate-ca.conf" << EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_intermediate_ca
prompt = no

[req_distinguished_name]
C = US
ST = California
L = San Francisco
O = Company Name
OU = IT Department
CN = Company Intermediate CA

[v3_intermediate_ca]
basicConstraints = critical,CA:TRUE,pathlen:0
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
EOF

    # Create CSR for intermediate CA
    openssl req -new -key "$INTER_CA_KEY" -out "$INTER_CA_CSR" \
        -config "$CA_DIR/intermediate-ca.conf"

    # Sign intermediate CA with root CA
    openssl x509 -req -in "$INTER_CA_CSR" -CA "$ROOT_CA_CERT" \
        -CAkey "$ROOT_CA_KEY" -CAcreateserial -out "$INTER_CA_CERT" \
        -days 1825 -extensions v3_intermediate_ca \
        -extfile "$CA_DIR/intermediate-ca.conf"

    # Create certificate chain
    cat "$INTER_CA_CERT" "$ROOT_CA_CERT" > "$CA_DIR/ca-chain.pem"

    # Clean up
    rm "$CA_DIR/intermediate-ca.conf" "$INTER_CA_CSR"
fi

log "✓ Certificate Authority setup completed"
log "Root CA: $ROOT_CA_CERT"
log "Intermediate CA: $INTER_CA_CERT"
log "CA Chain: $CA_DIR/ca-chain.pem"

Cert-Manager Integration

# k8s/cert-manager/cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: temporal-ca-issuer
spec:
  ca:
    secretName: temporal-ca-key-pair

---
apiVersion: v1
kind: Secret
metadata:
  name: temporal-ca-key-pair
  namespace: cert-manager
type: Opaque
data:
  tls.crt: LS0tLS1CRUdJTi... # Base64 encoded intermediate CA cert
  tls.key: LS0tLS1CRUdJTi... # Base64 encoded intermediate CA key

---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-production
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@company.com
    privateKeySecretRef:
      name: letsencrypt-production
    solvers:
    - http01:
        ingress:
          class: alb
    - dns01:
        route53:
          region: us-west-2
          accessKeyID: AKIAIOSFODNN7EXAMPLE
          secretAccessKeySecretRef:
            name: route53-credentials
            key: secret-access-key

2. Server TLS Configuration

Frontend Service TLS

# config/frontend-tls.yaml
tls:
  frontend:
    server:
      certFile: "/etc/temporal/certs/server.crt"
      keyFile: "/etc/temporal/certs/server.key"
      clientCaFiles:
        - "/etc/temporal/certs/ca-chain.pem"
      requireClientAuth: false  # For external clients
      cipherSuites:
        - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
        - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
        - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
        - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
      minVersion: "1.2"
      maxVersion: "1.3"

  # Alternative configuration for client certificate authentication
  frontend_mtls:
    server:
      certFile: "/etc/temporal/certs/server.crt"
      keyFile: "/etc/temporal/certs/server.key"
      clientCaFiles:
        - "/etc/temporal/certs/ca-chain.pem"
      requireClientAuth: true   # For mTLS clients
      clientCertPolicy: "RequireAndVerifyClientCert"

Certificate Request Template

# k8s/certificates/server-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: temporal-server-cert
  namespace: temporal
spec:
  secretName: temporal-server-tls
  issuerRef:
    name: temporal-ca-issuer
    kind: ClusterIssuer
  commonName: temporal.company.com
  dnsNames:
  - temporal.company.com
  - temporal-frontend
  - temporal-frontend.temporal.svc.cluster.local
  - temporal-history
  - temporal-history.temporal.svc.cluster.local
  - temporal-matching
  - temporal-matching.temporal.svc.cluster.local
  - temporal-worker
  - temporal-worker.temporal.svc.cluster.local
  ipAddresses:
  - 127.0.0.1
  usages:
  - digital signature
  - key encipherment
  - server auth
  duration: 8760h  # 1 year
  renewBefore: 720h  # 30 days

3. Mutual TLS (mTLS) Configuration

Inter-Service Communication

# config/internode-mtls.yaml
tls:
  internode:
    server:
      certFile: "/etc/temporal/certs/internode.crt"
      keyFile: "/etc/temporal/certs/internode.key"
      clientCaFiles:
        - "/etc/temporal/certs/ca-chain.pem"
      requireClientAuth: true
      clientCertPolicy: "RequireAndVerifyClientCert"
    client:
      certFile: "/etc/temporal/certs/internode.crt"
      keyFile: "/etc/temporal/certs/internode.key"
      serverCaFiles:
        - "/etc/temporal/certs/ca-chain.pem"
      serverName: "temporal.company.com"
      insecureSkipVerify: false

Client Certificate Configuration

# k8s/certificates/client-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: temporal-client-cert
  namespace: temporal
spec:
  secretName: temporal-client-tls
  issuerRef:
    name: temporal-ca-issuer
    kind: ClusterIssuer
  commonName: temporal-client
  usages:
  - digital signature
  - key encipherment
  - client auth
  duration: 2160h  # 90 days
  renewBefore: 168h  # 7 days

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: temporal-worker-cert
  namespace: temporal
spec:
  secretName: temporal-worker-tls
  issuerRef:
    name: temporal-ca-issuer
    kind: ClusterIssuer
  commonName: temporal-worker
  usages:
  - digital signature
  - key encipherment
  - client auth
  duration: 2160h  # 90 days
  renewBefore: 168h  # 7 days

Database TLS Configuration

1. PostgreSQL TLS Setup

Database TLS Configuration

# config/database-tls.yaml
persistence:
  default:
    sql:
      driver: "postgres"
      datasourceName: "postgres://temporal:${POSTGRES_PASSWORD}@postgres.company.com:5432/temporal?sslmode=require"
      tls:
        enabled: true
        caFile: "/etc/temporal/certs/postgres-ca.crt"
        certFile: "/etc/temporal/certs/postgres-client.crt"
        keyFile: "/etc/temporal/certs/postgres-client.key"
        serverName: "postgres.company.com"
        insecureSkipVerify: false

  visibility:
    sql:
      driver: "postgres"
      datasourceName: "postgres://temporal:${POSTGRES_PASSWORD}@postgres.company.com:5432/temporal_visibility?sslmode=require"
      tls:
        enabled: true
        caFile: "/etc/temporal/certs/postgres-ca.crt"
        certFile: "/etc/temporal/certs/postgres-client.crt"
        keyFile: "/etc/temporal/certs/postgres-client.key"
        serverName: "postgres.company.com"

PostgreSQL Server Configuration

# postgresql.conf
ssl = on
ssl_ca_file = '/etc/postgresql/certs/ca-chain.pem'
ssl_cert_file = '/etc/postgresql/certs/server.crt'
ssl_key_file = '/etc/postgresql/certs/server.key'
ssl_ciphers = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384'
ssl_prefer_server_ciphers = on
ssl_protocols = 'TLSv1.2,TLSv1.3'

# Client certificate authentication
ssl_client_ca_file = '/etc/postgresql/certs/ca-chain.pem'

2. Elasticsearch TLS Setup

Elasticsearch TLS Configuration

# config/elasticsearch-tls.yaml
cluster:
  metadata:
    elasticsearch:
      version: "v7"
      url:
        scheme: "https"
        host: "elasticsearch.company.com:9200"
      username: "temporal"
      password: "${ELASTICSEARCH_PASSWORD}"
      closeIdleConnectionsInterval: 15s
      tls:
        enabled: true
        caFile: "/etc/temporal/certs/elasticsearch-ca.crt"
        certFile: "/etc/temporal/certs/elasticsearch-client.crt"
        keyFile: "/etc/temporal/certs/elasticsearch-client.key"
        serverName: "elasticsearch.company.com"
        insecureSkipVerify: false

Kubernetes TLS Integration

1. TLS Secret Management

# k8s/tls-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: temporal-server-tls
  namespace: temporal
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRUdJTi... # Server certificate
  tls.key: LS0tLS1CRUdJTi... # Server private key
  ca.crt: LS0tLS1CRUdJTi...  # CA chain

---
apiVersion: v1
kind: Secret
metadata:
  name: temporal-client-tls
  namespace: temporal
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRUdJTi... # Client certificate
  tls.key: LS0tLS1CRUdJTi... # Client private key
  ca.crt: LS0tLS1CRUdJTi...  # CA chain

---
apiVersion: v1
kind: Secret
metadata:
  name: temporal-database-tls
  namespace: temporal
type: Opaque
data:
  ca.crt: LS0tLS1CRUdJTi...     # Database CA
  client.crt: LS0tLS1CRUdJTi... # Database client cert
  client.key: LS0tLS1CRUdJTi... # Database client key

2. Deployment Configuration

# k8s/temporal-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: temporal-frontend
  namespace: temporal
spec:
  replicas: 3
  selector:
    matchLabels:
      app: temporal-frontend
  template:
    metadata:
      labels:
        app: temporal-frontend
    spec:
      serviceAccountName: temporal-server
      containers:
      - name: temporal
        image: temporalio/auto-setup:1.22.0
        ports:
        - containerPort: 7233
          name: rpc
        - containerPort: 7234
          name: membership
        volumeMounts:
        - name: server-certs
          mountPath: /etc/temporal/certs
          readOnly: true
        - name: config
          mountPath: /etc/temporal/config
        env:
        - name: SERVICES
          value: "frontend"
        - name: TLS_CONFIG_FILE
          value: "/etc/temporal/config/tls.yaml"
        resources:
          requests:
            memory: 1Gi
            cpu: 500m
          limits:
            memory: 2Gi
            cpu: 1000m
      volumes:
      - name: server-certs
        secret:
          secretName: temporal-server-tls
      - name: config
        configMap:
          name: temporal-config

---
apiVersion: v1
kind: Service
metadata:
  name: temporal-frontend
  namespace: temporal
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-west-2:ACCOUNT:certificate/CERT-ID
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: ssl
    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
spec:
  type: LoadBalancer
  ports:
  - port: 443
    targetPort: 7233
    protocol: TCP
    name: rpc-tls
  selector:
    app: temporal-frontend

3. Ingress TLS Configuration

# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: temporal-ingress
  namespace: temporal
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
    alb.ingress.kubernetes.io/ssl-redirect: "443"
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:ACCOUNT:certificate/CERT-ID
    alb.ingress.kubernetes.io/backend-protocol: HTTPS
    alb.ingress.kubernetes.io/backend-protocol-version: GRPC
    cert-manager.io/cluster-issuer: temporal-ca-issuer
spec:
  tls:
  - hosts:
    - temporal.company.com
    secretName: temporal-ingress-tls
  rules:
  - host: temporal.company.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: temporal-frontend
            port:
              number: 443

Client SDK TLS Configuration

1. Go SDK Configuration

// client/tls-client.go
package client

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io/ioutil"

    "go.temporal.io/sdk/client"
)

type TLSConfig struct {
    ServerName   string
    CertFile     string
    KeyFile      string
    CaFile       string
    InsecureSkip bool
}

func NewTLSClient(hostPort, namespace string, tlsConfig TLSConfig) (client.Client, error) {
    var tlsConf *tls.Config

    if tlsConfig.CertFile != "" && tlsConfig.KeyFile != "" {
        // Client certificate authentication
        cert, err := tls.LoadX509KeyPair(tlsConfig.CertFile, tlsConfig.KeyFile)
        if err != nil {
            return nil, fmt.Errorf("failed to load client certificates: %w", err)
        }

        tlsConf = &tls.Config{
            Certificates: []tls.Certificate{cert},
            ServerName:   tlsConfig.ServerName,
        }
    } else {
        // Server verification only
        tlsConf = &tls.Config{
            ServerName: tlsConfig.ServerName,
        }
    }

    // Load CA certificates
    if tlsConfig.CaFile != "" {
        caCert, err := ioutil.ReadFile(tlsConfig.CaFile)
        if err != nil {
            return nil, fmt.Errorf("failed to read CA file: %w", err)
        }

        caCertPool := x509.NewCertPool()
        if !caCertPool.AppendCertsFromPEM(caCert) {
            return nil, fmt.Errorf("failed to parse CA certificate")
        }

        tlsConf.RootCAs = caCertPool
    }

    tlsConf.InsecureSkipVerify = tlsConfig.InsecureSkip

    return client.Dial(client.Options{
        HostPort:  hostPort,
        Namespace: namespace,
        ConnectionOptions: client.ConnectionOptions{
            TLS: tlsConf,
        },
    })
}

// Example usage
func ExampleTLSClient() (client.Client, error) {
    return NewTLSClient("temporal.company.com:443", "default", TLSConfig{
        ServerName: "temporal.company.com",
        CertFile:   "/etc/temporal/certs/client.crt",
        KeyFile:    "/etc/temporal/certs/client.key",
        CaFile:     "/etc/temporal/certs/ca-chain.pem",
    })
}

2. Java SDK Configuration

// TLSClientFactory.java
package com.company.temporal.client;

import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.security.KeyStore;

public class TLSClientFactory {

    public static WorkflowClient createTLSClient(String hostPort, String namespace, TLSConfiguration config) 
            throws Exception {

        SSLContext sslContext = createSSLContext(config);

        WorkflowServiceStubsOptions serviceOptions = WorkflowServiceStubsOptions.newBuilder()
                .setTarget(hostPort)
                .setSslContext(sslContext)
                .build();

        WorkflowServiceStubs service = WorkflowServiceStubs.newServiceStubs(serviceOptions);

        WorkflowClientOptions clientOptions = WorkflowClientOptions.newBuilder()
                .setNamespace(namespace)
                .build();

        return WorkflowClient.newInstance(service, clientOptions);
    }

    private static SSLContext createSSLContext(TLSConfiguration config) throws Exception {
        SSLContext sslContext = SSLContext.getInstance("TLS");

        // Load truststore (CA certificates)
        KeyStore trustStore = KeyStore.getInstance("JKS");
        trustStore.load(new FileInputStream(config.getTrustStorePath()), 
                       config.getTrustStorePassword().toCharArray());

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);

        // Load keystore (client certificates) if available
        KeyManagerFactory keyManagerFactory = null;
        if (config.getKeyStorePath() != null) {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(new FileInputStream(config.getKeyStorePath()), 
                         config.getKeyStorePassword().toCharArray());

            keyManagerFactory = KeyManagerFactory.getInstance(
                    KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, config.getKeyStorePassword().toCharArray());
        }

        sslContext.init(
                keyManagerFactory != null ? keyManagerFactory.getKeyManagers() : null,
                trustManagerFactory.getTrustManagers(),
                null
        );

        return sslContext;
    }
}

class TLSConfiguration {
    private String trustStorePath;
    private String trustStorePassword;
    private String keyStorePath;
    private String keyStorePassword;

    // Getters and setters
    public String getTrustStorePath() { return trustStorePath; }
    public void setTrustStorePath(String trustStorePath) { this.trustStorePath = trustStorePath; }

    public String getTrustStorePassword() { return trustStorePassword; }
    public void setTrustStorePassword(String trustStorePassword) { this.trustStorePassword = trustStorePassword; }

    public String getKeyStorePath() { return keyStorePath; }
    public void setKeyStorePath(String keyStorePath) { this.keyStorePath = keyStorePath; }

    public String getKeyStorePassword() { return keyStorePassword; }
    public void setKeyStorePassword(String keyStorePassword) { this.keyStorePassword = keyStorePassword; }
}

Certificate Rotation

1. Automated Certificate Rotation

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

set -euo pipefail

NAMESPACE="temporal"
CERT_MANAGER_NAMESPACE="cert-manager"

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 trigger certificate renewal
renew_certificate() {
    local cert_name="$1"
    local namespace="$2"

    log "Renewing certificate: $cert_name in namespace: $namespace"

    # Annotate certificate to trigger renewal
    kubectl annotate certificate "$cert_name" -n "$namespace" \
        cert-manager.io/issue-temporary-certificate="$(date +%s)" --overwrite

    # Wait for renewal to complete
    kubectl wait certificate "$cert_name" -n "$namespace" \
        --for=condition=Ready --timeout=300s

    log "✓ Certificate $cert_name renewed successfully"
}

# Function to restart deployments after certificate renewal
restart_deployment() {
    local deployment="$1"
    local namespace="$2"

    log "Restarting deployment: $deployment"

    kubectl rollout restart deployment "$deployment" -n "$namespace"
    kubectl rollout status deployment "$deployment" -n "$namespace" --timeout=300s

    log "✓ Deployment $deployment restarted successfully"
}

# Check certificate expiration
check_certificate_expiration() {
    local cert_name="$1"
    local namespace="$2"

    local cert_info=$(kubectl get certificate "$cert_name" -n "$namespace" -o json)
    local not_after=$(echo "$cert_info" | jq -r '.status.notAfter')

    if [[ "$not_after" != "null" ]]; then
        local expiry_date=$(date -d "$not_after" +%s)
        local current_date=$(date +%s)
        local days_until_expiry=$(( (expiry_date - current_date) / 86400 ))

        log "Certificate $cert_name expires in $days_until_expiry days"

        # Renew if expires within 30 days
        if [[ $days_until_expiry -lt 30 ]]; then
            log "Certificate $cert_name is due for renewal"
            return 0
        fi
    fi

    return 1
}

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

    # List of certificates to check and potentially renew
    certificates=(
        "temporal-server-cert:$NAMESPACE"
        "temporal-client-cert:$NAMESPACE"
        "temporal-worker-cert:$NAMESPACE"
        "temporal-database-cert:$NAMESPACE"
    )

    # Check and renew certificates
    for cert_entry in "${certificates[@]}"; do
        IFS=':' read -r cert_name cert_namespace <<< "$cert_entry"

        if check_certificate_expiration "$cert_name" "$cert_namespace"; then
            renew_certificate "$cert_name" "$cert_namespace"

            # Restart relevant deployments
            case "$cert_name" in
                "temporal-server-cert")
                    restart_deployment "temporal-frontend" "$NAMESPACE"
                    restart_deployment "temporal-history" "$NAMESPACE"
                    restart_deployment "temporal-matching" "$NAMESPACE"
                    restart_deployment "temporal-worker" "$NAMESPACE"
                    ;;
                "temporal-client-cert")
                    # Restart client applications
                    restart_deployment "temporal-worker" "$NAMESPACE"
                    ;;
                "temporal-worker-cert")
                    restart_deployment "temporal-worker" "$NAMESPACE"
                    ;;
                "temporal-database-cert")
                    # Restart all services that connect to database
                    restart_deployment "temporal-frontend" "$NAMESPACE"
                    restart_deployment "temporal-history" "$NAMESPACE"
                    ;;
            esac
        fi
    done

    log "✓ Certificate rotation process completed"
}

main "$@"

2. Certificate Monitoring

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

---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: certificate-expiry-alerts
  namespace: cert-manager
spec:
  groups:
  - name: certificate-expiry
    rules:
    - alert: CertificateExpiringSoon
      expr: certmanager_certificate_expiration_timestamp_seconds - time() < 7 * 24 * 3600
      for: 1h
      labels:
        severity: warning
      annotations:
        summary: "Certificate {{ $labels.name }} in namespace {{ $labels.namespace }} is expiring soon"
        description: "Certificate {{ $labels.name }} in namespace {{ $labels.namespace }} will expire in less than 7 days"

    - alert: CertificateExpired
      expr: certmanager_certificate_expiration_timestamp_seconds - time() <= 0
      for: 0m
      labels:
        severity: critical
      annotations:
        summary: "Certificate {{ $labels.name }} in namespace {{ $labels.namespace }} has expired"
        description: "Certificate {{ $labels.name }} in namespace {{ $labels.namespace }} has expired and needs immediate attention"

This comprehensive TLS configuration guide provides enterprise-grade encryption and certificate management for Temporal.io deployments, ensuring secure communication across all components with automated certificate lifecycle management.