Microservices architectures often require authorization decisions that go beyond simple allow-or-deny rules -- for example, granting different API access based on user roles, JWT claims, or request body content. Service Mesh (ASM) integrates Open Policy Agent (OPA) as a sidecar alongside your application pods to enforce these policies locally, with no additional network hop.
You define policies in Rego on the ASM control plane, and ASM pushes them to all clusters on the data plane automatically. Policies can allow or deny requests based on URL paths, HTTP methods, JSON Web Tokens (JWTs), request bodies, or any combination of these attributes. Policy updates take effect dynamically without pod restarts.
The following sections walk through the end-to-end workflow: enabling OPA, creating a role-based access control (RBAC) policy, deploying the Bookinfo sample application, and verifying access control. Three additional scenarios demonstrate JWT-based and request-body-based authorization.
How OPA works in ASM
OPA is a Graduated project of Cloud Native Computing Foundation (CNCF). In ASM, OPA runs as a sidecar container (opa-istio) in the same pod as your application and the Envoy sidecar proxy (istio-proxy). The authorization flow works as follows:
Envoy receives a request destined for your microservice.
Envoy queries the local OPA sidecar for an authorization decision.
OPA evaluates the request against Rego policies and returns an allow or deny verdict.
Only authorized requests reach the application container.
Because OPA evaluates policies locally within the pod, there is no additional network hop for authorization checks. This improves both latency and availability compared to calling an external authorization service.

OPA occupies ports 15081 and 9191 within the pod.
Prerequisites
Before you begin, make sure that you have:
An ASM instance of version 1.9.7 or later. For more information, see Create an ASM instance
A cluster added to the ASM instance. For more information, see Add a cluster to an ASM instance
Automatic sidecar proxy injection enabled for the
defaultnamespace. For more information, see the "Enable automatic sidecar proxy injection" section of the Manage global namespaces topic
Step 1: Enable the OPA plug-in
Log on to the ASM console. In the left-side navigation pane, choose Service Mesh > Mesh Management.
On the Mesh Management page, click the name of the ASM instance. In the left-side navigation pane, choose Mesh Security Center > OPA Policy.
On the OPA Policy page, select Enable Open Policy Agent (OPA) Plug-in and Enable OPA Injection Range Control, and then click Enable OPA. In the Note message, click OK.
Step 2: Create an OPA policy
OPA policies created on the ASM control plane are automatically pushed to clusters on the data plane, where OPA enforces them in each pod.
The following example policy implements basic RBAC using HTTP Basic authentication:
guest1has theguestrole -- can only GET/productpage.admin1has theadminrole -- can GET both/productpageand/api/v1/products.
Choose one of the following methods to create the policy.
Method 1: Use the ASM console
Log on to the ASM console. In the left-side navigation pane, choose Service Mesh > Mesh Management.
On the Mesh Management page, click the name of the ASM instance. In the left-side navigation pane, choose Mesh Security Center > OPA Policy.
On the OPA Policy page, click Create, select default from the Namespace drop-down list, set Name to bookinfo-opa, and then click Add Matching Label. In the Matching Label section, set Name to version and Value to v1. Copy the following Rego rules to the code editor, and then click Create.
Method 2: Use kubectl
Create a file named
opa.yamlwith the following content: The following table describes the key fields in theASMOPAPolicyresource.Field Description spec.policyPolicy content written in Rego. spec.workloadSelectorLimits policy scope to pods with the specified labels in the namespace. If omitted, the policy applies to all pods in the namespace. user_rolesMaps users to roles. In this example, guest1is assigned theguestrole andadmin1is assigned theadminrole.role_permsMaps roles to allowed HTTP method and path combinations. Run the following command to apply the policy: To connect kubectl to the ASM control plane, see Use kubectl on the control plane to access Istio resources.
kubectl apply -f opa.yaml
Only one OPA policy per pod should contain the
default allowfield. Multipledefault allowdeclarations across policies targeting the same pod prevent dynamic policy updates.Use labels in
workloadSelectorto scope policies precisely. An invalid Rego policy applied broadly can block all traffic to affected services.By default, the
default allowfield is set tofalse. Do not redeclare it in your policy, or a conflict occurs.
Step 3: Deploy the sample application and verify OPA injection
Deploy the Bookinfo sample application and confirm that OPA is injected into each pod.
Deploy the Bookinfo application. For more information, see Deploy an application in an ASM instance.
Create an ingress gateway, an Istio gateway, and a virtual service. For more information, see Use Istio resources to route traffic to different versions of a service.
Verify that OPA is injected into each Bookinfo pod: On the Container tab, confirm that both
istio-proxy(the sidecar proxy) andopa-istio(the OPA sidecar) are present. Repeat this check for each Bookinfo pod.Log on to the ACK console. In the left-side navigation pane, click Clusters.
On the Clusters page, click the cluster name. In the left-side navigation pane, choose Workloads > Pods.
On the Pods page, select default from the Namespace drop-down list and click a pod name.

Step 4: Verify that the OPA policy works
Test access control by sending requests as different users.
Test the guest1 user (guest role)
Access
/productpage-- expected result:200 OK(allowed). Expected output:curl -X GET http://<ingress-gateway-ip>/productpage --user guest1:password -IHTTP/1.1 200 OKAccess
/api/v1/products-- expected result:403 Forbidden(denied). Expected output:curl -X GET http://<ingress-gateway-ip>/api/v1/products --user guest1:password -IHTTP/1.1 403 Forbidden
The guest1 user has the guest role, which only permits GET requests to /productpage.
Test the admin1 user (admin role)
Access
/productpage-- expected result:200 OK. Expected output:curl -X GET http://<ingress-gateway-ip>/productpage --user admin1:password -IHTTP/1.1 200 OKAccess
/api/v1/products-- expected result:200 OK. Expected output:curl -X GET http://<ingress-gateway-ip>/api/v1/products --user admin1:password -IHTTP/1.1 200 OK
The admin1 user has the admin role, which permits GET requests to both /productpage and /api/v1/products.
Step 5: Update the OPA policy dynamically
OPA policies update dynamically without pod restarts. The following example grants the guest1 user both the guest and admin roles.
Run the following command to edit the policy:
kubectl edit asmopapolicy bookinfo-opa -n defaultUpdate the user_roles section to assign both roles to guest1:
apiVersion: istio.alibabacloud.com/v1beta1
kind: ASMOPAPolicy
metadata:
name: bookinfo-opa
namespace: default
spec:
policy: |
package istio.authz
import input.attributes.request.http as http_request
allow {
roles_for_user[r]
required_roles[r]
}
roles_for_user[r] {
r := user_roles[user_name][_]
}
required_roles[r] {
perm := role_perms[r][_]
perm.method = http_request.method
perm.path = http_request.path
}
user_name = parsed {
[_, encoded] := split(http_request.headers.authorization, " ")
[parsed, _] := split(base64url.decode(encoded), ":")
}
user_roles = {
"guest1": ["guest", "admin"],
"admin1": ["admin"]
}
role_perms = {
"guest": [
{"method": "GET", "path": "/productpage"},
],
"admin": [
{"method": "GET", "path": "/productpage"},
{"method": "GET", "path": "/api/v1/products"},
],
}Key change: guest1 now maps to ["guest", "admin"] instead of ["guest"].
Step 6: Verify the policy update
After saving the policy, test again to confirm the change takes effect.
Access
/productpageasguest1-- expected result:200 OK. Expected output:curl -X GET http://<ingress-gateway-ip>/productpage --user guest1:password -IHTTP/1.1 200 OKAccess
/api/v1/productsasguest1-- expected result:200 OK(previously403). Expected output:curl -X GET http://<ingress-gateway-ip>/api/v1/products --user guest1:password -IHTTP/1.1 200 OK
Before the update, guest1 could not access /api/v1/products. After the update, guest1 has both guest and admin permissions, so the request succeeds. This confirms dynamic policy updates without pod restarts.
Sample scenarios
Scenario 1: Authenticate requests by JWT
OPA can validate the JWT in the Authorization header and authorize requests based on JWT claims. The request is allowed only if the JWT passes signature verification and contains the required claims.
The following policy allows GET requests to /productpage only when the JWT contains Role: guest and userGroup: visitor:
apiVersion: istio.alibabacloud.com/v1beta1
kind: ASMOPAPolicy
metadata:
name: policy-jwt
namespace: default
spec:
policy: |
package istio.authz
allow {
input.attributes.request.http.method == "GET"
input.parsed_path[0] == "productpage"
# Verify the JWT signature with the shared secret
io.jwt.verify_hs256(bearer_token, "B41BD5F462719C6D6118E673A2389")
claims.Role == "guest"
claims.userGroup == "visitor"
}
claims := payload {
[_, payload, _] := io.jwt.decode(bearer_token)
}
bearer_token := t {
v := input.attributes.request.http.headers.authorization
startswith(v, "Bearer ")
t := substring(v, count("Bearer "), -1)
}The following table describes the key fields in the policy.
| Field | Description |
|---|---|
input.attributes.request.http.method | HTTP method. Set to GET in this example. |
input.parsed_path[0] | First path segment of the request URL. Matches the target application. |
io.jwt.verify_hs256 | Verifies the JWT signature using the HS256 algorithm and the specified shared secret. |
claims.Role | Required value of the Role field in the JWT payload. Set to guest. |
claims.userGroup | Required value of the userGroup field in the JWT payload. Set to visitor. |
Encode the Role and userGroup fields into a JWT string using a tool such as jwt.io.

Test the policy:
curl --location --request GET 'http://<ingress-gateway-ip>/productpage' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZ3Vlc3QxIiwiUm9sZSI6Imd1ZXN0IiwidXNlckdyb3VwIjoidmlzaXRvciJ9.44OnUFZwOzSWzC7hyVfcle-uYk8byv7q_BBxS10AEWc'Expected output:
200A 200 status code confirms that the GET request with a valid JWT (Role: guest, userGroup: visitor) is allowed. An invalid or missing JWT returns 403.
Scenario 2: Authenticate requests by HTTP body and JWT
OPA can cross-reference JWT claims with the request body. The following policy allows a request only when the username parameter in the request body matches the Role field in the JWT, and the userGroup field is manager:
apiVersion: istio.alibabacloud.com/v1beta1
kind: ASMOPAPolicy
metadata:
name: policy-body
namespace: default
spec:
policy: |
package istio.authz
allow {
input.attributes.request.http.method == "GET"
input.parsed_path[0] == "productpage"
io.jwt.verify_hs256(bearer_token, "B41BD5F462719C6D6118E673A2389")
claims.Role == input.parsed_body.username
claims.userGroup == "manager"
}
claims := payload {
[_, payload, _] := io.jwt.decode(bearer_token)
}
bearer_token := t {
v := input.attributes.request.http.headers.authorization
startswith(v, "Bearer ")
t := substring(v, count("Bearer "), -1)
}The following table describes the key fields in the policy.
| Field | Description |
|---|---|
input.attributes.request.http.method | HTTP method. Set to GET. |
input.parsed_path[0] | First path segment. Matches the target application. |
claims.Role | Must equal input.parsed_body.username -- the username field from the request body. |
claims.userGroup | Required value. Set to manager. |
Encode the Role and userGroup fields into a JWT string using jwt.io.

Test the policy:
curl --location --request GET 'http://<ingress-gateway-ip>/productpage' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZ3Vlc3QxIiwiUm9sZSI6ImFkbWluIiwidXNlckdyb3VwIjoibWFuYWdlciJ9.pAUvTeONHF-i5Ps-EUYYXk-hnaz-j-ZgP_wXJZMBiR0' \
--header 'Content-Type: application/json' \
--header 'Cookie: session=eyJ1c2VyIjoiYWRtaW4ifQ.YRz90g.GT34_5BqlFTwGqabZk_qGZzxYQ0' \
--data-raw '{
"username":"admin",
"password":"12****"
}'Expected output:
200A 200 status code confirms that the JWT Role field (admin) matches the request body username (admin), and userGroup is manager. An invalid or missing JWT returns 403.
Scenario 3: Authenticate requests with a user whitelist
Build on Scenario 2 by adding a whitelist check: the username field in the JWT must match an entry in the bookinfo_managers list.
The following policy adds the condition claims.username == bookinfo_managers[_].name, which restricts access to a predefined set of users (user1, user2, user3):
apiVersion: istio.alibabacloud.com/v1beta1
kind: ASMOPAPolicy
metadata:
name: policy-range
namespace: default
spec:
policy: |
package istio.authz
bookinfo_managers = [{"name": "user1"}, {"name": "user2"}, {"name": "user3"}]
allow {
input.attributes.request.http.method == "GET"
input.parsed_path[0] == "productpage"
io.jwt.verify_hs256(bearer_token, "B41BD5F462719C6D6118E673A2389")
claims.Role == input.parsed_body.username
claims.userGroup == "manager"
claims.username == bookinfo_managers[_].name
}
claims := payload {
[_, payload, _] := io.jwt.decode(bearer_token)
}
bearer_token := t {
v := input.attributes.request.http.headers.authorization
startswith(v, "Bearer ")
t := substring(v, count("Bearer "), -1)
}The following table describes the key fields in the policy.
| Field | Description |
|---|---|
input.attributes.request.http.method | HTTP method. Set to GET. |
input.parsed_path[0] | First path segment. Matches the target application. |
claims.Role | Must equal input.parsed_body.username. |
claims.userGroup | Required value. Set to manager. |
claims.username | Must match an entry in bookinfo_managers. Only user1, user2, and user3 are allowed. |
Encode the request information into a JWT string using jwt.io.

Test the policy:
curl --location --request GET 'http://<ingress-gateway-ip>/productpage' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwiUm9sZSI6ImFkbWluIiwidXNlckdyb3VwIjoibWFuYWdlciJ9.2X0Fmb96jBexLcVm_55t8ZY6XveSxUAsQ1j3ar5dI_g' \
--header 'Content-Type: application/json' \
--header 'Cookie: session=eyJ1c2VyIjoiYWRtaW4ifQ.YRz90g.GT34_5BqlFTwGqabZk_qGZzxYQ0' \
--data-raw '{
"username":"admin",
"password":"12****"
}'Expected output:
200A 200 status code confirms that all conditions are met: the JWT Role matches the body username, userGroup is manager, and the JWT username (user1) is in the bookinfo_managers list. An invalid or missing JWT returns 403.
FAQ
How do I check whether a pod uses OPA policies?
OPA runs as a sidecar in the same pod as your application. Connect to the pod and run:
curl 127.0.0.1:15081/v1/policiesThis returns the Rego policies loaded by OPA in that pod.
How do I validate Rego policies before deploying them?
Use the OPA Playground to test and debug Rego policies in a browser before applying them to your ASM instance.
See also
Use OPA to implement fine-grained access control in ASM -- define OPA policies with ConfigMaps