Skip to main content

Secure service invocation with access control

When one app calls another over service invocation, Catalyst routes the request and authenticates the caller by its App ID. By default, any app in a Catalyst project can invoke any other app. Service-invocation access control lets you lock this down: you declare which caller apps may invoke a target app — and which methods — and Catalyst denies everything else.

Access control is expressed as an access control list (ACL) inside a Catalyst Configuration resource that you attach to the target app's App ID. Unlike workflow access policies, there is no dedicated access-policy command — you manage a Configuration with the diagrid configuration commands (or a YAML manifest) and attach it to an App ID with diagrid appid update.

How access control works

A Configuration's accessControl block has two parts:

  • defaultAction — the global default, allow or deny, applied when no caller policy matches.
  • policies — a list of per-caller rules. Each names a caller appId and can narrow access to specific operations (request paths and HTTP verbs).

Catalyst evaluates the caller's authenticated App ID against these rules at the service-invocation boundary, before the request reaches the target app. A denied call is rejected with 403 Forbidden, and the target app never sees it.

Three behaviors to keep in mind:

  • No access control means open. If the target App ID has no Configuration with an accessControl block, every app in the project can invoke it. Attaching a deny-by-default configuration is what turns enforcement on.
  • Most specific rule wins. A matching operations rule takes precedence over the caller's defaultAction, which takes precedence over the global defaultAction.
  • App IDs are authenticated, not asserted. A caller cannot spoof another app's App ID — Catalyst authenticates it cryptographically during service invocation, so an allow-list is a real trust boundary.

Create and attach a policy

The fastest way to create a policy is with the --default-action and --policy flags:

diagrid configuration create order-app-acl \
--project my-project \
--default-action deny \
--policy orchestrator-app:allow \
--policy reporting-app:allow

This creates a deny-by-default configuration that allows only orchestrator-app and reporting-app. Attach it to the target app's App ID so it takes effect on inbound traffic:

diagrid appid update order-app --project my-project --app-config order-app-acl

Now any other app that tries to invoke order-app is rejected with 403 Forbidden. The --policy appID:action flag is all-or-nothing per caller — it allows or denies every method. To restrict callers to specific methods, define the configuration as a YAML manifest.

Fine-grained rules with a YAML manifest

To grant access to specific paths and verbs, declare operations on a caller and apply the manifest:

# order-app-acl.yaml
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: order-app-acl
spec:
accessControl:
defaultAction: deny
policies:
- appId: orchestrator-app
operations:
- name: /orders
httpVerb: ["POST"]
action: allow
- name: /orders/*
httpVerb: ["GET"]
action: allow
diagrid apply -f order-app-acl.yaml --project my-project
diagrid appid update order-app --project my-project --app-config order-app-acl

diagrid apply creates the configuration or replaces it in place, so you can re-run it as you iterate on the manifest. Here orchestrator-app can POST /orders and GET any /orders/{id} on order-app, and nothing else — every other caller and method is denied. Match the request path with name: an exact path like /orders, a single-segment wildcard like /orders/*, or a multi-segment wildcard like /orders/**/items. Set httpVerb to the methods the rule covers, or ["*"] for any method.

Start from deny by default

For the strictest posture, apply a configuration whose global default is deny with no caller policies, then attach it. Every cross-app call to the target App ID is denied until you add an allow rule:

# deny-all.yaml
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: order-app-acl
spec:
accessControl:
defaultAction: deny
diagrid apply -f deny-all.yaml --project my-project
diagrid appid update order-app --project my-project --app-config order-app-acl

From this baseline, layer in callers by editing the manifest and re-running diagrid apply -f. Apply one configuration per target app and keep its accessControl rules scoped to that app's callers.

Manage configurations

List the configurations in a project:

diagrid configuration list --project my-project

Inspect a configuration, including its full ACL:

diagrid configuration get order-app-acl --project my-project -o yaml

Update a configuration with flags. The flags replace the corresponding fields, so the example below replaces all caller policies with a single grant for orchestrator-app:

diagrid configuration update order-app-acl \
--project my-project \
--policy orchestrator-app:allow

To change fine-grained rules, edit the YAML manifest and re-run diagrid apply -f order-app-acl.yaml. To stop enforcing access control, delete the configuration:

diagrid configuration delete order-app-acl --project my-project

Verify a policy

To confirm a policy is enforced, invoke the target app from a caller that no rule allows. The caller receives a 403 Forbidden and the request never reaches order-app. Calls that match an allow rule continue to succeed.

note

Access control changes take 10–15 seconds to propagate after you apply the configuration or attach it to an App ID. If a caller is allowed or denied unexpectedly, wait and retry, and confirm the App ID in the policy exactly matches the caller's App ID (case-sensitive).

Next steps