Secure service invocation with access control
When one app calls another over service invocation, Catalyst routes the request and authenticates the caller by its 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. 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 with diagrid appid update.
How access control works
A Configuration's accessControl block has two parts:
defaultAction— the global default,allowordeny, applied when no caller policy matches.policies— a list of per-caller rules. Each names a callerappIdand can narrow access to specific operations (request paths and HTTP verbs).
Catalyst evaluates the caller's authenticated 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 has no
Configurationwith anaccessControlblock, 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
operationsrule takes precedence over the caller'sdefaultAction, which takes precedence over the globaldefaultAction. - IDs are authenticated, not asserted. A caller cannot spoof another app's 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 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 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.
Access control changes take 10–15 seconds to propagate after you apply the configuration or attach it to an app. If a caller is allowed or denied unexpectedly, wait and retry, and confirm the ID in the policy exactly matches the caller's ID (case-sensitive).
Next steps
- Secure cross-app workflows with workflow access policies
- See the
Configurationmanifest reference for everyaccessControlfield - Learn how Catalyst secures the platform end to end