All Products
Search
Document Center

Alibaba Cloud Service Mesh:Implement fine-grained access control with OPA policies

Last Updated:Mar 11, 2026

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:

  1. Envoy receives a request destined for your microservice.

  2. Envoy queries the local OPA sidecar for an authorization decision.

  3. OPA evaluates the request against Rego policies and returns an allow or deny verdict.

  4. 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 architecture

OPA occupies ports 15081 and 9191 within the pod.

Prerequisites

Before you begin, make sure that you have:

Step 1: Enable the OPA plug-in

  1. Log on to the ASM console. In the left-side navigation pane, choose Service Mesh > Mesh Management.

  2. On the Mesh Management page, click the name of the ASM instance. In the left-side navigation pane, choose Mesh Security Center > OPA Policy.

  3. 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:

  • guest1 has the guest role -- can only GET /productpage.

  • admin1 has the admin role -- can GET both /productpage and /api/v1/products.

Choose one of the following methods to create the policy.

Method 1: Use the ASM console

  1. Log on to the ASM console. In the left-side navigation pane, choose Service Mesh > Mesh Management.

  2. On the Mesh Management page, click the name of the ASM instance. In the left-side navigation pane, choose Mesh Security Center > OPA Policy.

  3. 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.

    Show the Rego rules

       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"],
           "admin1": ["admin"]
       }
       role_perms = {
           "guest": [
               {"method": "GET",  "path": "/productpage"},
           ],
           "admin": [
               {"method": "GET",  "path": "/productpage"},
               {"method": "GET",  "path": "/api/v1/products"},
           ],
       }

Method 2: Use kubectl

  1. Create a file named opa.yaml with the following content: The following table describes the key fields in the ASMOPAPolicy resource.

    FieldDescription
    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, guest1 is assigned the guest role and admin1 is assigned the admin role.
    role_permsMaps roles to allowed HTTP method and path combinations.

    Show the opa.yaml file

       apiVersion: istio.alibabacloud.com/v1beta1
       kind: ASMOPAPolicy
       metadata:
         name: bookinfo-opa
         namespace: default
       spec:
         workloadSelector:
            labels:
              version: v1
         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"],
               "admin1": ["admin"]
           }
           role_perms = {
               "guest": [
                   {"method": "GET",  "path": "/productpage"},
               ],
               "admin": [
                   {"method": "GET",  "path": "/productpage"},
                   {"method": "GET",  "path": "/api/v1/products"},
               ],
           }
  2. 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
Important
  • Only one OPA policy per pod should contain the default allow field. Multiple default allow declarations across policies targeting the same pod prevent dynamic policy updates.

  • Use labels in workloadSelector to scope policies precisely. An invalid Rego policy applied broadly can block all traffic to affected services.

  • By default, the default allow field is set to false. 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.

  1. Deploy the Bookinfo application. For more information, see Deploy an application in an ASM instance.

  2. 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.

  3. Verify that OPA is injected into each Bookinfo pod: On the Container tab, confirm that both istio-proxy (the sidecar proxy) and opa-istio (the OPA sidecar) are present. Repeat this check for each Bookinfo pod.

    1. Log on to the ACK console. In the left-side navigation pane, click Clusters.

    2. On the Clusters page, click the cluster name. In the left-side navigation pane, choose Workloads > Pods.

    3. On the Pods page, select default from the Namespace drop-down list and click a pod name.

    OPA injection verification

Step 4: Verify that the OPA policy works

Test access control by sending requests as different users.

Test the guest1 user (guest role)

  1. Access /productpage -- expected result: 200 OK (allowed). Expected output:

       curl -X GET http://<ingress-gateway-ip>/productpage --user guest1:password -I
       HTTP/1.1 200 OK
  2. Access /api/v1/products -- expected result: 403 Forbidden (denied). Expected output:

       curl -X GET http://<ingress-gateway-ip>/api/v1/products --user guest1:password -I
       HTTP/1.1 403 Forbidden

The guest1 user has the guest role, which only permits GET requests to /productpage.

Test the admin1 user (admin role)

  1. Access /productpage -- expected result: 200 OK. Expected output:

       curl -X GET http://<ingress-gateway-ip>/productpage --user admin1:password -I
       HTTP/1.1 200 OK
  2. Access /api/v1/products -- expected result: 200 OK. Expected output:

       curl -X GET http://<ingress-gateway-ip>/api/v1/products --user admin1:password -I
       HTTP/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 default

Update 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.

  1. Access /productpage as guest1 -- expected result: 200 OK. Expected output:

       curl -X GET http://<ingress-gateway-ip>/productpage --user guest1:password -I
       HTTP/1.1 200 OK
  2. Access /api/v1/products as guest1 -- expected result: 200 OK (previously 403). Expected output:

       curl -X GET http://<ingress-gateway-ip>/api/v1/products --user guest1:password -I
       HTTP/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.

FieldDescription
input.attributes.request.http.methodHTTP 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_hs256Verifies the JWT signature using the HS256 algorithm and the specified shared secret.
claims.RoleRequired value of the Role field in the JWT payload. Set to guest.
claims.userGroupRequired 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.

JWT encoding

Test the policy:

curl --location --request GET 'http://<ingress-gateway-ip>/productpage' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZ3Vlc3QxIiwiUm9sZSI6Imd1ZXN0IiwidXNlckdyb3VwIjoidmlzaXRvciJ9.44OnUFZwOzSWzC7hyVfcle-uYk8byv7q_BBxS10AEWc'

Expected output:

200

A 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.

FieldDescription
input.attributes.request.http.methodHTTP method. Set to GET.
input.parsed_path[0]First path segment. Matches the target application.
claims.RoleMust equal input.parsed_body.username -- the username field from the request body.
claims.userGroupRequired value. Set to manager.

Encode the Role and userGroup fields into a JWT string using jwt.io.

JWT encoding for body validation

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:

200

A 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.

FieldDescription
input.attributes.request.http.methodHTTP method. Set to GET.
input.parsed_path[0]First path segment. Matches the target application.
claims.RoleMust equal input.parsed_body.username.
claims.userGroupRequired value. Set to manager.
claims.usernameMust match an entry in bookinfo_managers. Only user1, user2, and user3 are allowed.

Encode the request information into a JWT string using jwt.io.

JWT encoding for context validation

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:

200

A 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/policies

This 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