Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions daprdocs/content/en/concepts/security-concept.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@ Dapr provides application-level scoping for components by allowing you to specif

Dapr components can use Dapr's built-in secret management capability to manage secrets. Read the [secret store overview]({{% ref secrets-overview %}}) and [How-To: Reference secrets in components]({{% ref component-secrets %}}) for more details.

## Workflow access control

Dapr supports fine-grained access control for cross-app workflow and activity scheduling through the `WorkflowAccessPolicy` resource. A workflow access policy restricts which calling applications can schedule specific workflows and activities on a target application.

Workflow access policies are enforced on the callee side as a pure allow-list. For cross-app requests, the caller's identity is taken from the [SPIFFE](https://spiffe.io/) identity embedded in the mTLS certificate, so mTLS must be active for cross-app enforcement. Self-calls (where the caller and target are the same app) are always permitted. Policies support glob pattern matching for workflow and activity names.

Read [How-To: Apply workflow access policies]({{% ref workflow-access-policy %}}) for configuration details and examples.

## Bindings security

Authentication with a binding target is configured by the binding’s configuration file. Generally, you should configure the minimum required access rights. For example, if you only read from a binding target, you should configure the binding to use an account with read-only access rights.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,33 @@ public sealed class BusinessWorkflow : Workflow<string, string>

{{< /tabpane >}}

## Security: Workflow access policies

When using multi-application workflows, you may want to restrict which applications can schedule activities or child workflows on a target application. Dapr provides the `WorkflowAccessPolicy` resource for this purpose.

Policies are a pure allow-list and self-calls are always permitted, so the target application does not need to list itself in the `callers` to execute its own activities. The following example of a workflow access policy is applied to the `ml-worker` application. All policies that target a given appID (in this case `ml-worker`) are loaded by the sidecar when the application is instantiated.

This policy allows the `orchestrator-app` application to schedule the `TrainModel` and `ValidateModel` activities on the `ml-worker` application.

```yaml
apiVersion: dapr.io/v1alpha1
kind: WorkflowAccessPolicy
metadata:
name: ml-worker-policy
Comment thread
JoshVanL marked this conversation as resolved.
namespace: production
scopes:
- ml-worker
spec:
rules:
- callers:
- appID: orchestrator-app
activities:
- name: TrainModel
- name: ValidateModel
```

Read [How-To: Apply workflow access policies]({{% ref workflow-access-policy %}}) for more examples and details on the cross-app enforcement model.

## Related links

- [Try out Dapr Workflows using the quickstart]({{% ref workflow-quickstart.md %}})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ See [How-To: Manage workflows]({{< ref howto-manage-workflow.md >}}) for detaile
- Azure Cosmos DB has [payload and workflow complexity limitations]({{% ref "setup-azure-cosmosdb.md#workflow-limitations" %}}).
- AWS DynamoDB has [workflow complexity limitations]({{% ref "setup-azure-cosmosdb.md#workflow-limitations" %}}).

## Workflow security

Dapr provides fine-grained access control for workflow and activity scheduling through the `WorkflowAccessPolicy` resource. You can restrict which applications are permitted to start specific workflows or call specific activities on your application.

Comment thread
msfussell marked this conversation as resolved.
Workflow access policies for a given application (appID) are loaded by the sidecar when the application is instantiated, and are hot-reloaded thereafter when policies are added, updated, or removed. Policies are a pure allow-list evaluated on the callee side: a cross-app schedule is permitted only if some rule in some loaded policy matches the caller and the workflow or activity name.

This is especially important for multi-application workflows, where activities and child workflows execute across application boundaries. Read [How-To: Apply workflow access policies]({{% ref workflow-access-policy %}}) for full configuration details.

## Watch the demo

Watch [this video for an overview on Dapr Workflow](https://youtu.be/s1p9MNl4VGo?t=131):
Expand Down
253 changes: 253 additions & 0 deletions daprdocs/content/en/operations/security/workflow-access-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
---
type: docs
title: "How-To: Apply workflow access policies"
linkTitle: "Workflow access policy"
weight: 5000
description: "Restrict which applications can invoke workflow and activity operations on a target application"
---

Using workflow access policies, you can control which calling applications are permitted to invoke specific workflow operations on a target application. A `WorkflowAccessPolicy` is a Kubernetes CRD (or YAML resource in self-hosted mode) that is evaluated on the callee side. You can scope it to one or more target applications with `scopes`, or omit `scopes` to apply the policy to all applications.

Workflow access policies are a pure allow-list. A request is permitted if, and only if, some rule in some loaded policy matches the caller, the operation, and the workflow or activity name. With no policies loaded, all calls are allowed (open by default), preserving backward compatibility. Self-calls (where the caller App ID is the same as the target App ID) are always allowed, regardless of policy contents.

## Prerequisites

- [Dapr installed with mTLS enabled]({{% ref mtls %}}). mTLS is required for cross-app enforcement because the caller's identity is extracted from the SPIFFE ID embedded in the mTLS client certificate.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that you have to have Sentry running locally which is not the default. And getting Sentry running locally is not that straightforward. What is the guidance for local testing then, why can you not test with mTLS disabled? If you must have Sentry then we should consider making this easier to deploy with a CLI command or better still an option on the 'dapr init' command

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is impossible to do without sentry because the target has no way to determine who called it as this comes from the peer identity certificate- it may be possible to do this with some header shenanigans..

I think best we make it very easy to run sentry locally.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. But then we should make Sentry run with the Dapr init command as an option, i feel. What is the best way?


## Terminology

### Caller and target applications

Workflow access policies describe what *caller* applications are allowed to do against a *target* application:

- The **target** application is the application that hosts the workflow or activity being scheduled. The policy is enforced inside the target's Dapr sidecar (callee-side enforcement). A policy applies to a target through the `spec.scopes` field, which lists the target App IDs the policy applies to.
- The **caller** application is the application that is invoking the workflow or activity. For cross-app calls, the caller's App ID is taken from the [SPIFFE](https://spiffe.io/) identity in the mTLS client certificate. For same-sidecar (self) calls, the local App ID is used directly.

The SPIFFE ID embedded in the mTLS certificate has the format `spiffe://<trustdomain>/ns/<namespace>/<appid>`. The App ID and namespace are extracted from this identity when a workflow access policy is evaluated.

### Workflow and activity name matching

Workflow and activity names in policy rules are matched as either exact names or glob patterns. Glob matching follows Go's [`path.Match`](https://pkg.go.dev/path#Match) semantics:

- `*` matches any sequence of non-separator characters
- `?` matches any single non-separator character
- `[abc]` matches any character in the set (a character class)

### Operations

Workflow and activity rules grant the listed callers permission to `schedule` the named workflow or activity. A parent workflow on one app can schedule a child workflow or activity on a target app; the target's policy decides whether the call is permitted.

- Workflow rules require an `operations` field. Set it to `[schedule]`.
- Activity rules don't have an `operations` field. Activities only support scheduling.

{{% alert title="Operations are scheduling-only today" color="warning" %}}
`schedule` is the only operation that takes effect through the standard Dapr workflow APIs. The CRD enum accepts additional values (`terminate`, `raise`, `pause`, `resume`, `purge`, `get`, `rerun`) for forward compatibility with future cross-app workflow APIs, but those operations currently target the local sidecar, resolve to self-calls, and so always succeed regardless of policy. Use `[schedule]` in your rules until cross-app variants of the other APIs are available.
{{% /alert %}}

## CRD specification

Comment thread
msfussell marked this conversation as resolved.
The example below shows every field in a workflow access policy. The policy is applied to `orders-target` in the `production` namespace (via `scopes`). It grants the `frontend` and `ops-console` applications (the `callers`) permission to schedule `OrderWF`, schedule any workflow whose name starts with `Report`, and schedule the `ChargePayment` activity and any activity whose name starts with `RefundEvent`.

```yaml
apiVersion: dapr.io/v1alpha1
kind: WorkflowAccessPolicy
metadata:
name: orders-policy
namespace: production
scopes:
- orders-target
spec:
rules:
- callers:
- appID: frontend
- appID: ops-console
workflows:
- name: OrderWF
operations: [schedule]
- name: "Report*"
operations: [schedule]
activities:
- name: ChargePayment
- name: "RefundEvent*"
```

### Spec fields

Fields are listed in the order they appear in the YAML document.

| Field | Required | Type | Description |
|-------|:--------:|------|-------------|
| `scopes` | N | list | Target App IDs this policy applies to. If omitted or empty, the policy applies to all applications. The policy is always enforced on the callee (target) side. |
| `rules` | N | list | Allow-list of rules. A call is permitted if any rule matches. If `rules` is omitted or empty while a policy is loaded for the target, all cross-app calls are denied. |
| `rules[].callers` | Y | list | List of caller objects this rule applies to. Must contain at least one entry. Every caller must be listed explicitly (with the exception of self-calls, which are always allowed). |
| `rules[].callers[].appID` | Y | string | The Dapr App ID of the calling application. The caller must be in the same namespace as the target; cross-namespace calls are denied when policies are active. |
| `rules[].workflows` | N* | list | Workflow rules granted to the matched callers. |
| `rules[].workflows[].name` | Y | string | Exact name or [glob pattern](https://pkg.go.dev/path#Match) of the workflow. |
| `rules[].workflows[].operations` | Y | list | Set to `[schedule]`. The CRD also accepts `terminate`, `raise`, `pause`, `resume`, `purge`, `get`, `rerun` for forward compatibility; these have no effect today because the matching public workflow APIs do not route cross-app. |
| `rules[].activities` | N* | list | Activity rules granted to the matched callers. |
| `rules[].activities[].name` | Y | string | Exact name or [glob pattern](https://pkg.go.dev/path#Match) of the activity. Activities only support the `schedule` operation, so there is no `operations` field. |

\* At least one of `workflows` or `activities` must be present in each rule.

## Policy semantics

1. **No policies loaded:** All workflow and activity requests are allowed. This preserves backward compatibility when no policies exist.
2. **One or more policies loaded:** The target defaults to deny. A cross-app schedule is permitted only if some rule matches the caller and the workflow or activity name.
3. **Self-calls are always allowed:** If the caller App ID is the same as the target App ID, the request is permitted regardless of policy contents. This means a target app does not need to list itself in its own policy to schedule its own workflows or activities (including the internal reminder-based execution path).
4. **Cross-namespace calls are denied** when policies are active. A policy is namespaced and applies to target apps in its own namespace via `scopes`. The caller must also be in the same namespace as the target; calls from any other namespace are rejected even if the caller App ID appears in a rule.
5. **mTLS is required for cross-app enforcement:** if any policy is loaded and mTLS is not active, cross-app calls are denied because the caller's SPIFFE identity cannot be verified.
6. **Glob matching:** `*`, `?`, and character classes work on both workflow and activity names.

## Enforcement paths

Workflow access policies are enforced inside the orchestrator and activity actors, under the actor lock, after the workflow's internal state has been loaded. This eliminates any time-of-check-to-time-of-use race between resolving a workflow's name and dispatching the operation.

The cross-app paths covered today are scheduling a child workflow or activity on another app: a parent workflow on the calling app reaches the target app's workflow/activity actor, which evaluates the policy before dispatching. The same enforcement point also blocks cross-app callers attempting non-subject actor methods or trying to inject reminders into a target actor.

## Example policies

### Scenario 1: Restrict who can schedule a cross-app workflow

Allow `orchestrator-app` to schedule `OrderWF` on the `order-service` application in the `default` namespace. No other applications can schedule this workflow, with the exception of `order-service` itself (self-calls are always allowed).

```yaml
apiVersion: dapr.io/v1alpha1
kind: WorkflowAccessPolicy
metadata:
name: order-service-policy
namespace: default
scopes:
- order-service
spec:
rules:
- callers:
- appID: orchestrator-app
workflows:
- name: OrderWF
operations: [schedule]
```

### Scenario 2: Glob-matched workflow scheduling

Allow `analytics-app` to schedule any workflow whose name begins with `Report` on the `reporting-service` application.

```yaml
apiVersion: dapr.io/v1alpha1
kind: WorkflowAccessPolicy
metadata:
name: reporting-glob
namespace: default
scopes:
- reporting-service
spec:
rules:
- callers:
- appID: analytics-app
workflows:
- name: "Report*"
operations: [schedule]
```

### Scenario 3: Cross-app activities (multi-application workflows)

When using multi-application workflows, the target application does not need to list itself in the `callers` to execute its own activities. Self-calls are always allowed, so the policy only describes which *other* apps may schedule activities on the target. In the policy below, `orchestrator-app` can schedule the `TrainModel` and `ValidateModel` activities on the `ml-worker` application. No other applications can. The `orchestrator-app` must be in the same namespace as `ml-worker`, because cross-namespace calls are denied when policies are active.

```yaml
apiVersion: dapr.io/v1alpha1
kind: WorkflowAccessPolicy
metadata:
name: ml-worker-policy
namespace: production
scopes:
- ml-worker
spec:
rules:
- callers:
- appID: orchestrator-app
activities:
- name: TrainModel
- name: ValidateModel
```

`ml-worker` can still schedule `TrainModel` and `ValidateModel` on itself without appearing in the rule because it is the local app.

### Scenario 4: Mixed workflow and activity access for a single caller

A single rule can grant a caller scheduling access to both workflows and activities. Here the `api-gateway` application can schedule the `ChargeCustomer` workflow, the `ChargePayment` activity, and any activity whose name starts with `Refund` on the `payments-service` application.

```yaml
apiVersion: dapr.io/v1alpha1
kind: WorkflowAccessPolicy
metadata:
name: payments-policy
namespace: default
scopes:
- payments-service
spec:
rules:
- callers:
- appID: api-gateway
workflows:
- name: ChargeCustomer
operations: [schedule]
activities:
- name: ChargePayment
- name: "Refund*"
```

## Production best practices

- **Use deny by default.** Loading any `WorkflowAccessPolicy` for a target automatically denies cross-app requests that are not explicitly listed. Keep policies minimal and review them when adding new workflows.
Comment thread
msfussell marked this conversation as resolved.
- **Use glob patterns conservatively.** Patterns like `*` can grant broader access than intended. Prefer exact names where possible, and use glob patterns only for stable name families.
- **Enable mTLS.** mTLS is required for cross-app enforcement. Without mTLS, cross-app requests are denied when any policy is loaded.
- **Audit denial logs.** Dapr logs a warning whenever a request is denied by a workflow access policy. Use these logs to spot misconfiguration and unauthorized callers.
- **Use `scopes` to target the policy.** Apply each policy only to the apps that should enforce it, reducing the surface area each daprd has to load.

## Self-hosted setup

In self-hosted mode, place the workflow access policy YAML in the resources directory (`$HOME/.dapr/components` by default, or the path passed via `--resources-path`).

```yaml
apiVersion: dapr.io/v1alpha1
kind: WorkflowAccessPolicy
metadata:
name: my-policy
scopes:
- my-app
spec:
rules:
- callers:
- appID: frontend
workflows:
- name: MyWorkflow
operations: [schedule]
```

For cross-app enforcement, mTLS must be enabled by running Sentry locally. See [Setup & configure mTLS certificates]({{% ref mtls %}}) for details on configuring mTLS in self-hosted mode.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For cross-app enforcement, mTLS must be enabled by running Sentry locally. See [Setup & configure mTLS certificates]({{% ref mtls %}}) for details on configuring mTLS in self-hosted mode.
For cross-app enforcement, mTLS must be enabled by running Sentry locally. See [Setup & configure mTLS certificates for self hosted]({{% ref mtls/#self-hosted %}}) for details on configuring mTLS in self-hosted mode.


{{% alert title="Local development without mTLS" color="primary" %}}
mTLS is required only for *cross-app* enforcement, because the caller identity is taken from the SPIFFE ID in the mTLS certificate. Same-sidecar (self) calls do not depend on mTLS and are always permitted, so you can develop and test a single-app workflow locally without running Sentry. As soon as you need to validate cross-app policy enforcement, run with mTLS enabled.
{{% /alert %}}

## Kubernetes setup

In Kubernetes, apply the `WorkflowAccessPolicy` CRD with `kubectl`:

```bash
kubectl apply -f workflow-access-policy.yaml
```

The Dapr operator watches for `WorkflowAccessPolicy` resources and distributes them to the appropriate sidecars based on the `scopes` field. mTLS is enabled by default in Kubernetes mode.

## Hot-reload support

Workflow access policies are hot-reloaded in both Kubernetes and self-hosted modes. Creating, updating, or deleting a policy takes effect without restarting the Dapr sidecar.

## Related links

- [Security concepts]({{% ref security-concept.md %}})
- [Multi-application workflows]({{% ref workflow-multi-app.md %}})
- [Workflow overview]({{% ref workflow-overview.md %}})
- [Service invocation access control]({{% ref invoke-allowlist.md %}})
- [Setup & configure mTLS certificates]({{% ref mtls %}})
- [WorkflowAccessPolicy CRD reference]({{% ref workflow-access-policy-schema.md %}})
Loading
Loading