注目イベント!
アドベントカレンダー2024開催します(12/1~12/25)!
一年を締めくくる特別なイベント、アドベントカレンダーを今年も開催します!
初心者からベテランまで楽しめる内容で、毎日新しい技術トピックをお届けします。
詳細はこちらから!
event banner

Envoy と Open Policy Agent を使用した認可

| 8 min read
Author: shigeki-shoji shigeki-shojiの画像

庄司です。

Envoy proxy は API を使って動的に構成すると無停止で設定変更等を行うことができます。このような操作は 通常 Istio や AWS App Mesh のようなコントロールプレーンで行うことになります。

この一連の記事では Envoy proxy 単体の機能を説明するために静的な設定を用いて説明しています。

認証についての記事で OpenID Connect の ID Token の検証について説明しています。

この記事では、下の図のように、さらに認可ができるようにしたいと思います。

図1

Envoy proxy の構成

#

Envoy proxy の定義ファイル (front-envoy.yaml) は次のようになります。

static_resources:
  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          codec_type: AUTO 
          stat_prefix: ingress_http
          route_config:
            name: s3_route
            virtual_hosts:
            - name: static_web
              domains:
              - "*"
              routes:
              - match:
                  prefix: /
                route:
                  cluster: s3
                  auto_host_rewrite: true
                  regex_rewrite:
                    pattern:
                      google_re2: {}
                      regex: '^(.*)\/$'
                    substitution: '\1/index.html'
          http_filters:
          - name: envoy.filters.http.jwt_authn
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
              providers:
                amazoncognito:
                  issuer: "https://cognito-idp.<REGION>.amazonaws.com/<POOL ID>"
                  audiences:
                  - "<CLIENT ID>"
                  forward_payload_header: jwt-payload
                  remote_jwks:
                    http_uri:
                      uri: "https://cognito-idp.<REGION>.amazonaws.com/<POOL ID>/.well-known/jwks.json"
                      cluster: jwks
                      timeout: 5s
                    cache_duration: 600s
              rules:
              - match:
                  prefix: /
                requires:
                  provider_name: amazoncognito
          - name: envoy.filters.http.ext_authz
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
              grpc_service:
                envoy_grpc:
                  cluster_name: authz-opa
                timeout: 0.250s
              transport_api_version: V3
          - name: envoy.filters.http.router
  
  clusters:
  - name: s3
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: s3
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: <BUCKET_NAME>.s3.amazonaws.com
                port_value: 443
            hostname: <BUCKET_NAME>.s3.amazonaws.com
    transport_socket:
      name: envoy.transport_sockets.tls
  - name: jwks
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: jwks 
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: cognito-idp.<REGION>.amazonaws.com 
                port_value: 443
    transport_socket:
      name: envoy.transport_sockets.tls
  - name: authz-opa
    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: authz-opa
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: opa-service
                port_value: 9191

admin:
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9901

<> で囲んだプレースホルダーにはそれぞれ利用環境に合わせて設定してください。

Open Policy Agent の構成

#

認可のために追加したフィルターは、Open Policy Agent を利用する External Authorization フィルターです。

Open Policy Agent (OPA) は、Cloud Native Computing Fundation (CNCF) の graduated プロジェクトです。さまざまなポリシーの適用で利用されています。

Stop using a different policy language, policy model, and policy API for every product and service you use. Use OPA for a unified toolset and framework for policy across the cloud native stack.

使用する製品やサービスごとに異なるポリシー言語、ポリシーモデル、およびポリシーAPIの使用をやめましょう。クラウドネイティブスタック全体のポリシーの統合ツールセットとフレームワークにOPAを使用してください。

ここで追加したフィルターの定義は次の部分です。

- name: envoy.filters.http.ext_authz
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
    grpc_service:
      envoy_grpc:
        cluster_name: authz-opa
      timeout: 0.250s
    transport_api_version: V3

OPA は Envoy proxy とは別のコンテナで実行されます。そして、Envoy proxy と OPA とは gRPC による通信を行います。クラスタ定義は次の部分になります。

- name: authz-opa
  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: authz-opa
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: opa-service
              port_value: 9191

Envoy proxy のコンテナと OPA のコンテナは、コンテナ実行環境内のネットワークを利用するため、DNS 名をサービス名で指定することができます。

このための docker-compose.yaml は次のとおりです。

version: "3"
services:
  envoy:
    image: envoyproxy/envoy:v1.21-latest
    volumes:
      - ./front-envoy.yaml:/etc/front-envoy.yaml
    ports:
      - 8080:8080
      - 9901:9901
    command: ["-c", "/etc/front-envoy.yaml", "--service-cluster", "front-proxy"]
  opa-service:
    image: openpolicyagent/opa:latest-envoy
    volumes:
      - ./config.yaml:/work/config.yaml
      - ./opa.rego:/work/opa.rego
    command: ["run", "--server", "--log-level", "debug", "-c", "/work/config.yaml", "/work/opa.rego"]

OPA の構成ファイル (config.yaml) も必要です。これは次のとおりです。

plugins:
  envoy_ext_authz_grpc:
    addr: :9191

decision_logs:
  console: true

ポリシー

#

OPA はデータとポリシーを分離しています。ポリシーは Rego と呼ばれるドメイン固有言語 (DSL) で定義します。この記事では、/index.html へのリクエストのみを許可し、それ以外の場合は 403 Forbidden のレスポンスを返すように定義します。また、/index.html へのアクセスの場合には、JWT 認証トークンの cognito:username をヘッダに追加するようにしています。ファイル opa.rego は次のとおりです。

package envoy.authz

import input.attributes.request.http as http_request

default allow = false

name = name {
    jwt_payload := http_request.headers["jwt-payload"]
    payload := json.unmarshal(base64url.decode(jwt_payload))
    name := payload["cognito:username"]
}

allow = response {
    http_request.method == "GET"
    http_request.path == "/index.html"
    response := {
        "allowed": true,
        "headers": {
            "x-jwt-name": name
        }
    } 
}

実行

#

この設定による動作シーケンスの概要は図のようになります。正確に書く場合は、図中の jwks.json の取得は実際には非同期に実行されキャッシュされることに注意してください。

図2

以上のように定義した構成は、nerdctl または docker-compose で起動することができます。

nerdctl compose up

ここで説明した設定ファイル等は GitHub リポジトリにあります。

参考

#

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。