API GatewayとOpen Policy Agent (OPA)を使用したRBAC
May 15, 2023
さまざまなアクセス制御モデルと実装方法が利用可能であるにもかかわらず、バックエンドサービスAPIの認可システムを構築することは依然として難しい課題です。しかし、最終的な目標は、適切な個人が関連するリソースに適切にアクセスできるようにすることです。この記事では、オープンソースのAPIゲートウェイであるApache APISIXとOpen Policy Agent(OPA)を使用して、APIに**ロールベースのアクセス制御(RBAC)**認可モデルを有効にする方法について説明します。
学習目標
この記事を通じて、以下のことを学びます:
- RBACとは何か、そしてどのように機能するのか?
- OPAとは何か、そしてどのように機能するのか?
- OPAとApache APISIXを使用してRBACを実装する方法
- OPAでポリシーを定義し、登録する方法
- アップストリーム、ルートを作成し、OPAプラグインを有効にする方法
- ユーザーのロールと権限をJWTトークンのペイロードまたはコンシューマーデータから解析する方法
RBACとは?
ロールベースのアクセス制御(RBAC)と属性ベースのアクセス制御(ABAC)は、コンピュータシステム内でリソースへのアクセスを管理し、制御するために使用される2つの一般的なアクセス制御モデルです。RBACは、組織内のユーザーのロールに基づいて権限を割り当てます。RBACでは、ユーザーの機能や責任に基づいてロールが定義され、それらのロールに権限が割り当てられます。ユーザーは1つ以上のロールに割り当てられ、それらのロールに関連する権限を継承します。APIのコンテキストでは、例えば、開発者ロールはAPIリソースを作成および更新する権限を持ち、エンドユーザーロールはAPIリソースを読み取りまたは実行する権限のみを持つかもしれません。
基本的に、RBACはユーザーのロールに基づいて権限を割り当て、ABACはユーザーとリソースに関連する属性に基づいて権限を割り当てます。
RBACでは、ポリシーはユーザーに割り当てられたロール、許可されたアクション、およびそれらのアクションを実行できるリソースの組み合わせによって定義されます。
OPAとは?
OPA(Open Policy Agent)は、分散システム全体でポリシーを強制するためのポリシーエンジンおよびツールセットです。これにより、単一のポイントからポリシーを定義、管理、および強制することができます。ポリシーをコードとして定義することで、OPAはポリシーのレビュー、編集、ロールバックを容易にし、効率的なポリシー管理を可能にします。
OPAは、Regoと呼ばれる宣言型言語を提供し、スタック全体でポリシーを作成および強制することができます。OPAにポリシー決定を要求すると、.rego
ファイルで提供されたルールとデータを使用してクエリを評価し、応答を生成します。クエリ結果はポリシー決定として返されます。OPAはすべてのポリシーと必要なデータをメモリ内キャッシュに保存します。その結果、OPAは迅速に結果を返します。以下は、簡単なOPA Regoファイルの例です:
package example
default allow = false
allow {
input.method == "GET"
input.path =="/api/resource"
input.user.role == "admin"
}
この例では、"example"というパッケージがあり、"allow"というルールを定義しています。"allow"ルールは、入力メソッドが"GET"で、リクエストされたパスが/api/resource
であり、ユーザーのロールが"admin"である場合にリクエストを許可します。これらの条件が満たされると、"allow"ルールは"true"と評価され、リクエストが進行します。
なぜOPAとAPIゲートウェイをRBACに使用するのか?
APIゲートウェイは、APIとAPIコンシューマーを構成および管理するための集中型の場所を提供します。これは、各サービスが内部で認証ロジックを実装することを避けることで、集中型認証ゲートウェイとして使用できます。一方、OPAは認可レイヤーを追加し、コードからポリシーを分離することで、認可に明確な利点をもたらします。この組み合わせにより、APIリソースに対する権限をロールに追加できます。ユーザーは1つ以上のユーザーロールに関連付けられるかもしれません。各ユーザーロールは、RBACリソース(URIパスで定義)に対する一連の権限(GET、PUT、DELETE)を定義します。次のセクションでは、これら2つを使用してRBACを実現する方法を学びます。
OPAとApache APISIXを使用してRBACを実装する方法
Apache APISIXでは、ルートとプラグインを構成してAPIの動作を定義できます。APISIXのopaプラグインを使用して、リクエストをOPAに転送し、意思決定を行うことでRBACポリシーを強制できます。その後、OPAはユーザーのロールと権限に基づいてリアルタイムで認可決定を行います。
Conference APIがあると仮定します。このAPIでは、イベントセッション、トピック、スピーカー情報を取得/編集できます。スピーカーは自分のセッションとトピックのみを読み取ることができ、管理者はさらにセッションとトピックを追加/編集できます。または、参加者はスピーカーのセッションに関するフィードバックを/speaker/speakerId/session/feedback
にPOSTリクエストで送信し、スピーカーは同じURIのGETメソッドをリクエストすることでフィードバックを確認できます。以下の図は、このシナリオ全体を示しています:
- APIコンシューマーは、認証ヘッダーにJWTトークンなどの資格情報を付けてAPIゲートウェイにルートをリクエストします。
- APIゲートウェイは、JWTヘッダーとコンシューマーデータをOPAエンジンに送信します。
- OPAは、.regoファイルで指定したポリシー(ロールと権限)を使用して、コンシューマーがリソースにアクセスする権利を持っているかどうかを評価します。
- OPAの決定が許可された場合、リクエストはアップストリームのConferenceサービスに転送されます。
次に、APISIXをインストールし、構成し、OPAでポリシーを定義します。
前提条件
- Dockerは、コンテナ化されたetcdとAPISIXをインストールするために使用されます。
- curlは、APISIX Admin APIにリクエストを送信するために使用されます。Postmanなどのツールを使用してAPIとやり取りすることもできます。
ステップ1: Apache APISIXをインストールする
APISIXは、以下のクイックスタートスクリプトを使用して簡単にインストールおよび起動できます:
curl -sL https://run.api7.ai/apisix/quickstart | sh
ステップ2: バックエンドサービス(アップストリーム)を構成する
Conference APIのバックエンドサービスにリクエストをルーティングするために、Admin APIを使用してApache APISIXにアップストリームサーバーを追加する必要があります。
curl http://127.0.0.1:9180/apisix/admin/upstreams/1 -X PUT -d '
{
"name":"Conferences API upstream",
"desc":"Register Conferences API as the upstream",
"type":"roundrobin",
"scheme":"https",
"nodes":{
"conferenceapi.azurewebsites.net:443":1
}
}'
ステップ3: APIコンシューマーを作成する
次に、Apache APISIXでユーザー名jack
のコンシューマー(新しいスピーカー)を作成します。これにより、指定されたキーとシークレットを使用して、コンシューマーにjwt-authプラグインが設定されます。これにより、コンシューマーはJSON Web Token(JWT)を使用して認証できるようになります。
curl http://127.0.0.1:9180/apisix/admin/consumers -X PUT -d '
{
"username": "jack",
"plugins": {
"jwt-auth": {
"key": "user-key",
"secret": "my-secret-key"
}
}
}'
ステップ4: JWTトークンを生成するための公開エンドポイントを作成する
また、public-apiプラグインを使用してトークンを生成および署名する新しいルートを設定する必要があります。このシナリオでは、APIゲートウェイは、コンシューマーjackのキーを使用してトークンを作成および検証するIDプロバイダーサーバーとして機能します。IDプロバイダーは、Google、Okta、Keycloak、Ory Hydraなどの他のサードパーティサービスでもかまいません。
curl http://127.0.0.1:9180/apisix/admin/routes/jas -X PUT -d '
{
"uri": "/apisix/plugin/jwt/sign",
"plugins": {
"public-api": {}
}
}'
ステップ5: APIコンシューマーの新しいJWTトークンを要求する
これで、作成した公開エンドポイントからスピーカーJackの新しいトークンを取得できます。以下のcurlコマンドは、Jackの資格情報を使用して新しいトークンを生成し、ペイロードにロールと権限を割り当てます。
curl -G --data-urlencode 'payload={"role":"speaker","permission":"read"}' http://127.0.0.1:9080/apisix/plugin/jwt/sign?key=user-key -i
上記のコマンドを実行すると、トークンが応答として返されます。このトークンをどこかに保存してください。後でこのトークンを使用して新しいAPIゲートウェイエンドポイントにアクセスします。
ステップ6: 新しいプラグイン構成を作成する
このステップでは、APISIXの3つのプラグイン、proxy-rewrite、jwt-auth、opaプラグインを構成します。
curl "http://127.0.0.1:9180/apisix/admin/plugin_configs/1" -X PUT -d '
{
"plugins":{
"jwt-auth":{
},
"proxy-rewrite":{
"host":"conferenceapi.azurewebsites.net"
}
}
}'
proxy-rewrite
プラグインは、conferenceapi.azurewebsites.net
ホストにリクエストをプロキシするように構成されています。- OPA認証プラグインは、http://localhost:8181/v1/data/rbacExampleで実行されているOPAポリシーエンジンを使用するように構成されています。また、APISIXはすべてのコンシューマー関連情報をOPAに送信します。このポリシー
.rego
ファイルは、Opa構成セクションで追加します。
ステップ7: Conferenceセッションのルートを作成する
最後のステップは、Conferences APIのスピーカーセッションの新しいルートを作成することです:
curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT -d '
{
"name":"Conferences API speaker sessions route",
"desc":"Create a new route in APISIX for the Conferences API speaker sessions",
"methods": ["GET", "POST"],
"uris": ["/speaker/*/topics","/speaker/*/sessions"],
"upstream_id":"1",
"plugin_config_id":1
}'
ペイロードには、ルートの名前、説明、メソッド、URI、アップストリームID、プラグイン構成IDなどの情報が含まれています。この場合、ルートは、/speaker/topics
および/speaker/sessions
の2つの異なるURIに対するGETおよびPOSTリクエストを処理するように構成されています。"upstream_id"フィールドは、このルートに対する着信リクエストを処理するアップストリームサービスのIDを指定し、"plugin_config_id"フィールドは、このルートに使用するプラグイン構成のIDを指定します。
ステップ8: OPAなしでセットアップをテストする
これまでに、APISIXがConference APIエンドポイントに着信リクエストを転送し、認可されたAPIコンシューマーのみがアクセスできるようにするために必要なすべての構成を設定しました。これで、APIコンシューマーがエンドポイントにアクセスするたびに、JWTトークンを提供してConferenceバックエンドサービスからデータを取得する必要があります。以下のコマンドを実行して、エンドポイントにアクセスし、リクエストしているドメインアドレスが実際のConferenceサービスではなく、カスタムAPIゲートウェイであることを確認できます:
curl -i http://127.0.0.1:9080/speaker/1/topics -H 'Authorization: {API_CONSUMER_TOKEN}'
ステップ9: OPAサービスを実行する
他の2つのステップは、Dockerを使用してOPAサービスを実行し、そのAPIを使用してポリシー定義をアップロードすることです。これにより、着信リクエストに対する認可ポリシーを評価するために使用できます。
docker run -d --network=apisix-quickstart-net --name opa -p 8181:8181 openpolicyagent/opa:latest run -s
このDockerコマンドは、最新バージョンのOPAイメージのコンテナを実行します。既存のAPISIXネットワークapisix-quickstart-net
上にopa
という名前の新しいコンテナを作成し、ポート8181
を公開します。これにより、APISIXは[http://opa:8181](http://opa:8181)
というアドレスを使用して直接OPAにポリシーチェックリクエストを送信できます。OPAとAPISIXは同じDockerネットワークで実行する必要があります。
ステップ10: ポリシーを定義し、登録する
OPA側の2番目のステップは、APIリソースへのアクセスを制御するために使用されるポリシーを定義することです。これらのポリシーは、アクセスに必要な属性(どのユーザーがどのロールを持っているか)と、それらの属性に基づいて許可または拒否される権限(どのロールがどの権限を持っているか)を定義する必要があります。例えば、以下の構成では、jack
のロールをuser_roles
テーブルで確認するようにOPAに指示しています。この情報は、APISIXがinput.consumer.username
内で送信します。また、JWTペイロードを読み取り、token.payload.permission
を抽出してコンシューマーの権限を確認します。コメントはステップを明確に説明しています。
curl -X PUT '127.0.0.1:8181/v1/policies/rbacExample' \
-H 'Content-Type: text/plain' \
-d 'package rbacExample
# ユーザーロールを割り当てる
user_roles := {
"jack": ["speaker"],
"bobur":["admin"]
}
# ロールの権限を割り当てる
role_permissions := {
"speaker": [{"permission": "read"}],
"admin": [{"permission": "read"}, {"permission": "write"}]
}
# JWTヘルパー関数
bearer_token := t {
t := input.request.headers.authorization
}
# 認証トークンをデコードしてロールと権限を取得
token = {"payload": payload} {
[_, payload, _] := io.jwt.decode(bearer_token)
}
# RBACを実装するロジック
default allow = false
allow {
# ユーザーのロールリストを検索
roles := user_roles[input.consumer.username]
# そのリスト内の各ロールに対して
r := roles[_]
# ロールrの権限リストを検索
permissions := role_permissions[r]
# 各権限に対して
p := permissions[_]
# rに付与された権限がユーザーのリクエストと一致するか確認
p == {"permission": token.payload.permission}
}'
ステップ11: 既存のプラグイン構成をOPAプラグインで更新する
OPAサービスでポリシーを定義したら、ルートの既存のプラグイン構成を更新してOPAプラグインを使用する必要があります。OPAプラグインのpolicy
属性で指定します。
curl "http://127.0.0.1:9180/apisix/admin/plugin_configs/1" -X PATCH -d '
{
"plugins":{
"opa":{
"host":"http://opa:8181",
"policy":"rbacExample",
"with_consumer":true
}
}
}'
ステップ12: OPAを使用してセットアップをテストする
これで、OPAポリシーを使用してすべてのセットアップをテストできます。同じcurlコマンドを実行してAPIゲートウェイエンドポイントにアクセスしようとすると、まずJWTトークンを認証プロセスとしてチェックし、コンシューマーとJWTトークンデータをOPAに送信して、認可プロセスとしてロールと権限を確認します。JWTトークンがないリクエストや許可されていないロールのリクエストは拒否されます。
curl -i http://127.0.0.1:9080/speaker/1/topics -H 'Authorization: {API_CONSUMER_TOKEN}'
結論
この記事では、OPAとApache APISIXを使用してRBACを実装する方法を学びました。APIコンシューマーのロールと権限に基づいてAPIリソースへのアクセスを許可/拒否するための簡単なカスタムポリシーロジックを定義しました。また、このチュートリアルでは、APISIXから送信されたJWTトークンのペイロードまたはコンシューマーオブジェクトからAPIコンシューマー関連情報をポリシーファイルで抽出する方法も示しました。
関連リソース
- Apache APISIX Authorization Policy: Protect Your APIs
- Centralized Authentication with Apache APISIX Plugins
- Manage API Consumers with Apache APISIX