系列专栏申明:比拟流水,次要是写一些踩坑的点,和实际中与文档差距较大的中央的思考。这个专栏的典型特色可能是 次佳实际,争取能在大量的最佳实际中生存。

Open Policy Agent 吸引我的点在于 Policy as Code,所以看上去整个云原生概念吸引我的点就在于 X as Code 或者更精确的说是 X as Declarative DSL

TODO:会有 JWT 的局部,然而第一稿来不及写了,能够珍藏当前来看。

PBAC: Policy Base Authorization Control 中有三个外围的概念,PIPPDPPEP

OPA 是一个 PDP,即 规定引擎。审计方在这里用 自然语言的 DSL 形容权限管制规定,OPA 执行这些规定并返回 allowdeny。晚期的权限规定是单向的,只能从需要被翻译成面向过程的代码,而很难从曾经实现的代码反推出理论被执行的规定,也很难对规定进行更新或迁徙。应用 DSL 是量变的要害,它使得需要和代码能够双向翻译了。

一、申请流向

这是下面概念图落地后的实例图,有两个比拟显著的简化点是:

  1. 本文实际上只是第一道网关,只做简略的准入管制,不做行、列粒度的数据权限管制。所以和 Envoy(PEP) 配套的 OPA(PDP) 没有去申请额定的 PIP 取得更多属性,而是仅应用 Envoy 带来的属性包含 JWT。因而这里通常只会携带以后用户的 id 和要害 role。如果要做细粒度的权限管制,应该在 Service 处再加一套 PEP + OPA(PDP)
  2. OPA 要求所有的 Policy 都在内存里。它提供了 restful api 用来更新内存中的 policy,这是大部分入门介绍的形式,但看上去没有实际意义。另外还提供了从数据源实时同步的形式,这个应该才是 正确的形式,不在本文中详细描述了。本文在启动时从文件中加载。

docker-compose.yml

  app:    container_name: app    image: hub.docker.com/example/app:latest    networks:      - traefik    # 这里不须要配置 traefik 相干的 labels,因为理论对外裸露的是 envoy  app-envoy:    container_name: app-envoy    image: envoyproxy/envoy-alpine:v1.17.4    networks:      - traefik    labels:      - "traefik.enable=true"      - "traefik.http.routers.app.rule=Host(`api.example.com`)"      # 省略其它必须的 traefik labels    volumes:      - "/path/to/envoy.yaml:/etc/envoy/envoy.yaml"  app-opa:    container_name: app-opa    image: openpolicyagent/opa:0.35.0-envoy-5-rootless    networks:      - traefik    volumes:      - "/path/to/policy.rego:/config/policy.rego"    command:      - run      - --log-level=debug      - --set=decision_logs.console=true      - --skip-version-check # 这里敞开向 open telemetry 上报埋点      - --server      - --set=plugins.envoy_ext_authz_grpc.addr=:9191      - --set=plugins.envoy_ext_authz_grpc.path=com/example/app/opa/allow      - /config/policy.rego

几个踩坑点:

  1. opa 应用的是内置 envoy plugin 的 版本

envoy.yaml

static_resources:  listeners:  - address:      socket_address:        address: 0.0.0.0        port_value: 80    filter_chains:    - filters:      - name: envoy.http_connection_manager        typed_config:          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager          # 省略          route_config:            # 省略          http_filters:          - name: envoy.filters.http.ext_authz            typed_config:              "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz              transport_api_version: V3              grpc_service:                envoy_grpc:                  cluster_name: app-opa                timeout: 0.5s              # 省略          - name: envoy.filters.http.cors          - name: envoy.filters.http.router            typed_config: {}  clusters:  - name: app    connect_timeout: 0.25s    type: strict_dns    dns_lookup_family: V4_ONLY    lb_policy: round_robin    load_assignment:      cluster_name: app      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address:                address: app                port_value: 8080  - name: app-opa    connect_timeout: 0.25s    type: strict_dns    typed_extension_protocol_options:      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions        explicit_http_config:          http2_protocol_options: {}    load_assignment:      cluster_name: app-opa      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address:                address: app-opa                port_value: 9191

几个踩坑点:

  1. envoy 1.17 开始默认应用 v3,所以降级上来的不少配置语法要改,文档比拟难找,没有手把手教程,但理论改好当前平滑降级
  2. 模仿在一个 Pod 内的场景,cluster 的定义用的是在 docker 内的 name,没有去 traefik 再兜一圈进来,所以也不须要为 app 配置 traefik labels 依据 host 做路由

policy.rego

package com.example.app.opaimport input.attributes.request.http as http_requestdefault allow = {  "allowed": false,  "headers": { "x-envoy-opa": "deny", "content-type": "application/json" },  "body": "{ \"code\": -1, \"message\": \"deny\" }",  "http_status": 403}allow = response {  http_request.method == "OPTIONS"  response := {    "allowed": true,    "headers": { "x-envoy-opa": "allow" }  }}allow = response {  allowed_methods := ["GET", "POST", "PUT", "DELETE"]  http_request.method == allowed_methods[_]  http_request.headers["Authorzation"] == "Basic BasicAccessToken"  glob.match("/app/open-api/*", [], http_request.path)  response := {    "allowed": true,    "headers": { "x-envoy-opa": "allow" }  }}

几个踩坑点:

  1. 挺顺利的,Rego 的语法值得详解一下,前面补充
  2. 应用的是 opa-envoy-plugin,所以有些语法不是 opa 自带的,而是 envoy-plugin 集成进来的,比方 glob.match
  3. 等有空钻研 opa 调实时接口更新规定,再补充一下

参考资料:

  1. Envoy Getting Started
  2. Getting Started With Rego,对于 Rego 的一些反直觉的语法,但你可能须要适应这些语法,在 DSL 里写代码的确是全新的体验
  3. Policy Language
  4. https://github.com/NewbMiao/o...