Skip to main content

Connection and Encryption in the TypeScript SDK

Temporal Workers and Clients connect with your Temporal Cluster via gRPC, and must be configured securely for production. There are three main features to know:

  • Namespaces help isolate code from each other
  • TLS Encryption helps encrypt code in transit
  • Data Converter helps encrypt code at rest (available soon)

Temporal Server internally has other Security features, particularly Authorization.

An important part of Temporal's security model is that Temporal Server only manages state and time - it never actually sees or runs your Workflow/Activity code. Code is hosted by Temporal Workers that you run, and Temporal Server only sees inbound/outbound gRPC messages. This eliminates a whole class of problems particularly when providing Temporal to multiple teams in your company, or when working with Temporal Cloud as a customer.

Namespaces

A Namespace is a unit of isolation within the Temporal Platform.

A single Namespace is still multi-tenant.

Usage

Namespaces are created on the Temporal Cluster, and provide a range of controls to achieve isolation on Workflow Executions.

  • Namespaces are a mechanism for resource isolation. Heavy traffic from one Namespace will not impact other Namespaces running on the same Cluster. For example, you can use Namespaces to match the development lifecycle by having separate dev and prod Namespaces.
  • If no other Namespace is specified, the Temporal Cluster uses the Namespace "default" for all Temporal SDKs and tctl. See the Registration section for details.
  • Namespaces created on self-hosted Temporal Clusters are case-sensitive. For example, foo and Foo are two different Namespaces. On Temporal Cloud, Namespaces are case-insensitive, and we recommend using lowercase for Namespace names to avoid potential issues.
  • Membership: Task Queue names and Workflow Ids must all correspond to a specific Namespace. For example, when a Workflow Execution is spawned, it does so within a specific Namespace.
  • Uniqueness: Temporal guarantees a unique Workflow Id within a Namespace. Workflow Executions may have the same Workflow Id if they are in different Namespaces.
  • Namespace Configuration: Various configuration options like the Retention Period and the Archival destination are configured per Namespace through a special CRUD API or through tctl.

Registration

Registering a Namespace creates the Namespace on the Temporal Cluster. When you register your Namespace, you must also set the Retention Period for the Namespace.

On Temporal Cloud, use the Temporal Cloud UI or tcld commands to create and manage Namespaces.

On self-hosted Temporal Cluster, you can register your Namespaces using tctl (recommended) or programmatically using APIs. Note that these APIs and tctl commands will not work with Temporal Cloud.

All SDKs require a Namespace on the Temporal Cluster (or Temporal Cloud) for their Client calls. If not set using Client options, the Workflow Client API looks for the default Namespace. If there is no default Namespace registered with your Temporal Cluster (or Temporal Cloud), all calls will throw errors. You must register your Namespace with the Temporal Cluster (or Temporal Cloud) before setting it in your Client.

On self-hosted Temporal Clusters, you can register your Namespaces in the following ways:

  • In your Cluster setup, create your Namespaces, including the default, in your setup script. For example:

    • If deploying through Docker Compose or using the auto-setup image in a custom Docker Compose application, the Namespace "default" is created, through the auto-setup script.
    • If deploying through the Temporal Helm charts, you can create the "default" Namespace by using tctl; for example, tctl --namespace default namespace register.
  • Use the tctl namespace register command with the --retention modfiier to register your Namespaces, one at a time, and set the Retention Period on each.

  • In your Client program, register your Namespace using RegisterNamespaceRequest API available in all the SDKs.

Note that registering a Namespace takes up to 15 seconds to complete. Ensure that you are waiting for this process to complete before making calls to the Namespace.

Manage Namespaces

Use a custom Authorizer on your Frontend Service in the Temporal Cluster to set restrictions on who can create, update, or deprecate Namespaces.

On Temporal Cloud, use the Temporal Cloud UI or tcld commands to manage Namespaces.

On self-hosted Temporal Cluster, you can manage your registered Namespaces using tctl (recommended) or programmatically using APIs. Note that these APIs and tctl commands will not work with Temporal Cloud.

  • Update information and configuration for a registered Namespace on your Temporal Cluster:

    • With tctl: tctl namespace update
    • Use the [UpdateNamespace API]9(/application-development/features#namespaces) to update configuration on a Namespace.
  • Get details for a registered Namespace on your Temporal Cluster:

  • Get details for all registered Namespaces on your Temporal Cluster:

  • Deprecate a Namespace: The DeprecateNamespace API updates the state of a registered Namespace to "DEPRECATED". Once a Namespace is deprecated, you cannot start new Workflow Executions on it. All existing and running Workflow Executions on a deprecated Namespace will continue to run.

Setting

Set Namespaces in your SDK Client to isolate your Workflow Executions to the Namespace. If you do not set a Namespace, all Workflow Executions started using the Client will be associated with the "default" Namespace. This means, you must have a default Namespace called "default" registered with your Temporal Cluster. See Registration for details.

All SDK connections (whether Workers or Clients) are to a specific namespace. If not specified in WorkflowClientOptions, this defaults to the default namespace.

const connection = await Connection.connect();

const client = new WorkflowClient({
connection,
namespace: 'your-custom-namespace', // defaults to 'default'
});

Encryption in transit with mTLS

There are two classes in the SDK that connect to the Temporal server, the Worker and the client Connection. When instantiating either of them, you may choose whether to connect securely or not.

A full example for Clients looks like this:

import { Connection, WorkflowClient } from '@temporalio/client';

const connection = await Connection.connect({
// defaults port to 7233 if not specified
address: 'foo.bar.tmprl.cloud',
tls: {
// set to true if TLS without mTLS
// See docs for other TLS options
clientCertPair: {
crt: clientCert,
key: clientKey,
},
},
});
const client = new WorkflowClient({
connection,
namespace: 'foo.bar', // as explained in Namespaces section
});

A full example for Workers looks like this:

import { NativeConnection, Worker } from '@temporalio/worker';
import * as activities from './activities';

async function run() {
const connection = await NativeConnection.connect({
address: 'foo.bar.tmprl.cloud', // defaults port to 7233 if not specified
tls: {
// set to true if TLS without mTLS
// See docs for other TLS options
clientCertPair: {
crt: clientCert,
key: clientKey,
},
},
});

const worker = await Worker.create({
connection,
namespace: 'foo.bar', // as explained in Namespaces section
// ...
});
await worker.run();
}

run().catch((err) => {
console.error(err);
process.exit(1);
});

If you are using mTLS, is completely up to you how to get the clientCert and clientKey pair into your code, whether it is reading from filesystem, secrets manager, or both. Just keep in mind that they are whitespace sensitive and some environment variable systems have been known to cause frustration because they modify whitespace.

Example code that works for local dev and for certs hosted on AWS S3
let serverRootCACertificate: Buffer | undefined;
let clientCertificate: Buffer | undefined;
let clientKey: Buffer | undefined;
if (certificateS3Bucket) {
const s3 = new S3client({ region: certificateS3BucketRegion });
serverRootCACertificate = await s3.getObject({
bucket: certificateS3Bucket,
key: serverRootCACertificatePath,
});
clientCertificate = await s3.getObject({
bucket: certificateS3Bucket,
key: clientCertPath,
});
clientKey = await s3.getObject({
bucket: certificateS3Bucket,
key: clientKeyPath,
});
} else {
serverRootCACertificate = fs.readFileSync(serverRootCACertificatePath);
clientCertificate = fs.readFileSync(clientCertPath);
clientKey = fs.readFileSync(clientKeyPath);
}

Thanks to our Design Partner [Mina Abadir](https://twitter.com/abadir) for sharing this._

Connecting to Temporal Cloud (with mTLS)

The Hello World mTLS sample shows how to connect to a Temporal Cloud account. After signing up for Temporal Cloud, you should have a namespace, a server address, and a client certificate and key. Use the following environment variables to set up the sample:

  • TEMPORAL_ADDRESS: looks like foo.bar.tmprl.cloud (NOT web.foo.bar.tmprl.cloud)
  • TEMPORAL_NAMESPACE: looks like foo.bar
  • TEMPORAL_CLIENT_CERT_PATH: e.g. /tls/ca.pem (file contents start with -----BEGIN CERTIFICATE-----)
  • TEMPORAL_CLIENT_KEY_PATH: e.g. /tls/ca.key (file contents start with -----BEGIN PRIVATE KEY-----)

You can leave the remaining vars, like TEMPORAL_SERVER_NAME_OVERRIDE and TEMPORAL_SERVER_ROOT_CA_CERT_PATH blank. There is another var, TEMPORAL_TASK_QUEUE, which the example defaults to 'hello-world-mtls' but you can customize as needed.

Example environment settings
export function getEnv(): Env {
return {
// NOT web.foo.bar.tmprl.cloud
address: 'foo.bar.tmprl.cloud',
namespace: 'foo.bar',
// in project root
clientCertPath: 'foobar.pem',
clientKeyPath: 'foobar.key',
// just to ensure task queue is same on client and worker, totally optional
taskQueue: process.env.TEMPORAL_TASK_QUEUE || 'hello-world-mtls',
// // not usually needed
// serverNameOverride: process.env.TEMPORAL_SERVER_NAME_OVERRIDE,
// serverRootCACertificatePath: process.env.TEMPORAL_SERVER_ROOT_CA_CERT_PATH,
};
}

If you have misconfigured your connection somehow, you will get an opaque [TransportError: transport error] error. Read through your settings carefully and contact us if you are sure you have checked everything.

Note the difference between the gRPC and Temporal Web endpoints:

  • The gRPC endpoint has a DNS address of <Namespace_ID>.tmprl.cloud, for example: accounting-production.f45a2.tmprl.cloud.
  • The Temporal Web endpoint is web.<Namespace_ID>.tmprl.cloud, for example: https://web.accounting-production.f45a2.tmprl.cloud.

Local mTLS sample tutorial

Follow this tutorial for setting up mTLS (Mutual TLS authentication) with Temporal Server, Client, and Worker locally. For Temporal Cloud customers, there is a separate tutorial above.

  1. Set up Temporal Server with mTLS encryption locally
    • Clone the server samples repo and change to the tls/tls-simple directory
    • Follow these instructions to set up a local server with mTLS
    • The sample does not register the default Namespace on startup, register it with: docker exec -it tls-simple_temporal-admin-tools_1 tctl n re --retention 1 default
  2. Configure your Temporal Client and Worker to connect with mTLS
    • Scaffold a new Temporal project with npx @temporalio/create@latest using the hello-world-mtls template, or copy the relevant configuration from the snippets below into an existing project.
    • Export the required environment variables:
      export TEMPORAL_ADDRESS=localhost
      export TEMPORAL_NAMESPACE=default
      export TEMPORAL_CLIENT_CERT_PATH=/path/to/samples-server/tls/tls-simple/certs/client.pem
      export TEMPORAL_CLIENT_KEY_PATH=/path/to/samples-server/tls/tls-simple/certs/client.key
      # just for the local mTLS sample
      export TEMPORAL_SERVER_ROOT_CA_CERT_PATH=/path/to/samples-server/tls/tls-simple/certs/ca.cert
      export TEMPORAL_SERVER_NAME_OVERRIDE=tls-sample
  3. Test the connection with npm run start.watch and npm run workflow. You should see everything working as per the regular Hello World tutorial.

Temporal has no opinions on production deployment strategy other than the connections and architecture displayed here.

Encryption at rest with Payload Codec