こちらは バイセルテクノロジーズ Advent Calendar 2023 の9日目の記事です。 昨日は大舘さんによるKubernetes の Rolling Update の落とし穴 〜replicas の値は余裕を持って設定しよう〜でした。
はじめに
こんにちは。バイセルテクノロジーズの開発1部に所属している、BEエンジニアの金澤です。
バイセルでは顧客・買取案件管理システムであるGYROというサービスを開発・運用しています。
最近では、バイセルで目下構築中の総合リユースプラットフォームを目指すプロダクト群“Cosmos“の1つであるCRMというサービスが、GYROの機能を一部移管する形で運用がスタートしました。
この記事では、Kubernetes(以下、k8s)とIstioでインフラ構成されEntra ADで認証認可をしているGYROに、IdPが異なるCRMを接続するために実施したあれこれの設定をお話しします。
一点注意ですが、本記事の内容はk8sやIstioについての詳細な解説ではありません。
Kustomizeを用いたk8sマニフェストの設定事例や、多機能なIstio Ingress Gatewayの利用事例として参考になれば幸いです。
背景
顧客管理を目的として開発されたGYROは、管轄する業務ドメインが巨大になりすぎています。
お客様からのアポイントメント予約から案件管理、契約書の決裁までを担うため、関係する事業部も社内随一となるサービスです。
現在ローンチから3年が経過し、事業部が打つ施策に対して小回りが効かなかったり、変化する業務フローに対して適切なUI/UXの提供が追いつかなかったりしているのが現状です。
CRMは、前述の課題を解決するためより顧客管理に特化して開発・運用されています。
CRMのファーストローンチにあたり、CRMのAPIの一部はGYROのAPIを利用することになったのですが、CRMとGYROでは利用しているIdPが違うという問題ありました。
これらを解決するためにIstio Ingress Gatewayをうまく設定していくことになりました。
GYROのアーキテクチャ
ここでGYROのアーキテクチャを簡単に示します。
構成を簡単に整理すると以下にまとめられます。
- 認証基盤はMicrosoft Entra ID(旧: Azure AD)を利用
- GYROのBEはBFFが入口であるマイクロサービスとなっており、それぞれのサービスはk8s上のコンテナで稼働
- k8sのロードバランサとしてIstio Ingress Gatewayを採用し、マイクロサービス間の通信はEnvoyを利用
GYROクライアントからHTTPリクエストする際(GYROのみで完結する場合)、通信の流れをシーケンス図を示すと以下の通りです。
異なる IdP を利用しているプロダクトを繋ぐために
CRMはIdPにAuth0を採用しているため、Entra IDを利用しているBFFに直接リクエストできません。
そのため、CRM用のBFF(以下、CRM-BFF)を立て、そこからBFFへのHTTPリクエストを行う構成を取りました。
CRMを含めたアーキテクチャ図はこちらです。
CRMからはAuth0のJWTをAuthorizationヘッダーに積んで、GYROにリクエストを飛ばすように実装します。
この通信経路を実現するために、k8sのマニフェストを適切に記述することが必要になりました。
実際に行った設定
GYRO-CRM連携を実現するために満たすべき仕様は以下の2つとなりました。
- CRMからのリクエスト時はAuth0によってJWT検証し、CRM-BFFに通す
- CRM-BFF → BFFへのリクエスト時には、BFF前段にて行うEntra IDへのJWT検証をパス
実現すべき通信処理を整理したシーケンス図は以下の通りとなります。
以上の仕様を満たすように、k8sマニフェストに設定を追記していきます。
GYROではKustomizeというツールを用いてk8sのマニフェストを記述しています。
複数の設定ファイルをまとめたり、共通の設定が記述されたファイルに環境ごとの設定を上書き(overlays)したりできるものです。これにより、複数の環境に対応するマニフェストの管理工数を下げることができています。
特定サービスへのリクエスト時にJWT検証先を変更して接続を許可
GYROでは、APIのエンドポイントに応じてアクセス先のサービスを指定するため、IstioのVirtual Serviceと呼ばれる機能を利用しています。
CRMからGYROにリクエストした場合は、エンドポイントに追加したPrefixによってCRM-BFFのサービスにアクセスするよう設定しています。ここで、CRM-BFFへのアクセス時に行うJWT検証のアクセス先にAuth0を追記します。
実際の設定ファイルのイメージはこちらとなります。
ファイルパスについては、Kustomizeの設定でマージするパスを設定可能であるため、あくまで参考としていただければ幸いです。
# 環境ごと設定しているので overlays を利用 k8s_manifests/overlays/ENV_NAME/istio-related └── request_authentication ├── bff │ └── patch-audiences.yaml └── crm-bff └── patch-audiences.yaml # これを追加
# crm-bff/patch-audiences.yaml apiVersion: security.istio.io/v1beta1 kind: RequestAuthentication metadata: name: crm-bff-ra namespace: default spec: JWTRules: - issuer: https://hogehoge.auth0.com/ jwksUri: https://hgoehoge.jp.auth0.com/path/to/keyUri audiences: - https://hogehoge.api - https://hogehoge.auth0.com/path/to/audiences forwardOriginalToken: true
既存の「BFFのサービスへのアクセス時にはEntra IDに対してJWT検証をする」という設定ファイルの他に、「CRM-BFFのサービスへのアクセス時にはAuth0に対してJWT検証をする」というものを新規追加した形になります。
以上でissuerにて指定した発行元が発行するJWTを、jwksUriに設定した公開鍵で検証するための設定ができました。
続いて「JWT検証が問題ない場合にCRM-BFFへリクエストを許可する」設定を追加します。
設定したファイルのイメージを示します。
# 全環境共通の設定のため base 配下に追加 k8s_manifests/base/istio-related └── authorization_policy ├── bff-allow-with-JWT.yaml └── crm-bff-allow-with-JWT.yaml # これを追加
# authorization_policy/crm-bff-allow-with-JWT.yaml apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: crm-bff-ap-allow-with-JWT spec: selector: matchLabels: app: crm-bff rules: - from: - source: requestPrincipals: ["*"]
以上を記述することで、JWT検証後のリクエストを許可する設定となります。
ここまで設定することで、CRM → GYROのリクエスト時にAuth0へのJWT検証とCRM-BFFへのアクセスが可能になりました。
特定サービスからのリクエスト時にJWT検証をパスする
CRM-BFF → BFFへのアクセス時にJWT検証を無視するような例外処理を追加していきます。
今回は、CRM-BFFからのアクセスであることをサービスアカウントによって判断しています。
Istio上でサービスアカウントの定義し、CRM-BFFのk8sマニフェストのdeploymentsに定義したサービスアカウント名を記述しています。
# k8s_manifests/base/serviceaccounts/crm_bff_serviceaccount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: crm-bff-sa
# k8s_manifests/base/deployments/crm_bff_deployment.yaml(一部抜粋) apiVersion: apps/v1 kind: Deployment metadata: name: crm-bff-deployment labels: app: crm-bff spec: template: spec: serviceAccountName: crm-bff-sa # ここで指定
前章で追加したcrm-bff-allow-with-JWT.yamlと同階層に、BFFに対するauthorization_policyを追加します。
k8s_manifests/base/istio-related └── authorization_policy ├── bff-allow-from-crm.yaml # これを追加 ├── bff-allow-with-JWT.yaml └── crm-bff-allow-with-JWT.yaml
# authorization_policy/bff-allow-from-crm.yaml apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: bff-ap-allow-from-cosmoscrm-bff spec: selector: matchLabels: app: bff rules: - from: - source: principals: ["name/of/service-account/crm-bff-sa"]
サービスアカウント名で判断してアクセスを許可するような設定を記述しています。
ここまで設定すれば、CRM-GYRO間のアクセスが可能である…と思っていました。
JWT 検証が無視されないエラーとその対応
ここまで設定をしたにも関わらず、CRM-BFF → BFFの通信にてJWT検証が実行されてしまう事象が起きました。
結論から示すと、原因は「CRM-BFFからBFFにJWTを渡す際、Authorizationヘッダーに入れていたから」となります。
GYROではJWTに格納されている従業員番号を元に独自の認可処理を行っているため、Entra ID / Auth0問わずJWTに従業員情報を含めています。
Isitoの仕様なのかどこかで設定してしまったのか、「HTTPリクエストにAuthorizationヘッダーが存在している場合にはJWT検証する」というルールが優先されてしまうようでした。
以上より、CRM-BFFにてJWTを独自ヘッダーに積み換えることで対応しました。
ここまで設定することで、無事CRM-GYRO連携が実現できました。
結びとして
本記事では、Entra IDとAuth0という異なるIdPを利用しているプロダクトを接続するため、Istioの設定をはじめとするk8sのマニフェストの設定事例を紹介しました。
実際に設定してみて、今回のような複雑なシステムアーキテクチャや要件に対しても対応できる懐の深さがあると感じました。
k8sはよく「できることが多すぎて使いこなせない」と言われていますが、やりたいことを実現できる仕組みでもあります。適切な設定や機能を組み合わせ、うまく疎通させる楽しさがインフラの良いところですね。
無数にある設定の中で、複雑なインフラ構成を組まないといけなくなった場合の一助となれば幸いです。
BuySell Technologiesではエンジニアを募集しています。
新規のプロダクトがどんどんリリースされている今のバイセルでは、アーキテクチャ設計からスマートな実装まで、様々な場面で力になってくださる方を募集しています。
このような事例に対して一家言あるという方、ぜひお力を貸していただけませんか?
ご応募お待ちしております。
明日は神保さんによるフロントエンドの新規開発でNext.jsの採用を見送った話です。お楽しみに。