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.
- How to list Namespaces in a Cluster using tctl
- How to register a new Namespace using tctl
- How to set the Namespace for a Temporal Client
- How to view (describe) Namespace metadata and details using tctl
A single Namespace is still multi-tenant.
You can use Namespaces to match the development lifecycle; for example, having separate dev
and prod
Namespaces.
Or you could use them to ensure Workflow Executions between different teams never communicate; such as ensuring that the teamA
Namespace never impacts the teamB
Namespace.
- If no other Namespace is specified, the Temporal Cluster uses the Namespace "default" for all Temporal SDKs and tctl.
- If you are 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 you are deploying through the Temporal Helm charts, you can create the "default" Namespace by using tctl; for example,
tctl namespace default
. We recommend using the default Namespace if you aren’t using multiple Namespaces.
- Case Insensitive: Because of DNS, Namespaces are case insensitive on the network and routing side. 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
.
All SDK connections (whether Workers or Clients) are to a specific namespace.
If not specified in WorkflowClientOptions, this defaults to the default
namespace.
- TypeScript
- JavaScript
const connection = await Connection.connect();
const client = new WorkflowClient({
connection,
namespace: 'your-custom-namespace', // defaults to 'default'
});
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.
- In order to connect to the server using TLS, set a truthy value (
true
or TLSConfig for custom options) in thetls
configuration option. - Use
ServerOptions.tls
when creating a new Worker andConnectionOptions.tls
for theConnection
constructor. - The client connection also accepts gRPC credentials at
ConnectionOptions.credentials
as long astls
is not also specified.
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 { Worker, NativeConnection } 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
- TypeScript
- JavaScript
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);
}
let serverRootCACertificate;
let clientCertificate;
let clientKey;
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
- TypeScript
- JavaScript
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,
};
}
export function getEnv() {
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.
- 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
- Clone the server samples repo and change to the
- Configure your Temporal Client and Worker to connect with mTLS
- Scaffold a new Temporal project with
npx @temporalio/create@latest
using thehello-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
- Scaffold a new Temporal project with
- Test the connection with
npm run start.watch
andnpm 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.