Skip to main content

Overview

Cadence provides comprehensive security features including OAuth-based authorization, mutual TLS, SASL authentication for Kafka, and network encryption. This guide covers production security best practices.

Authorization

Cadence supports two authorization modes:
  1. NoopAuthorizer - No authorization (development only)
  2. OAuthAuthorizer - JWT-based authorization with RS256

OAuth Authorization

Configuration

authorization:
  oauthAuthorizer:
    enable: true
    maxJwtTTL: 86400  # 24 hours in seconds
    jwtCredentials:
      algorithm: "RS256"
      publicKey: "/etc/cadence/keys/public.pem"

JWT Public Key Configuration

Cadence validates JWTs using RSA public keys. The public key must be in PEM format:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
Only RS256 (RSA with SHA-256) algorithm is supported. Ensure your identity provider issues RS256 JWTs.

Using External Identity Providers

For external OAuth providers (Auth0, Okta, etc.), use JWKS URL:
authorization:
  oauthAuthorizer:
    enable: true
    maxJwtTTL: 86400
    provider:
      jwksURL: "https://your-domain.auth0.com/.well-known/jwks.json"
      groupsAttributePath: "https://your-app/groups"
      adminAttributePath: "https://your-app/admin"

JWT Claims

Cadence expects these claims in JWTs:
{
  "sub": "[email protected]",
  "exp": 1709548800,
  "iat": 1709545200,
  "groups": ["team-a", "team-b"],
  "admin": true
}
  • sub: Subject (user identifier)
  • exp: Expiration timestamp (must be within maxJwtTTL)
  • iat: Issued at timestamp
  • groups: User group memberships (custom claim path configurable)
  • admin: Admin flag (custom claim path configurable)

Authorization Validation

Validation rules:
// From common/config/authorization.go

// 1. Only one authorizer can be enabled
if oauthAuthorizer.Enable && noopAuthorizer.Enable {
    return error
}

// 2. MaxJwtTTL must be positive
if maxJwtTTL <= 0 {
    return error
}

// 3. Either jwtCredentials or provider required
if jwtCredentials == nil && provider == nil {
    return error
}

// 4. Algorithm must be RS256
if algorithm != "RS256" {
    return error
}
Cadence validates JWT signatures but does not perform additional authorization checks beyond signature validation. Implement fine-grained access control in your API gateway or via domain-level permissions.

Client Authentication

Clients must include the JWT in RPC headers:
import (
    "go.uber.org/cadence/client"
    "go.uber.org/yarpc"
)

c, err := client.NewClient(
    client.Options{
        HostPort:     "cadence-frontend:7933",
        Domain:       "my-domain",
        MetricsScope: metricsScope,
        HeadersProvider: func() map[string]string {
            return map[string]string{
                "authorization": "Bearer " + jwtToken,
            }
        },
    },
)

TLS/SSL Configuration

Server-Side TLS

Enable TLS for gRPC endpoints:
services:
  frontend:
    rpc:
      grpcPort: 7833
      tls:
        enabled: true
        certFile: "/etc/cadence/tls/server.crt"
        keyFile: "/etc/cadence/tls/server.key"
        caFile: "/etc/cadence/tls/ca.crt"
        requireClientAuth: true
        enableHostVerification: true
        serverName: "cadence-frontend.example.com"

TLS Configuration Options

OptionDescriptionRequired
enabledEnable TLSYes
certFileServer certificate pathYes
keyFileServer private key pathYes
caFileCA certificate for client verificationFor mTLS
caFilesMultiple CA certificatesFor mTLS
requireClientAuthEnforce mutual TLSNo
enableHostVerificationVerify client hostnameNo
serverNameExpected server name in certificateNo

Mutual TLS (mTLS)

For mutual TLS, both server and client present certificates:

Server Configuration

services:
  frontend:
    rpc:
      tls:
        enabled: true
        certFile: "/etc/cadence/tls/server.crt"
        keyFile: "/etc/cadence/tls/server.key"
        caFile: "/etc/cadence/tls/ca.crt"
        requireClientAuth: true  # Require client certificates

Client Configuration

import (
    "crypto/tls"
    "crypto/x509"
    "go.uber.org/cadence/client"
)

// Load client certificate
cert, err := tls.LoadX509KeyPair(
    "/etc/cadence/tls/client.crt",
    "/etc/cadence/tls/client.key",
)

// Load CA certificate
caCert, err := ioutil.ReadFile("/etc/cadence/tls/ca.crt")
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

tlsConfig := &tls.Config{
    Certificates: []tls.Certificate{cert},
    RootCAs:      caCertPool,
    ServerName:   "cadence-frontend.example.com",
}

c, err := client.NewClient(
    client.Options{
        HostPort:  "cadence-frontend:7833",
        Domain:    "my-domain",
        TLSConfig: tlsConfig,
    },
)

Cross-Cluster TLS

For cross-DC replication, configure TLS per cluster:
clusterGroupMetadata:
  currentClusterName: "cluster1"
  clusterGroup:
    cluster1:
      enabled: true
      initialFailoverVersion: 1
      rpcName: "cadence-frontend"
      rpcAddress: "cluster1.example.com:7833"
      rpcTransport: "grpc"
      tls:
        enabled: true
        caFile: "/etc/cadence/tls/cluster1-ca.crt"
        certFile: "/etc/cadence/tls/cluster1-client.crt"
        keyFile: "/etc/cadence/tls/cluster1-client.key"
        enableHostVerification: true
        serverName: "cluster1.example.com"
    cluster2:
      enabled: true
      initialFailoverVersion: 2
      rpcName: "cadence-frontend"
      rpcAddress: "cluster2.example.com:7833"
      rpcTransport: "grpc"
      tls:
        enabled: true
        caFile: "/etc/cadence/tls/cluster2-ca.crt"
        certFile: "/etc/cadence/tls/cluster2-client.crt"
        keyFile: "/etc/cadence/tls/cluster2-client.key"
        enableHostVerification: true
        serverName: "cluster2.example.com"

Database Security

Cassandra TLS

persistence:
  datastores:
    default:
      cassandra:
        hosts: "cassandra1,cassandra2,cassandra3"
        keyspace: "cadence"
        tls:
          enabled: true
          caFile: "/etc/cadence/tls/cassandra-ca.crt"
          certFile: "/etc/cadence/tls/cassandra-client.crt"
          keyFile: "/etc/cadence/tls/cassandra-client.key"
          enableHostVerification: true

MySQL/PostgreSQL TLS

MySQL

persistence:
  datastores:
    default:
      sql:
        pluginName: "mysql"
        connectAddr: "mysql.example.com:3306"
        connectProtocol: "tcp"
        databaseName: "cadence"
        tls:
          enabled: true
          sslmode: "require"  # Options: disable, require, verify-ca, verify-full
          caFile: "/etc/cadence/tls/mysql-ca.pem"
          certFile: "/etc/cadence/tls/mysql-client-cert.pem"
          keyFile: "/etc/cadence/tls/mysql-client-key.pem"

PostgreSQL

persistence:
  datastores:
    default:
      sql:
        pluginName: "postgres"
        connectAddr: "postgres.example.com:5432"
        connectProtocol: "tcp"
        databaseName: "cadence"
        tls:
          enabled: true
          sslmode: "verify-full"  # Options: disable, require, verify-ca, verify-full
          caFile: "/etc/cadence/tls/postgres-ca.pem"
Use verify-full for PostgreSQL and verify-ca for MySQL in production to prevent MITM attacks.

Kafka Security (SASL)

For Kafka replication and async workflows:

SASL/PLAIN

kafka:
  clusters:
    test:
      brokers:
        - "kafka1:9093"
        - "kafka2:9093"
      tls:
        enabled: true
        caFile: "/etc/cadence/tls/kafka-ca.crt"
      sasl:
        enabled: true
        mechanism: "PLAIN"
        user: "cadence-user"
        password: "${KAFKA_PASSWORD}"

SASL/SCRAM

kafka:
  clusters:
    test:
      brokers:
        - "kafka1:9093"
      sasl:
        enabled: true
        mechanism: "SCRAM-SHA-512"  # or "SCRAM-SHA-256"
        user: "cadence-user"
        password: "${KAFKA_PASSWORD}"
For SASL support, see common/authorization/scram_client.go for implementation details.

Network Security

Port Configuration

Default ports:
ServiceTChannelgRPCHTTPMetrics
Frontend79337833-9090
History7934--9091
Matching7935--9092
Worker7939--9093

Firewall Rules

Inbound Rules

# Frontend (from clients)
Allow TCP 7933 from 0.0.0.0/0  # TChannel
Allow TCP 7833 from 0.0.0.0/0  # gRPC

# Inter-service (within cluster)
Allow TCP 7933-7939 from <cluster_cidr>

# Metrics (from Prometheus)
Allow TCP 9090-9093 from <monitoring_cidr>

Outbound Rules

# Database
Allow TCP 9042 to <cassandra_hosts>   # Cassandra
Allow TCP 3306 to <mysql_hosts>        # MySQL
Allow TCP 5432 to <postgres_hosts>     # PostgreSQL

# Kafka
Allow TCP 9092-9093 to <kafka_brokers>

# Cross-DC replication
Allow TCP 7833 to <remote_cluster_cidr>

Network Isolation

Best practices:
  1. Deploy in private subnets - No direct internet access
  2. Use NAT gateway - For outbound internet (archival to S3/GCS)
  3. VPC peering - For cross-region/cross-account replication
  4. Security groups - Restrict access by source IP/security group

Secrets Management

Environment Variables

Use environment variable substitution for secrets:
persistence:
  datastores:
    default:
      cassandra:
        user: "${CASSANDRA_USER}"
        password: "${CASSANDRA_PASSWORD}"

kafka:
  clusters:
    test:
      sasl:
        user: "${KAFKA_USER}"
        password: "${KAFKA_PASSWORD}"

Secrets Management Systems

Integrate with external secret stores:

AWS Secrets Manager

# Fetch secrets at startup
export MYSQL_PASSWORD=$(aws secretsmanager get-secret-value \
  --secret-id cadence/mysql/password \
  --query SecretString \
  --output text)

./cadence-server start

HashiCorp Vault

# Fetch secrets from Vault
export CASSANDRA_PASSWORD=$(vault kv get \
  -field=password \
  secret/cadence/cassandra)

./cadence-server start

Kubernetes Secrets

apiVersion: v1
kind: Secret
metadata:
  name: cadence-secrets
type: Opaque
data:
  mysql-password: <base64-encoded>
  kafka-password: <base64-encoded>
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cadence-frontend
spec:
  template:
    spec:
      containers:
      - name: frontend
        env:
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: cadence-secrets
              key: mysql-password

Audit Logging

Enable Audit Logs

log:
  level: "info"
  encoding: "json"
  outputFile: "/var/log/cadence/audit.log"
Audit log entries include:
{
  "ts": "2026-03-04T10:15:30.123Z",
  "level": "info",
  "msg": "API request",
  "operation": "StartWorkflowExecution",
  "domain": "my-domain",
  "principal": "[email protected]",
  "remote-addr": "10.0.1.42",
  "wf-id": "workflow-123"
}

Audit Events

Key events to monitor:
  • Workflow start/terminate/cancel
  • Domain registration/update
  • Authorization failures
  • Admin operations (shard management, queue operations)
Ship audit logs to a centralized logging system (Splunk, Elasticsearch) for compliance and forensics.

Compliance

Data Encryption

At Rest

  • Database: Enable encryption at rest (Cassandra transparent encryption, RDS encryption)
  • Archival: Enable S3 SSE or GCS encryption
  • Local storage: Use encrypted volumes (LUKS, EBS encryption)

In Transit

  • Client ↔ Frontend: TLS/mTLS
  • Inter-service: TLS (optional but recommended)
  • Database connections: TLS
  • Kafka: TLS + SASL

Data Retention

domainDefaults:
  archival:
    history:
      status: "enabled"
      URI: "s3://my-bucket/cadence-archival"
    visibility:
      status: "enabled"
      URI: "s3://my-bucket/cadence-visibility"
  retention: "7d"  # Primary storage retention
Workflow history is retained in primary storage for the retention period, then archived. Archived data is retained indefinitely unless you implement archival bucket lifecycle policies.

Security Best Practices

1. Principle of Least Privilege

  • Use separate database users per service
  • Grant minimal required permissions
  • Rotate credentials regularly

2. Network Segmentation

  • Deploy services in private subnets
  • Use security groups/network policies
  • Isolate production from non-production

3. TLS Everywhere

  • Enable TLS for all RPC communication
  • Use mTLS for inter-cluster replication
  • Enable TLS for database connections

4. Secrets Rotation

  • Rotate JWT signing keys annually
  • Rotate database passwords quarterly
  • Rotate TLS certificates before expiry

5. Monitoring and Alerting

# Failed authorization attempts
rate(cadence_authorization_failed[5m]) > 10

# TLS handshake failures
rate(cadence_tls_handshake_errors[5m]) > 5

Troubleshooting

Authorization Failures

Check JWT validation:
# Decode JWT
echo $JWT_TOKEN | cut -d. -f2 | base64 -d | jq

# Verify expiration
date -d @$(echo $JWT_TOKEN | cut -d. -f2 | base64 -d | jq -r .exp)

TLS Connection Issues

Verify certificate chain:
# Test TLS connection
openssl s_client -connect cadence-frontend:7833 \
  -CAfile /etc/cadence/tls/ca.crt \
  -cert /etc/cadence/tls/client.crt \
  -key /etc/cadence/tls/client.key

# Verify certificate
openssl x509 -in /etc/cadence/tls/server.crt -text -noout

Database TLS Issues

Test database connectivity:
# MySQL
mysql -h mysql.example.com \
  --ssl-ca=/etc/cadence/tls/mysql-ca.pem \
  --ssl-cert=/etc/cadence/tls/mysql-client-cert.pem \
  --ssl-key=/etc/cadence/tls/mysql-client-key.pem

# PostgreSQL
psql "host=postgres.example.com \
  sslmode=verify-full \
  sslrootcert=/etc/cadence/tls/postgres-ca.pem \
  dbname=cadence"

See Also

Build docs developers (and LLMs) love