Envoy を使用して ID Token (OIDC) を検証する

以前構築した S3 にアクセスする Envoy proxy を拡張して、Amazon Cognito Userpool で認証されたユーザのみがこの静的コンテンツにアクセスできるようにしたいと思います。

図1

Envoy proxy は API を使って動的に構成すると無停止で設定変更等を行うことができます。このような操作は Istio や AWS App Mesh のようなコントロールプレーンで行うことになりますが、この一連の記事では 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.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

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

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

OpenID Connect の ID Token を検証するために JWT Authentication フィルターを追加しています。該当部分は次のところです。

- 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

Envoy proxy のこのフィルターの実装では、OpenID Connect Discovery を経由した JSON Web Key (JWK) の取得はできません。そのため、remote_jwks にキー取得の URI を設定する必要があります。

これに対応して、JSON Web Key を取得するクラスタ設定も必要になります。

- 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

設定したら、次のコマンド (nerdctl または docker) で起動することができます。

nerdctl run -it --rm --name envoy -v `pwd`/front-envoy.yaml:/etc/front-envoy.yaml -p 8080:8080 -p 9901:9901 envoyproxy/envoy:v1.21-latest -c /etc/front-envoy.yaml --service-cluster front-proxy

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

図2

まとめ

ここまで説明したとおり、ノーコードで静的コンテンツに対する認証機能の追加ができました。静的コンテンツに限らず、既存の認証機能を持たない API サーバに認証機能を追加する場合も同様の方法で追加することができます。

次回は、さらに認可機能の追加について説明します。ここで説明した設定ファイル等は GitHub リポジトリにあります。

参考