Use OPA policies for fine-grained access control

更新时间:
复制 MD 格式

Service Mesh (ASM) integrates with the Open Policy Agent (OPA) plugin, enabling fine-grained access control for your applications through OPA policies. ASM allows you to define these policies on the control plane and push them to data plane clusters. This topic describes how to use this feature to control access based on criteria such as the request URL and tokens in the request header.

Prerequisites

Background

As an incubating project hosted by the CNCF, Open Policy Agent (OPA) is a policy engine that can be used to implement fine-grained access control for your applications. As a general-purpose policy engine, OPA can be deployed as a standalone service alongside microservices. To protect applications, each request to a microservice must be authorized before it can be processed. To check for authorization, the microservice makes an API call to OPA to determine whether the request is authorized.OPA

Step 1: Enable the OPA plugin and scope control

  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 the Open Policy Agent (OPA) plug-in and Enable the feature of controlling the OPA injection scope, click Enable OPA, and then click OK in the Note dialog box.

Step 2: Create an OPA policy

You create an ASMOPAPolicy resource on the ASM control plane. ASM then pushes this resource to the data plane cluster, where the OPA engine in each relevant pod uses it to enforce fine-grained access control.

Option 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 the default namespace, set the Name of the OPA policy to bookinfo-opa, and click Add Matching Label. Set Name to version and Value to v1. Copy the following Rego rules into the text box and then click Create.

    View 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"},
        ],
    }

Option 2: Use kubectl

  1. Create a file named opa.yaml with the following content.

    View opa.yaml

    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 an OPA policy for a pod, you can include only one default allow field. If multiple OPA policies apply to a single pod and each policy defines a default allow field, the multiple default allow fields cause dynamic updates to fail.

    • Scope policies by using labels. An invalid Rego rule can make a service inaccessible.

    • The OPA execution engine and the application container run in the same pod and occupy ports 15081 and 9191.

    • OPA policies default to allow = false. Do not redefine default allow to avoid conflicts.

    Parameter

    Description

    spec

    The policy rules, written in the Rego language. For more information about Rego syntax, see Rego.

    workloadSelector

    Specifies the policy's scope within the namespace. If omitted, the policy applies to all pods in the namespace. If specified, it applies only to pods with matching labels.

    user_roles

    Grants role permissions to users. In this example, the user guest1 is granted the guest role, and the user admin1 is granted the admin role.

    role_perms

    Defines the permissions of each role. In this example, the guest role can access /productpage, and the admin role can access /productpage and /api/v1/products.

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

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

    kubectl apply -f opa.yaml

Step 3: Inject the OPA proxy

Deploy the Bookinfo sample application to the ASM instance and confirm that ASM injected the OPA proxy into each pod.

  1. Deploy the Bookinfo sample application to the ASM instance. For more information, see Deploy an application in a cluster associated with 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 based on versions.

  3. Check whether the OPA proxy is injected into each application pod.

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

    2. On the Clusters page, click the name of your cluster. In the left navigation pane, click Workloads > Pods.

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

      On the Containers tab, verify that the pod contains both the istio-proxy (the sidecar proxy) and opa-istio (the OPA proxy) containers.注入OPA代理

Step 4: Verify the OPA access control policy

  1. Run the following command to access /productpage.

    curl -X GET http://<INGRESS_GATEWAY_IP>/productpage --user guest1:password -I

    Expected output:

    HTTP/1.1 200 OK
  2. Run the following command to access /api/v1/products.

    curl -X GET http://<INGRESS_GATEWAY_IP>/api/v1/products --user guest1:password -I

    Expected output:

    HTTP/1.1 403 Forbidden

    The results confirm that the user guest1, with the guest role, can access /productpage but is denied access to /api/v1/products.

  3. Run the following command to access /productpage.

    curl -X GET http://<INGRESS_GATEWAY_IP>/productpage --user admin1:password -I

    Expected output:

    HTTP/1.1 200 OK
  4. Run the following command to access /api/v1/products.

    curl -X GET http://<INGRESS_GATEWAY_IP>/api/v1/products --user admin1:password -I

    Expected output:

    HTTP/1.1 200 OK

    The results show that admin1 is assigned the admin role. admin1 can access both /productpage and /api/v1/products. This indicates that the OPA access control policy is working as expected.

Step 5: Dynamically update an OPA policy

Run the following command to modify the OPA policy.

kubectl edit asmopapolicy bookinfo-opa -n default 

In the output, edit the OPA policy to grant guest1 both the guest and admin roles.

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: Grants role permissions to users. In this example, the user guest1 is granted both the guest and admin roles, and the user admin1 is granted the admin role.

  • role_perms: Defines the permissions of each role. In this example, the guest role can access /productpage, and the admin role can access both /productpage and /api/v1/products.

Step 6: Verify the dynamic update

  1. Run the following command to access /productpage.

    curl -X GET http://<INGRESS_GATEWAY_IP>/productpage --user guest1:password -I

    Expected output:

    HTTP/1.1 200 OK
  2. Run the following command to access /api/v1/products.

    curl -X GET http://<INGRESS_GATEWAY_IP>/api/v1/products --user guest1:password -I

    Expected output:

    HTTP/1.1 200 OK

    Previously, guest1 could only access /productpage. After the update, guest1 can also access /api/v1/products, which confirms the dynamic policy update was successful.

Scenarios

Scenario 1: Authorize requests based on JWT claims

This scenario shows how to authorize requests based on claims in a JSON Web Token (JWT). The policy verifies the JWT in the request header, allowing only trusted requests to access the application.

The following ASMOPAPolicy allows access to the Productpage application only when the request uses the GET method, the Role claim in the JWT is guest, and the userGroup claim is 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 GET.

  • input.parsed_path[0]: The application being accessed.

  • claims.Role: Restricts the JWT. In this example, Role must be guest.

  • claims.userGroup: Restricts the JWT. In this example, userGroup must be visitor.

Use a JWT tool to encode request information such as Role and userGroup into a JWT string.请求授权

Run the following command to access the Productpage application.

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

Expected output:

HTTP/1.1 200 OK

A 200 response indicates that a GET request to the Productpage application is successful. This occurs when the request includes a JWT Token where the Role is guest and the userGroup is visitor. If an incorrect JWT Token is used, or if the request does not contain a JWT Token, a 403 response is returned, indicating that access to the Productpage application failed.

Scenario 2: Restrict the HTTP request body

This scenario demonstrates how to authorize requests by validating fields in the HTTP request body against claims in the JWT.

The following ASMOPAPolicy allows access to the Productpage application only when the request uses the GET method, the username in the request body matches the Role claim in the JWT, and the userGroup claim 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)
    }
  • input.attributes.request.http.method: The request method. In this example, the value is GET.

  • input.parsed_path[0]: The application being accessed.

  • claims.Role: Restricts the JWT. In this example, the value is set to input.parsed_body.username, which requires that the username in the request body must match the Role claim in the JWT.

  • claims.userGroup: Restricts the JWT. In this example, userGroup must be manager.

Use a JWT tool to encode request information such as Role and userGroup into a JWT string.body

Run the following command to access the Productpage application.

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:

HTTP/1.1 200 OK

A 200 OK response indicates that a GET request can access the Productpage application if the username in the request body matches the Role claim in the JWT and the userGroup claim in the JWT is manager. Invalid or missing JWTs will result in a 403 Forbidden response.

Scenario 3: Restrict more contextual information

Based on Scenario 2, you can restrict more contextual information. For example, you can require that the username claim in the JWT must be within the bookinfo_managers list.

The following ASMOPAPolicy allows access to the Productpage application only when the request uses the GET method, the username in the request body matches the Role claim in the JWT, the username claim in the JWT is within the bookinfo_managers list, and the userGroup claim is 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 GET.

  • input.parsed_path[0]: The application being accessed.

  • claims.Role: Restricts the JWT. In this example, the value is set to input.parsed_body.username, which requires that the username in the request body must match the Role claim in the JWT.

  • claims.userGroup: Restricts the JWT. In this example, userGroup must be manager.

  • claims.username: Restricts the JWT. In this example, the value is set to bookinfo_managers[_].name, which requires that the username claim in the JWT must be present in the bookinfo_managers list.

Use a JWT tool to encode the request information into a JWT string.上下文

Run the following command to access the Productpage application.

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:

HTTP/1.1 200 OK

A 200 OK response indicates that a GET request can access the Productpage application if the username in the request body matches the Role claim in the JWT, the username claim in the JWT is present in the bookinfo_managers list, and the userGroup claim in the JWT is manager. Invalid or missing JWTs will result in a 403 Forbidden response.

FAQ

How to check OPA policies on a pod?

The OPA engine runs as a sidecar in the application pod. To see all OPA policies that apply to a pod, log on to it and run the following command:

curl 127.0.0.1:15081/v1/policies

How do I test Rego syntax?

OPA provides an online testing tool for validating Rego policies.

References

If you previously used ConfigMaps to configure OPA policies, see the following topics: