Companies have never had so many resources to manage, and with that comes the challenge of not just controlling access, but provisioning all the dependencies that make that access possible. Getting this right requires complex orchestration and guarantees: resources and access need to be both granted and revoked reliably. Orphaned resources or access are a real business risk. And the systems managing all of this are usually centralized, creating a single point of failure and compromise. Yet another risk.
Enter Temporal#
Temporal addresses two critical problems here.
First, guarantees around execution and determinism. Temporal ensures that when changes are made (accesses granted, access revoked, resources created, resources removed), those operations actually complete. Complex sagas that span multiple services and dependencies can be resolved and orchestrated reliably.
Second, distributed execution. Thanks to Temporal’s architecture with Workers and Nexus, we can push Workers into the specific environments or systems that need to interact with the services or accounts we’re provisioning. This limits the blast radius of any compromise, since sensitive service accounts only need to live at the source, on the Temporal Worker itself.
Enter Serverless Workflows#
Temporal offers a great development experience through its SDKs. But here’s the tension: identities, hierarchical roles, resource providers, and, most importantly, company policies all shape how and why users get access. Trying to capture all of that within a programmatically defined Workflow limits an organization’s ability to describe access patterns at runtime.
Serverless Workflows are an open-source specification from the Cloud Native Computing Foundation (CNCF) that formally describes a workflow using YAML or JSON, backed by extensive documentation. This gives us the flexibility to deploy many different workflows — or even re-engineer them at runtime — without touching the underlying Temporal Workflow code. It also sidesteps the pain of migrating long-running workflows. As a bonus, the spec uses CNCF CloudEvents, which map neatly onto Temporal Signal processing and Event emission.
Here’s a simple hello-world example:
document:
name: "hello-world"
version: "1.0.0"
do:
- greet:
set:
greeting: ${ "Hello, " + (.name // "World") + "!" }
Running distributed Serverless Workflows#
Now let’s combine these two technologies and see what it looks like in practice.
Imagine a principal (user or service) who needs access to an AWS resource. Before we can grant that, we need to check their entitlements against an internal gRPC service. Then we want to run an Osquery conditional access policy check on their local machine. Finally, we provision access via AWS Identity Center. This single Serverless Workflow spans three environments — a local device, a SaaS platform, and an internal network — each requiring its own Temporal Worker.
Serverless gRPC#
Let’s start with the internal gRPC service (though this could just as easily be HTTP). This entitlements service has no ingress: it lives in a sensitive part of the network.
Using Serverless Workflows, we define a task (state) to call the internal service.
- entitlements:
call: grpc
with:
proto:
endpoint: file://app/greet.proto
service:
name: Thand.Entitlements
host: localhost
port: 5011
method: GetEntitlements
arguments:
principal: ${ .user.uuid }
The challenge is that the standard Serverless Workflow Temporal Activity will execute this on any available Worker that has the gRPC Activity registered. We need to route the request to the right Worker.
That’s where Temporal Task Queues come in: they let us place messages on segregated queues so that specific Workers handle specific parts of the Workflow. By registering a Worker to a custom Task Queue, we ensure only certain Activities run on that system. (Worker Deployment names — part of Temporal Worker Versioning — offer another way to achieve this segregation.)
a.worker = worker.New(
temporalClient,
“hughs-laptop-1234”,
workerOptions,
)
By registering a Worker in each environment with its own Task Queue, we ensure the Temporal Workflow can execute each step of the Serverless Workflow definition while scheduling Activities onto the right queue and the right environment.
Serverless run tasks#
The Osquery conditional access check works similarly. We need to assess the state of the principal’s local system, which means routing the command to their machine specifically.
Osquery is a SQL-compatible engine that lets you query system state using SQL For example, SELECT global_state FROM alf; returns the global firewall state on a macOS device:
1if the firewall is enabled with exceptions2if the firewall is configured to block all incoming connections0otherwise
The Serverless Workflows spec provides a run task for executing shell commands or containers:
- runShell:
run:
shell:
command: 'osqueryi --json "SELECT * FROM alf WHERE global_state IS NOT NULL;"'
Serverless Workflows and jq#
With several tasks defined, we need a way to thread output for one step into the next. The Serverless Workflows spec supports jq for manipulating JSON/YAML responses:
- checkFirewall:
call: shell
with:
command: osqueryi --json "SELECT global_state FROM alf WHERE global_state IS NOT NULL;"
output:
from: '${ [0].global_state }'
export:
as: .firewall
The output attribute reformats the result before passing it to the next state. The export attribute writes it back to the Serverless Workflow’s root context, making it available for later steps to use or transform.
Enter Thand#
So we’ve covered a lot of ground. But you might be wondering: when Temporal executes a Serverless Workflow definition, how does it actually know where to route each request — which Task Queue, which environment? That’s what Thand solves.
Thand is an open-source implementation of the Serverless Workflows specification that adds extensions for Temporal capabilities, including Nexus. It’s a distributed provisioning and access management platform built on Serverless Workflows.
Thand introduces the concept of providers. These register capabilities alongside Temporal Workers, so the executing Temporal Workflow knows exactly which Task Queue a message needs to land on to reach the correct environment. Instead of manually wiring up Task Queues yourself, you declare what kind of provider a Task needs, and Thand handles the routing.
Let’s see how this changes our example. Remember, we had two problems:
- The Osquery check needs to run on the principal’s local devices
- The AWS provisioning needs to run in an AWS-connected environment.
With Thand, we can express that directly in the Serverless Workflow definition using thand blocks.
First, the device check. The thand: agent directive tells Thand to route this task to the Agent (Worker) registered as a device provider for the given identity. Importantly, this requires no separate central database of mappings between identities and devices — Temporal stores all this state:
do:
- osquery:
thand: agent
with:
identities: ${ $context.identities // [$context.user.email] }
# The do here creates a sub-workflow to be executed by the running agent
do:
- checkOsquery:
run:
shell:
command: 'osqueryi --json "SELECT global_state FROM alf WHERE global_state IS NOT NULL;"'
output:
from: '${ [0].global_state }'
export:
as: .firewall
The Thand Agent registers specific Task Queues (the one where their device’s Worker is listening) for the identity associated with the Agent and the device identity. The Osquery result gets exported to .firewall in the Serverless Workflow’s context, which is returned to the Temporal Workflow state.
Now, the AWS provisioning step. A second thand: aws block routes this task to a Worker running in the AWS environment, with full access to the Serverless Workflow’s accumulated state:
do:
- authorize:
# Authorize the user for the requested role
thand: authorize
with:
revocation: revoke
notifiers:
slack:
provider: slack
to: ${ $context.identities // [$context.user.email] }
message: >
You have been granted access.
email:
provider: email
to: ${ $context.identities // [$context.user.email] }
subject: "Access Granted"
message: >
You have been granted access.
Each step defined in the Serverless Workflow runs on a different Temporal Worker, in a different environment, but the workflow author never needs to think about Task Queue names or Worker topology directly. You describe what needs to happen and Thand routes the partition of the Workflow to where it should run — the Temporal platform handles the orchestration, routing, and execution guarantees underneath.
Wrapping up#
Privilege access is one of those problems that sounds simple until you realize it touches every layer of your infrastructure, and that every step needs to complete reliably or roll back cleanly. Centralizing that logic creates exactly the kind of single point of failure and compromise that keeps security teams up at night.
By combining Temporal’s Durable Execution and distributed Worker model with the flexibility of the Serverless Workflow specification, Thand offers a different path: one where access workflows are declarative, distributed by design, and backed by the guarantees that Temporal provides, ensuring that access and resources are provisioned and de-provisioned in the environment they reside.
If you’re managing access and provisioning across complex environments, check out Thand. And if you’re new to Temporal, get started here.