服务网格ASM集成了开放策略代理(OPA)插件,通过OPA定义访问控制策略,可以使您的应用实现细粒度的访问控制。ASM支持在控制平面定义OPA策略,然后推送到数据平面集群中。本文介绍如何在ASM定义OPA策略实现访问应用时的权限细粒度控制,包括对请求URL、请求头信息中的Token等访问限制。

前提条件

背景信息

作为由CNCF托管的一个孵化项目,开放策略代理(OPA)是一个策略引擎,可用于为您的应用程序实现细粒度的访问控制。OPA作为通用策略引擎,可以与微服务一起部署为独立服务。为了保护应用程序,必须先授权对微服务的每个请求,然后才能对其进行处理。为了检查授权,微服务对OPA进行API调用,以确定请求是否被授权。OPA

操作步骤

步骤一:启用OPA插件

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 在网格详情页面左侧导航栏选择网格实例 > 基本信息,然后在右侧页面单击功能设置
  5. 功能设置更新页面中选中启用开放策略代理OPA插件,单击确定
    基本信息页面可以看到OPA插件的状态变为开启

步骤二:创建OPA策略

在ASM控制平面创建ASMOPAPolicy资源,该资源会被推送到数据平面集群,然后Pod中OPA引擎使用该资源实现细粒度访问控制。

使用ASM控制台创建OPA策略

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 在网格详情页面左侧导航栏选择零信任安全 > OPA策略,然后在右侧页面单击创建
  5. 设置命名空间为default,设置OPA策略的名称,单击新增匹配标签,设置名称为version,值为v1,将以下内容复制到文本框中,然后单击创建
    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"},
        ],
    }
    
    
    
                            

使用kubectl命令行创建OPA策略

  1. 通过kubectl连接ASM实例
  2. 使用以下内容创建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"},
            ],
        }
     
    说明
    • OPA定义一个Pod的OPA策略时,只允许有一条default allow字段。如果多个OPA策略生效于一个Pod,且每个策略中都定义了default allow字段,则多条default allow字段会导致动态更新失败。
    • 设定OPA策略时,建议通过标签控制策略生效的范围,不恰当的Rego规则会导致无法访问服务。
    • OPA执行引擎和业务容器在一个Pod中,需要占用15081和9191端口。
    • OPA Policy中已经默认设置default allow = false ,不可以再重复设置default allow,否则会导致冲突。
    • spec:使用Rego语法表述的具体规则。关于Rego语法的更多信息,请参见Rego
    • workloadSelector:设定该OPA策略在此命名空间生效范围,如果不设定,默认在全部Pod中生效,设定后只生效于符合标签条件的Pod。
    • user_roles:为用户授予角色权限。本例设置guest1拥有guest角色权限,admin1拥有admin角色权限。
    • role_perms:设置角色拥有的权限。本文设置guest角色可以通过/productpage访问应用,admin角色可以通过/productpage/api/v1/products访问应用。
  3. 执行以下命令,创建OPA策略。
    kubectl apply -f opa.yaml

步骤三:注入OPA代理

部署示例应用Bookinfo到ASM实例,确认每个Pod都注入了OPA代理。

  1. 部署示例应用Bookinfo到ASM实例,请参见部署应用到ASM实例
  2. 定义相应的Istio虚拟服务和入口网关,请参见使用Istio资源实现版本流量路由
  3. 检查每个应用是否都注入OPA代理。
    1. 登录容器服务管理控制台
    2. 在控制台左侧导航栏中,单击集群
    3. 集群列表页面中,单击目标集群名称或者目标集群右侧操作列下的详情
    4. 在集群管理页左侧导航栏中,选择工作负载 > 容器组
    5. 容器组页面,从命名空间下拉列表中选择default,单击目标应用容器组的名称。
      容器页签下可以看到容器被注入了Sidecar代理(istio-proxy)和OPA代理(opa-istio)。依次检查每个应用的容器,确保每个容器都被注入了Sidecar代理和OPA代理。注入OPA代理

步骤四:验证使用OPA定义访问控制策略是否成功

  • 执行以下命令,可以看到guest1被授予guest角色,并且可以使用带有/productpage的URL访问应用,但不能使用带有/api/v1/products的URL访问应用。
    • 使用带有/productpage的URL访问应用。
      curl -X GET http://<入口网关服务的IP地址>/productpage --user guest1:password -I

      预期输出:

      HTTP/1.1 200 OK
    • 使用带有/api/v1/products的URL访问应用。
      curl -X GET http://<入口网关服务的IP地址>/api/v1/products --user guest1:password -I

      预期输出:

      HTTP/1.1 403 Forbidden
  • 执行以下命令,可以看到admin1被授予admin角色,并且可以使用带有/productpage和/api/v1/products的URL访问应用。
    • 使用带有/productpage的URL访问应用。
      curl -X GET http://{{入口网关服务的IP地址}}/productpage --user admin1:password -I

      预期输出:

      HTTP/1.1 200 OK
    • 使用带有/api/v1/products的URL访问应用。
      curl -X GET http://<入口网关服务的IP地址>/api/v1/products --user admin1:password -I

      预期输出:

      HTTP/1.1 200 OK

    根据以上结果,可以看到OPA定义访问控制策略是成功的。

步骤五:动态更新OPA策略

执行以下命令,修改OPA策略。

kubectl edit asmopapolicy bookinfo-opa -n default 

在返回结果中编辑OPA策略,使guest1同时用于guest和admin权限。

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:为用户授予角色权限。本例设置guest1同时拥有guest和admin角色权限,admin1拥有admin角色权限。
  • role_perms:设置角色拥有的权限。本例设置guest角色可以通过/productpage访问应用,admin角色可以通过/productpage/api/v1/products访问应用。

步骤六:验证动态更新OPA策略是否成功

执行以下命令,可以看到guest1被授予了admin角色,此时可以使用/productpage和/api/v1/products访问应用。

  • 使用带有/productpage的URL访问应用。
    curl -X GET http://<入口网关服务的IP地址>/productpage --user guest1:password -I

    预期输出:

    HTTP/1.1 200 OK
  • 使用带有/api/v1/products的URL访问应用。
    curl -X GET http://<入口网关服务的IP地址>/api/v1/products --user guest1:password -I

    预期输出:

    HTTP/1.1 200 OK

在没有更新OPA策略之前,guest1只能使用带有/productpage的URL访问应用,但不能使用带有/api/v1/products的URL访问应用。更新OPA策略之后,guest1可以使用带有/productpage和/api/v1/products的URL访问应用。说明动态更新OPA策略成功。

场景示例

场景一:JWT请求授权

在接收用户请求时,认证请求头信息中的JWT Token是否可信,只有符合要求的请求才能访问到应用。

以下ASMOPAPolicy定义了只有通过get请求的方式,且JWT Token的RoleguestuserGroupvisitor的请求才能访问Productpage应用。

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:请求方法,本文设置为GET
  • input.parsed_path[0]:访问的应用。
  • claims.Role:对JWT Token进行限制,本文设置Roleguest
  • claims.userGroup:对JWT Token进行限制,本文设置userGroupvisitor
使用JWT工具将RoleuserGroup等请求信息编码成JWT字符串。请求授权
执行以下命令,访问Productpage应用。
curl --location --request GET 'http://{入口网关服务的IP地址服务IP}/productpage' \
--header 'Authorization: Bearer 
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZ3Vlc3QxIiwiUm9sZSI6Imd1ZXN0IiwidXNlckdyb3VwIjoidmlzaXRvciJ9.44OnUFZwOzSWzC7hyVfcle-uYk8byv7q_BBxS10AEWc'

预期输出:

200

返回200,说明通过get请求的方式,且JWT Token的RoleguestuserGroupvisitor的请求访问Productpage应用成功。如果您使用错误的JWT Token,或者请求中不包含JWT Token,将会返回403,说明访问Productpage应用失败。

场景二:对HTTP请求的请求体body进行限制

对HTTP请求的请求体body进行限制,只有请求体body与JWT中的Role相同的请求才能访问应用。

以下ASMOPAPolicy定义了只有通过get请求的方式,且请求体body的username与JWT中的Role相同,并且userGroupmanager的请求才能访问Productpage应用。

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:请求方法,本文设置为GET
  • input.parsed_path[0]:访问的应用。
  • claims.Role:对JWT Token进行限制,本文设置input.parsed_body.username,限制请求体body的username与JWT中的Role必须相同。
  • claims.userGroup:对JWT Token进行限制,本文设置userGroupmanager
使用JWT工具将RoleuserGroup等请求信息编码成JWT字符串。body
执行以下命令,访问Productpage应用。
curl --location --request GET 'http://{入口网关服务的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****"
                

预期输出:

200

返回200,说明通过get请求的方式,请求体body的username与JWT中的Role相同,并且JWT Token中的userGroupmanager的请求访问Productpage应用成功。如果您使用错误的JWT Token,或者请求中不包含JWT Token,将会返回403,说明访问Productpage应用失败。

场景三:限制更多上下文信息

在场景二的基础上限制更多上下文信息,限制JWT中的username信息必须包含在bookinfo_managers范围内。

以下ASMOPAPolicy定义了只有通过get请求的方式,且请求体body的username与JWT中的Role相同,JWT中的username信息必须包含在bookinfo_managers范围内,userGroupmanager的请求才能访问Productpage。

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:请求方法,本文设置为GET
  • input.parsed_path[0]:访问的应用。
  • claims.Role:对JWT Token进行限制,本文设置input.parsed_body.username,限制请求体body的username与JWT中的Role必须相同。
  • claims.userGroup:对JWT Token进行限制,本文设置userGroupmanager
  • claims.username:对JWT Token进行限制,本文设置bookinfo_managers[_].name,限制JWT中的username信息必须包含在bookinfo_managers范围内。
使用JWT工具将请求信息编码成JWT字符串。上下文
执行以下命令,访问productpage应用。
curl --location --request GET 'http://{入口网关服务的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****"
}'

预期输出:

200

返回200,说明通过get请求的方式,请求体body的username与JWT中的Role相同,JWT中的username信息必须包含在bookinfo_managers范围内。并且JWT Token中的userGroupmanager的请求访问Productpage应用成功。如果您使用错误的JWT Token,或者请求中不包含JWT Token,将会返回403,说明访问Productpage应用失败。

FAQ

如何查看哪些Pod使用OPA策略?

OPA的执行引擎以Sidecar的方式和业务容器部署在同一个Pod中。您需要进入到Pod中,执行以下命令,可以看到应用在Pod上的全部OPA策略。
curl 127.0.0.1:15081/v1/policies

如何对Rego语法进行测试?

OPA Policy Agent官方提供了在线测试工具,您可以使用该工具对Rego编写的策略进行测试。

相关文档

如果您之前使用的是Configmap配置OPA策略,您可以参考以下文档: