关于云原生:云原生-04用-Envoy-Open-Policy-Agent-做前置代理

6次阅读

共计 3770 个字符,预计需要花费 10 分钟才能阅读完成。

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

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

import input.attributes.request.http as http_request

default 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…
正文完
 0