🛡️

Envoy入門(その5)Auth0 で RBAC

2024/12/02に公開

はじめに

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

(その4)のおわりに で匂わせた

今回、Lua を取り上げたのは、Auth0 で RBAC(Role-Based Access Control)を設定して、JWT に反映された内容に応じた認可を Envoy で実現したかったためです。

をやります。

やってみた

環境構築

Envoy

(その3)の環境構築と同じリポジトリを git clone して頂いて、Auth0 の送信先をご自分のものに変更すると動くかと思います。

Auth0

Auth0 さんも(その3)の環境構築 の続きで設定をしていきますので、(その3)をやっていない方は、(その3)からお願いします。

Permission

下から設定していきます。
今回は、read:servicewrite:servicePermission として、これを API に対する許可とします。User には、Role を介して、Permission を設定するため、UserRole で管理できます。Role を実現するための Permission は別で管理しても、User に適切な Permission が付与できるということになります。

Permission は、API から設定するので、ApplicationsAPIs でご自分の API を選択して、Permissions タブを選んで、Add a Permission します。今回は、上のとおり、read:servicewrite:service の2つを追加しました。

RBAC

同じく、ご自分の API を選択した状態で、Settings タブの下の方の RBAC Settings で、Enable RBACAdd Permissions in Access TokenON にします。ON にしただけで反映された感のある UI になっていますが、さらに下の方の Save ボタンを押さないと反映されません。(ハマりポイントです)

Role

Role は、左側のトップメニューの User ManagementRoles を選んで、Create Role ボタンを押します。
Name は、ReaderWriter にしましたが、Role よりも上は、Auth0 の世界で整合性が合っていれば、Envoy というか API プログラムの動作・設定には影響はありません。RolePermissions タブで Permission を指定できますので、Reader には、read:serviceWriter には、write:serviceAdd Permissions ボタンで設定します。

User

Role と同様、User ManagementUsers を選んで、Create User ボタンを押します。Email は、+ のエイリアスでも通るので、テスト用のユーザーは量産可能です。以下の4ユーザーを作成しました。

User Reader Writer
+none
+reader
+writer
+both

Access Token 取得用 Application

ブラウザから上で作った UserLog in して、Access Token を取得するための Application を作ります。
ApplicationsApplications から(自分はビビりましたが、ビビらずに)Create Application します。
Name は、My App のままでも良いです。Single Page Web Applications で進めます。(もっと簡単なものもあるかもしれませんが、試してません。)

My AppQuickstartJavaScript を選んで、Download すると vanillajs-01-login.zip がダウンロードされます。これのコピーは、今回のリポジトリにあるのですが、Download したものは、自分用の auth_config.json が入っていますので、それをクローンしたリポジトリにコピーすると早いです。

その他、画面に書かれているとおり、以下の3か所に同じ URI を設定します。これで、Auth0 さんとアプリが連携できるようになります。

Name Value
Allowed Callback URIs http://localhost:3000
Allowed Logout URIs http://localhost:3000
Allowed Web Origins http://localhost:3000

で、Node.js がセットアップされた環境であれば、基本的には、Download した vanillajs-01-login.zip を解凍して、以下のようにすると動くのですが、まだ、API 側で認可可能な Access Token は取得できません。

$ npm ci
$ npm run dev

いつもと同じように、curl で実験していくので、Chrome のディベロッパーツールのコンソールに Access Token の文字列が表示できるところまで、最小限の改造を行います。

  1. auth_config.jsonaudience を追加します。ここには、(その3)で作った API の audience を指定します。(その3)の例のままなら https://zenn.dev/take0a です。

  2. auth0/01-login/public/js/app.js の認証に audience を加えて、ログインの直後に Access Token を取得して、コンソールに書きだします。

--- a/auth0/01-login/public/js/app.js
+++ b/auth0/01-login/public/js/app.js
@@ -54,7 +54,10 @@ const configureClient = async () => {
 
   auth0Client = await auth0.createAuth0Client({
     domain: config.domain,
-    clientId: config.clientId
+    clientId: config.clientId,
+    authorizationParams: {
+      audience: config.audience
+    }
   });
 };
 
@@ -121,6 +124,10 @@ window.onload = async () => {
       }
 
       console.log("Logged in!");
+
+      const token = await auth0Client.getTokenSilently();
+      console.log("Access Token", token);
+
     } catch (err) {
       console.log("Error parsing redirect:", err);
     }

これで、上で作った4人のユーザーのアクセストークンのJWTが入手できます。

take0a/envoy-samples/auth0

(その3)の復讐

https://github.com/take0a/envoy-samples

このリポジトリを持ってきて、以下のようにすると、(その3)ができます。

$ docker-compose down
$ FRONT_ENVOY_YAML=config/auth0/v3.yaml docker-compose up --build -d
$ docker-compose ps

RBAC 認可

RBAC 認可できる Envoy で動作確認するには、以下のようにします。

$ docker-compose down
$ FRONT_ENVOY_YAML=config/rbac/v3.yaml docker-compose up --build -d
$ docker-compose ps

Auth0 用の Application のコンソールからコピペした Access Token を使用して、実験します。
まずは、+none のユーザー。認可でエラーになります。

$ curl -v localhost:8000/service --header 'authorization: Bearer eyJ...Bg'
*   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 eyJ...Bg
> 
< HTTP/1.1 403 Forbidden
< content-length: 22
< content-type: text/plain
< date: Thu, 07 Nov 2024 09:49:51 GMT
< server: envoy
< 
* Connection #0 to host localhost left intact
JWT validation failed.

次に、+both のユーザー。認可されているので、内側のサービスまで通ります。

$ curl -v localhost:8000/service --header 'authorization: Bearer eyJ...Cg'
*   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 eyJ...Cg
> 
< HTTP/1.1 200 OK
< content-type: text/plain; charset=utf-8
< content-length: 29
< date: Thu, 07 Nov 2024 09:52:04 GMT
< server: envoy
< x-envoy-upstream-service-time: 0
< 
* Connection #0 to host localhost left intact
Hello None from behind Envoy!

+reader のユーザーも同様に通ります。

$ curl -v localhost:8000/service --header 'authorization: Bearer eyJ...2g'
*   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 eyJ...2g
> 
< HTTP/1.1 200 OK
< content-type: text/plain; charset=utf-8
< content-length: 29
< date: Thu, 07 Nov 2024 09:52:18 GMT
< server: envoy
< x-envoy-upstream-service-time: 0
< 
* Connection #0 to host localhost left intact
Hello None from behind Envoy!

+writer のユーザーは認可エラーになります。

$ curl -v localhost:8000/service --header 'authorization: Bearer eyJ...xg'
*   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 eyJ...xg
> 
< HTTP/1.1 403 Forbidden
< content-length: 22
< content-type: text/plain
< date: Thu, 07 Nov 2024 09:59:09 GMT
< server: envoy
< 
* Connection #0 to host localhost left intact
JWT validation failed.

おまけで、+none ユーザーの古い Access Token を使用した場合は、前段の JWT を Auth0 に検証してもらうステップでエラーになります。

$ curl -v localhost:8000/service --header 'authorization: Bearer eyJ...Ng'
*   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 eyJ...Ng
> 
< HTTP/1.1 401 Unauthorized
< www-authenticate: Bearer realm="http://localhost:8000/service", error="invalid_token"
< content-length: 14
< content-type: text/plain
< date: Thu, 07 Nov 2024 09:43:33 GMT
< server: envoy
< 
* Connection #0 to host localhost left intact
Jwt is expired

できました。しゅーりょー。

https://github.com/take0a/envoy-samples/blob/master/auth0/config/rbac/v3.yaml

裏側を見ておきます。(その3)との差分です。

40a41
>                   payload_in_metadata: jwt_payload
46c47,62
< 
---
>           - name: envoy.filters.http.lua
>             typed_config:
>               "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
>               default_source_code:
>                 inline_string: |
>                   function envoy_on_request(request_handle)
>                     local jwt_authn = request_handle:streamInfo():dynamicMetadata():get("envoy.filters.http.jwt_authn")
>                     if (jwt_authn ~= nil and jwt_authn.jwt_payload ~= nil and jwt_authn.jwt_payload.permissions ~= nil) then
>                       for k, v in pairs(jwt_authn.jwt_payload.permissions) do
>                         if(v == 'read:service') then
>                           return
>                         end
>                       end
>                     end
>                     request_handle:respond({[":status"] = "403",},"JWT authorization failed.") 
>                   end

まず、jwt_authn のフィルタで JWT の Payload を jwt_payload として metadata に入れておきます。
続くフィルタは、(その4)で学んだ Lua フィルタで、上の jwt_payload があって、read:service パーミッションがあった場合だけ正常終了して、その他は、403 エラーにします。

この permissions の検査を本番の API のものにすれば、Auth0 の認証に加えて、Auth0 の RBAC 設定による認可も API に反映できました。

おわりに

Envoy で、API のセキュリティを確保するという本来の目的は、一旦、達成できましたので、このシリーズは終了にします。

番外編として、envoy getting started などで調べると、先輩方は、Try Envoy という Katacoda という教材を使用していたという記事が見つかりますが、Katacoda がサービス終了していて、以下の教材のリポジトリはあるもののバージョンが古くて動きません。

https://github.com/envoyproxy/katacoda-scenarios

この入門記事を書くにあたり、この教材をなんとかできないか?ということで、Katacoda クローンの Killercoda で動くようにしてみました。

https://killercoda.com/envoyproxy-scenarios

このコースのリポジトリは、以下にありますが、今回のシリーズの方が実用的かな?と思いますので、メンテナンスの予定はありません。

https://github.com/take0a/envoy-killercoda-scenarios

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

Discussion