All Products
Search
Document Center

Alibaba Cloud Service Mesh:Use an OPA policy to implement fine-grained access control

Last Updated:Mar 04, 2024

Service Mesh (ASM) integrates with the Open Policy Agent (OPA) plug-in. You can use OPA to define access control policies to implement fine-grained access control on your applications. ASM allows you to define OPA policies on the control plane and push the policies to clusters on the data plane. This topic describes how to define OPA policies in ASM to implement fine-grained access control on your applications. For example, you can define OPA policies to allow or block requests based on request URLs or tokens in request headers. This topic also provides sample scenarios in which OPA policies are used to implement access control.

Prerequisites

  • An ASM instance whose version is 1.9.7 or later is created. For more information, see Create an ASM instance.

  • The cluster is added to the ASM instance. For more information, see Add a cluster to an ASM instance.

  • Automatic sidecar proxy injection is enabled for the default namespace. For more information, see the "Enable automatic sidecar proxy injection" section of the Manage global namespaces topic.

Background information

OPA is a Graduated project of Cloud Native Computing Foundation (CNCF). As a policy engine, OPA can be used to implement fine-grained access control on your applications. You can deploy OPA as a standalone service along with microservices. To protect an application, make sure that each request to a microservice of the application is authorized before the request is processed. To check the authorization, the microservice makes an API call to OPA to check whether the request is authorized.OPA

Step 1: Enable the OPA plug-in and the feature of controlling the injection scope of OPA

  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

After you create an OPA policy on the control plane of the ASM instance, the OPA policy is pushed to clusters on the data plane. Then, OPA in the pods of the clusters uses the OPA policy to implement fine-grained access control. You can create an OPA policy by using the following methods:

Method 1: Create an OPA policy in 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 content to the Rego Rule 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: Create an OPA policy by using kubectl

  1. Create an opa.yaml file that contains the following content:

    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"},
            ],
        }
    Note
    • When you define OPA policies for a pod, make sure that only one OPA policy contains the default allow field. If multiple OPA policies apply to a pod and the default allow field is defined in each OPA policy, the OPA policies fail to be dynamically updated due to multiple default allow fields.

    • We recommend that you use labels to specify the effective scope of OPA policies when you define the OPA policies. Invalid Rego policies make services inaccessible.

    • OPA resides in the same pod as business containers and occupies ports 15081 and 9191.

    • By default, the default allow field in an OPA policy is set to false. Do not set the default allow field again. Otherwise, a conflict occurs. The following table describes some parameters of the opa.yaml file.

    Parameter

    Description

    spec

    The policy content that is written in Rego. For more information about Rego syntax, see Policy Language.

    workloadSelector

    The effective scope of the OPA policy in the specified namespace. By default, the OPA policy applies to all pods in the namespace. After you set this parameter, the OPA policy applies only to pods with specified labels.

    user_roles

    The roles assigned to users. In this example, the guest role is assigned to the guest1 user, and the admin role is assigned to the admin1 user.

    role_perms

    The permissions of each role. In this example, the guest role is granted the permissions to access an application by using a URL that contains /productpage, and the admin role is granted the permissions to access an application by using a URL that contains /productpage or /api/v1/products.

  2. Run the following command to create the OPA policy.

    For more information about how to use kubectl to connect to the ASM instance, see Use kubectl on the control plane to access Istio resources.

    kubectl apply -f opa.yaml

Step 3: Inject OPA

Deploy the sample application Bookinfo in the ASM instance and check whether OPA is injected into each pod of the Bookinfo application.

  1. Deploy the Bookinfo application in the ASM instance. 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. Check whether OPA is injected into the pod of each application in the Bookinfo application.

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

    2. On the Clusters page, click the name of the cluster that you want to manage and choose Workloads > Pods in the left-side navigation pane.

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

      On the Container tab, you can find that a sidecar proxy named istio-proxy and OPA named opa-istio are injected into each container. Check the containers of each application in turn to ensure that the sidecar proxy and OPA are injected into each container.注入OPA代理

Step 4: Verify that the OPA policy implements access control as expected

  1. Run the following command to access the application by using the URL that contains /productpage:

    curl -X GET http://<IP address of the ingress gateway>/productpage --user guest1:password -I

    Expected output:

    HTTP/1.1 200 OK
  2. Run the following command to access the application by using the URL that contains /api/v1/products:

    curl -X GET http://<IP address of the ingress gateway>/api/v1/products --user guest1:password -I

    Expected output:

    HTTP/1.1 403 Forbidden

    The expected results indicate that the guest role is assigned to the guest1 user and the guest1 user has the permissions to access the application by using a URL that contains /productpage, but not by using a URL that contains /api/v1/products.

  3. Run the following command to access the application by using the URL that contains /productpage:

    curl -X GET http://{{IP address of the ingress gateway}}/productpage --user admin1:password -I

    Expected output:

    HTTP/1.1 200 OK
  4. Run the following command to access the application by using the URL that contains /api/v1/products:

    curl -X GET http://<IP address of the ingress gateway>/api/v1/products --user admin1:password -I

    Expected output:

    HTTP/1.1 200 OK

    The expected results indicate that the admin role is assigned to the admin1 user and the admin1 user has the permissions to access the application by using a URL that contains /productpage or /api/v1/products. The preceding results indicate that the defined OPA policy implements access control as expected.

Step 5: Dynamically update the OPA policy

Run the following command to go to the editing UI of the OPA policy:

kubectl edit asmopapolicy bookinfo-opa -n default 

In the command output, edit the OPA policy to assign both the guest and admin roles to the guest1 user.

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"},
        ],
    }
  • user_roles: the roles assigned to users. In this example, the guest and admin roles are assigned to the guest1 user, and the admin role is assigned to the admin1 user.

  • role_perms: the permissions of each role. In this example, the guest role is granted the permissions to access an application by using a URL that contains /productpage, and the admin role is granted the permissions to access an application by using a URL that contains /productpage or /api/v1/products.

Step 6: Verify that the OPA policy is dynamically updated

  1. Run the following command to access the application by using the URL that contains /productpage:

    curl -X GET http://<IP address of the ingress gateway>/productpage --user guest1:password -I

    Expected output:

    HTTP/1.1 200 OK
  2. Run the following command to access the application by using the URL that contains /api/v1/products:

    curl -X GET http://<IP address of the ingress gateway>/api/v1/products --user guest1:password -I

    Expected output:

    HTTP/1.1 200 OK

    Before the OPA policy is updated, the guest1 user can access the application by using a URL that contains /productpage, but not /api/v1/products. After the OPA policy is updated, the guest1 user can access the application by using a URL that contains /productpage or /api/v1/products. The results indicate that the OPA policy is dynamically updated.

Sample scenarios

Scenario 1: Authenticate a request by checking the JWT

When an application receives a request, OPA checks the JSON Web Token (JWT) in the headers of the request. The request to access the application is allowed only if the JWT meets specified requirements.

The following OPA policy defines that a request to access the Productpage application is allowed only if the request uses the GET method and the request contains a JWT in which the Role field is set to guest and the userGroup field is set to 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"
      # set certificate 'B41BD5F462719C6D6118E673A2389'
      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)
    }
  • input.attributes.request.http.method: the request method. In this example, the value is set to GET.

  • input.parsed_path[0]: the application that is to be accessed.

  • claims.Role: the expected value of the Role field in the JWT. In this example, the value is set to guest.

  • claims.userGroup: the expected value of the userGroup field in the JWT. In this example, the value is set to visitor.

You can use a JWT tool to encode request information such as the Role and userGroup fields into a JWT string.请求授权

Run the following command to access the Productpage application:

curl --location --request GET 'http://{IP address of the ingress gateway}/productpage' \
--header 'Authorization: Bearer 
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZ3Vlc3QxIiwiUm9sZSI6Imd1ZXN0IiwidXNlckdyb3VwIjoidmlzaXRvciJ9.44OnUFZwOzSWzC7hyVfcle-uYk8byv7q_BBxS10AEWc'

Expected output:

200

The status code 200 is returned, which indicates that the Productpage application can be accessed by using a GET request that contains a JWT in which the Role field is set to guest and the userGroup field is set to visitor. If you use an invalid JWT or the request contains no JWT, the error code 403 is returned. This indicates that the request to access the Productpage application is rejected.

Scenario 2: Authenticate a request by checking the HTTP request body and JWT

You can use an OPA policy to allow an HTTP request only if the value of the username parameter in the request body is the same as the value of the Role field in the JWT of the request.

The following OPA policy defines that a request to access the Productpage application is allowed only if the request uses the GET method, the value of the username parameter in the request body is the same as the value of the Role field in the JWT of the request, and the userGroup field in the JWT is set to 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)
    }
  • input.attributes.request.http.method: the request method. In this example, the value is set to GET.

  • input.parsed_path[0]: the application that is to be accessed.

  • claims.Role: the expected value of the Role field in the JWT. In this example, the value is set to input.parsed_body.username. This indicates that the value of the username parameter in the request body must be the same as the value of the Role field in the JWT of the request.

  • claims.userGroup: the expected value of the userGroup field in the JWT. In this example, the value is set to manager.

You can use a JWT tool to encode request information such as the Role and userGroup fields into a JWT string.body

Run the following command to access the Productpage application:

curl --location --request GET 'http://{IP address of the ingress gateway}/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

The status code 200 is returned, which indicates that the Productpage application can be accessed by using a GET request that contains a JWT in which the value of the Role field is the same as the value of the username parameter in the request body and the userGroup field is set to manager. If you use an invalid JWT or the request contains no JWT, the error code 403 is returned. This indicates that the request to access the Productpage application is rejected.

Scenario 3: Authenticate a request by checking more context information

You can use an OPA policy to check more context information in addition to the information that is checked in Scenario 2. In this example, the value of the username field in the JWT must fall into the value range specified by the bookinfo_managers field.

The following OPA policy defines that a request to access the Productpage application is allowed only if the request uses the GET method, the value of the username parameter in the request body is the same as the value of the Role field in the JWT of the request, the value of the username field in the JWT falls into the value range specified by the bookinfo_managers field, and the userGroup field in the JWT is set to manager:

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)
    }
  • input.attributes.request.http.method: the request method. In this example, the value is set to GET.

  • input.parsed_path[0]: the application that is to be accessed.

  • claims.Role: the expected value of the Role field in the JWT. In this example, the value is set to input.parsed_body.username. This indicates that the value of the username parameter in the request body must be the same as the value of the Role field in the JWT of the request.

  • claims.userGroup: the expected value of the userGroup field in the JWT. In this example, the value is set to manager.

  • claims.username: the expected value of the username field in the JWT. In this example, the value is set to bookinfo_managers[_].name. This indicates that the value of the username field in the JWT must fall into the value range specified by the bookinfo_managers field.

You can use a JWT tool to encode request information into a JWT string.上下文

Run the following command to access the Productpage application:

curl --location --request GET 'http://{IP address of the ingress gateway}/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

The status code 200 is returned, which indicates that the Productpage application can be accessed by using a GET request that contains a JWT in which the value of the Role field is the same as the value of the username parameter in the request body, the value of the username field in the JWT falls into the value range specified by the bookinfo_managers field, and the userGroup field is set to manager. If you use an invalid JWT or the request contains no JWT, the error code 403 is returned. This indicates that the request to access the Productpage application is rejected.

FAQ

How do I check whether a pod uses OPA policies?

OPA is deployed in sidecar mode in the same pod as business containers. To check whether a pod uses OPA policies, connect to the pod and run the following command:

curl 127.0.0.1:15081/v1/policies

How do I check the policies that are written in Rego?

The official OPA website provides an online tool that you can use to check the policies that are written in Rego.

References

If you want to use ConfigMaps to define OPA policies, read the following topics: