マイクロサービスでの認証認可

複数のクラウドサービスを利用している（マルチクラウド）など、単純には閉域網を構築できない環境でマイクロサービスアーキテクチャを採用する場合には、サービス間の認証認可が必要となる。この場合のサービス間の認証認可方式を決める参考となる、OSSやSaaS、Webサービスで採用方式ついて整理した。

#### Istio

サービスメッシュの実装として有名な[Istio](https://istio.io/)ではサービス間通信を以下のように制御できる。

* Istioの認証認可では認証主体がService Identityというモデルで抽象化され、KubernatesやIstioで定義するService Accountに加えて、GCP/AWSのIAMアカウントやオンプレミスの既存IDなどをService Identityとして扱うことができる。
* サービス間の認証 (Peer Authentication) は、各サービス (Pod) に設置するSidecarプロキシ (Envoy) 同士での相互TLS認証を行うことが基本となるが、既存サービスの移行のために平文通信も許可するPermissiveモードにすることもできる。
* サービス間の認可 (Workload-to-Workload Authorization) は、アクション（ALLOWもしくはDENY）とルール（リクエスト元、リクエスト先、その他の条件）を認可ポリシーとして定義することで認可判定を行う。

##### 参考

* [Istioサービスメッシュ入門 - セキュリティ関連機能](https://www.slideshare.net/yokawasa/istio-114360124/37)
* https://istio.io/docs/concepts/security/

#### RFC 8693 OAuth 2.0 Token Exchange

OAuth 2.0 認可サーバーの機能を拡張して、Security Token Service (STS) と呼ばれる、クライアントから受け取ったトークンを検証して他の用途で使うための新しいトークンを発行する機能が定義されている。

* STSの典型的なユースケースとして以下が説明されている。

  ```
  クライアント --> リソースサーバー --> バックエンドサービス
  ```
  * クライアントがユーザー（サブジェクト）の認可を受けて得たクレデンシャル（典型的にはアクセストークン）を認証情報としてリソースサーバーを呼び出す。
  * リソースサーバーはSTSに受け取ったクレデンシャルを送信して、バックエンドサービスを呼び出すための新しいクレデンシャルを取得（Token Exchange）する。
  * リソースサーバーは新しいクレデンシャルを認証情報としてバックエンドサービスを呼び出す。

* Token Exchangeではトークンに、どのサブジェクトのリクエストを代行（Impersonation）しているか（Token Introspectionでのsub）の情報に加えて、誰がそのサブジェクトからの委譲（Delegation）を受けてリクエストをしているか（acter_tokenとactクレーム）の情報を入れることができる。

##### 参考

* https://tools.ietf.org/html/rfc8693
* https://www.techscore.com/blog/2019/09/06/keycloak-oauth-2-0-token-exchange/

#### Google Cloud

[Cloud Endpoint](https://cloud.google.com/endpoints)を利用したサービス間通信での認証の実現方式として以下が推奨されている。

* 呼び出し元となるサービスに[service account](https://cloud.google.com/iam/docs/understanding-service-accounts)を発行し、呼び出し元サービスがservice accountの秘密鍵を使って作成したJWTをAPI呼び出し時に送付する。
* [Cloud Endpoint](https://cloud.google.com/endpoints)に登録したOpenAPI定義にアクセスを許可するservice accountを記述する。これにより[Extensible Service Proxy](https://cloud.google.com/endpoints/docs/openapi/glossary?hl=ja#extensible_service_proxy) (ESP) がAPIリクエストに付けられたJWTとの検証を実施してくれる。
  * OpenAPI定義の`securityDefinitions.{Security Definition名}.x-google-issuer` にアクセスを許可するservice accountの名称を記述する。（複数のservice_accountに呼び出しを許可したい場合は複数のSecurity Definitionを記述する。）
  * service_accountは[Cloud IAM](https://cloud.google.com/iam/docs/overview?hl=ja)で管理されるリソースであるが、ESPにおいてはCloud IAMのパーミッションを利用した検証はサポートされていない。

##### 参考

* [Authentication between services](https://cloud.google.com/endpoints/docs/openapi/service-account-authentication)

#### Amazon Web Services

Google Cloudのようにサービス間通信での認証を解説した直接的なドキュメントは存在しないが、以下のように[API Gateway](https://docs.aws.amazon.com/apigateway/)と[AWS IAM](https://docs.aws.amazon.com/iam/)を組み合わせて実現するのが常套手段のようである。

+ マイクロサービスのAPIの前段にAPI Gatewayを置き、API Gatewayで認証認可を管理する。
+ 呼び出し元サービスをAWS IAMユーザーとして登録して、API Gatewayで管理されるAPIを呼び出すパーミッションを設定する。
+ マイクロサービスは割り当てられたAWS IAMユーザーで発行したアクセスキーを使って、API Gatewayで管理されるAPIを呼び出す。

##### 参考

* [Controlling and managing access to a REST API in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-control-access-to-api.html)
* [Control access to an API with IAM permissions](https://docs.aws.amazon.com/apigateway/latest/developerguide/permissions.html)
* [IAM Users](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html)
* [Best practices for managing AWS access keys](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html)

#### メルカリ（メルペイ）

メルカリ（メルペイ）ではマイクロサービス間通信の認証用に内部通信用トークン（JWT）を発行し、呼び出されたサービスが他のサービスを呼び出す場合はそのトークンを伝搬するという仕組みを採用している。

* 外部からのリクエストをGatewayサービスが受け、リクエストの検証と内部通信用トークン生成をAuthorityサービスに依頼する。
* Authorityサービスはリクエストに含まれるトークンの検証や必要な検証を他のマイクロサービスに依頼し、リクエストの正当性が確認できたら内部通信用トークンを発行して返却する。
  * 内部通信用トークンは1リクエストごとに1つ生成する。
  * 内部通信用トークンはJWT形式となっている。
* Gatewayサービスは受け取ったトークンを付けてリクエストを担当するマイクロサービスに処理を依頼する。
* 依頼を受けたマイクロサービスは受け取った内部通信用トークンの署名を検証して処理を実行する。
  * 各マイクロサービスにGoで実装されたSDKを提供し、トークンの検証を組み込んでいる。（SDKでは署名検証用の公開鍵をキャッシュするため、通信レスでトークン検証ができる。）
  * 他のマイクロサービスに処理を依頼する際には、受け取った内部通信用トークンを付けて処理を依頼する。

* マイクロサービス間通信での認可について
  * 3rd Partyからのリクエスト（OIDC）の場合のみ、内部通信用トークンにOIDCトークンから算出した内部用スコープを入れている。

##### 参考

* [マイクロサービスにおける内部通信の認証について](https://speakerdeck.com/pospome/maikurosabisuniokerunei-bu-tong-xin-falseren-zheng-nituite)
* [【レポート】Backend Engineer’s meetup ~マイクロサービスにおける認証認可基盤~](https://dev.classmethod.jp/articles/merpay-microservice-auth/)

#### プリズマティックス（prismatix）

* OAuth 2.0で認証認可するサービスがマイクロサービスで構成される場合には、下記のような玉突き認証認可が発生する。

  ```
  ユーザー -- ユーザー認可に基づくトークン -> サービスA -- ? -> サービスB
  ```

* このとき、サービスA → サービスBの呼び出し時の認証認可戦略には以下の2つが考えられる。

  * トークンを伝搬してユーザー認可に基づいて処理をする。（prismatixではこちらを採用）
  * ユーザー認可と関係なくサービスAとしてのクライアント認証を行う。

##### 参考

* [AWS Dev Day Tokyo 2018 で「マイクロサービス時代の認証と認可」の話をしてきた](https://dev.classmethod.jp/articles/microservice-auth-aws-dev-day-tokyo-2018/)