🛡️

Envoy入門(その2)外部認証

2024/11/11に公開

はじめに

マイクロサービスやWeb API界隈では、サービス間のネットワークの制御をライブラリではなく、プロキシのコンテナをサイドカーとして使うのだとか。そのデファクトスタンダード的な立ち位置なのが Envoy さん。

ですが、日本語の新しい情報が少ないので、だったら本家のサイトを見ながら、自力でなんとかするしかないね。という記事です。

第一回目は、Transport layer security (TLS) でした。

https://zenn.dev/robon/articles/fc7feab5e77d59

第二回目は、External authorization (ext_authz) filter です。

やってみた

環境構築

今回は省略します。必要な方は、(その1) をご覧ください。

External authorization (ext_authz) filter

External Authorization 自体の説明とそこからさらにArchitecture overviewのリンクもありますが、今回も、ま、やってみよう!ということで。

このサンプルが目指しているのは、x-current-useer ヘッダーに認証サーバから得られたユーザー名を付けて、内側のサーバーを呼び出すことになります。

Step 1

ということで、今回も Step 1 では、必要なものを全部作って、動かします。書いてあるとおりにやると(若干出力が異なりますが)動きます。

$ docker-compose pull
Pulling ext_authz-http-service ... done
Pulling ext_authz-grpc-service ... done
Pulling ext_authz-opa-service  ... done
Pulling upstream-service       ... done
Pulling front-envoy            ... done

$ docker-compose up --build -d
Creating network "ext_authz_default" with the default driver
Creating ext_authz_ext_authz-http-service_1 ... done
Creating ext_authz_ext_authz-opa-service_1  ... done
Creating ext_authz_upstream-service_1       ... done
Creating ext_authz_ext_authz-grpc-service_1 ... done
Creating ext_authz_front-envoy_1            ... done

$ docker-compose ps
Name                             Command                  State                     Ports               
-----------------------------------------------------------------------------------------------------------------------
ext_authz_ext_authz-grpc-service_1   /app/server -users /etc/us ...   Up
ext_authz_ext_authz-http-service_1   docker-entrypoint.sh /bin/ ...   Up
ext_authz_ext_authz-opa-service_1    ./opa_envoy_linux_amd64 ru ...   Up
ext_authz_front-envoy_1              /docker-entrypoint.sh /bin ...   Up             10000/tcp, 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp                 
ext_authz_upstream-service_1         python3 /code/service.py         Up (healthy)

今回も docker-compose.yaml で登場人物を見ていきましょう。

https://github.com/envoyproxy/examples/blob/main/ext_authz/docker-compose.yaml

今回、Envoy は一つで ext_authz_front-envoy_1。内側のサービスも一つで ext_authz_upstream-service_1。認証サーバが三つで、環境変数 FRONT_ENVOY_YAML を切り替えてテストしていくことになります。

Step 4 ??

なぜか Step 1 の次は Step 4 です。まぁ、2、3、4 で、三つの認証サーバを試そうね♡。という予定だったのかもしれません。ということで、それぞれ見ていきます。

ext_authz-http-service

最初は、docker-compose.yaml にハードコードしてある config/http-service.yaml から。

https://github.com/envoyproxy/examples/blob/main/ext_authz/config/http-service.yaml

listener は、8000 番ポートで待っていて、filter_chains の最初の filter の目的地は、upstream-service cluster で、これは upstream-service の 8080 番ポートです。

ただ、http_filters は、いつもの Router の前に ExtAuthz が刺さっていて、これが ext_authz-http-service cluster と通信してレスポンスの x-current-user ヘッダーをチェックしています。

ですから、書かれているとおり、単純に GET すると失敗します。

$ curl -v localhost:8000/service
*   Trying [::1]:8000...
* Connected to localhost (::1) port 8000
> GET /service HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.3.0
> Accept: */*
> 
< HTTP/1.1 403 Forbidden
< date: Wed, 30 Oct 2024 10:26:09 GMT
< server: envoy
< content-length: 0
< 
* Connection #0 to host localhost left intact

なんで localhost:8000/service なのか?は、upstream-service が待っているからです。

https://github.com/envoyproxy/examples/blob/main/ext_authz/upstream/service/service.py

認証サーバも確認しておきましょう。

https://github.com/envoyproxy/examples/blob/main/ext_authz/auth/http-service/server.js

authorization ヘッダーの Bearer の次の token が users.json にあれば、userx-current-useer ヘッダーとして、200 OK を返し、無ければ、403 Forbidden を返していることがわかります。(require って JSON も食べられるんですね。)

で、users.json は、こんな感じ

https://github.com/envoyproxy/examples/blob/main/ext_authz/auth/users.json

なので、成功パターンは、以下のようになります。

$ curl -v -H "Authorization: Bearer token1" localhost:8000/service
*   Trying [::1]:8000...
* Connected to localhost (::1) port 8000
> GET /service HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.3.0
> Accept: */*
> Authorization: Bearer token1
> 
< HTTP/1.1 200 OK
< content-type: text/plain; charset=utf-8
< content-length: 30
< date: Wed, 30 Oct 2024 10:43:16 GMT
< server: envoy
< x-envoy-upstream-service-time: 0
< 
* Connection #0 to host localhost left intact
Hello user1 from behind Envoy!

ext_authz-grpc-service

こちらも、まずは、Envoy の Yaml ファイルから

https://github.com/envoyproxy/examples/blob/main/ext_authz/config/grpc-service/v3.yaml

ExtAuthz で、envoy_grpc として呼び出しているので、チェックなどは ext_authz-grpc-service 側へ行っているようです。

ここを作るのは、Envoy入門(笑)の範囲を超えていますが、軽く見ておくと

https://github.com/envoyproxy/examples/blob/main/ext_authz/auth/grpc-service/pkg/auth/v3/auth.go

のように、ガッツリ Envoy さんの API を使って実装されていますが、authorization ヘッダーのチェックと成功時の x-current-user ヘッダーの付与など ext_authz-http-service と同じ実装になっているようです。

ということで、落として、設定を変えて立ち上げます。(出力は省略します。)

$ docker-compose down
$ FRONT_ENVOY_YAML=config/grpc-service/v3.yaml docker-compose up --build -d

以下は、失敗し

$ curl -v localhost:8000/service

以下は、成功します。

$ curl -v -H "Authorization: Bearer token1" localhost:8000/service

ext_authz-opa-service

3つ目の最後は、Open Policy Agent の OPA です。最初に見た docker-compose.yaml のこの部分で定義しています。

https://github.com/envoyproxy/examples/blob/main/ext_authz/docker-compose.yaml#L36-L46

ここで使用している Dockerfile は、以下のもので

https://github.com/envoyproxy/examples/blob/main/ext_authz/Dockerfile-opa

この policy.rego をコピーしています。

https://github.com/envoyproxy/examples/blob/main/ext_authz/config/opa-service/policy.rego

詳細は、OPA のドキュメントを読む必要がありますが、rego というのは、prolog 系の宣言型の言語ということなので、上から順に実行されるというよりも、入力と宣言されたルールが適合したものが出力になるみたいな感じのようです。

上の例では、HTTP メソッドが GET なら、許可されて、x-current-user ヘッダに OPA が入ることになります。これを受けた動きを定義するのが、以下の Envoy の Yaml ファイルです。

https://github.com/envoyproxy/examples/blob/main/ext_authz/config/opa-service/v3.yaml

と言っても、期待している x-current-user ヘッダーの設定が終わっているので、通すだけです。ただし、HTTP メソッドが GET でないとエラーになります。当然、リクエストの URL も指定できますので、API 単位での認可が実現できる仕組みになります。

動かします。(出力は省略します。)

$ docker-compose down
$ FRONT_ENVOY_YAML=config/opa-service/v3.yaml docker-compose up --build -d

テストします。GET であれば成功します。

$ curl localhost:8000/service --verbose 
*   Trying [::1]:8000...
* Connected to localhost (::1) port 8000
> GET /service HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.3.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< content-type: text/plain; charset=utf-8
< content-length: 28
< date: Sat, 02 Nov 2024 05:25:34 GMT
< server: envoy
< x-envoy-upstream-service-time: 0
< 
* Connection #0 to host localhost left intact
Hello OPA from behind Envoy!

POST は失敗します。

$ curl -X POST localhost:8000/service --verbose
*   Trying [::1]:8000...
* Connected to localhost (::1) port 8000
> POST /service HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.3.0
> Accept: */*
> 
< HTTP/1.1 403 Forbidden
< date: Sat, 02 Nov 2024 05:27:21 GMT
< server: envoy
< content-length: 0
< 
* Connection #0 to host localhost left intact

おわりに

外部認証についても、サンプルとマニュアルを見ながらであれば、設定できそうですね。
それでは、第3回があれば(笑)、第3回で。

GitHubで編集を提案
株式会社ROBONの技術ブログ

Discussion