
TL; DR: オープンソースのアクセス制御システムである Keycloak の説明、内部構造の分析、構成の詳細について説明します。
はじめにと重要なアイデア
この記事では、Kubernetes 上に Keycloak クラスターをデプロイする際に留意すべき基本的な考え方について説明します。
Keycloak について詳しく知りたい場合は、記事の最後にあるリンクを参照してください。 より実践に没頭するために、勉強することができます この記事の主なアイデアを実装するモジュールが含まれています (起動ガイドがあり、この記事ではデバイスと設定の概要を説明します) 約。 翻訳者).
KeycloakはJavaで書かれ、アプリケーションサーバー上に構築された包括的なシステムです。 。 つまり、アプリケーション ユーザーにフェデレーション機能と SSO (シングル サインオン) 機能を提供する承認フレームワークです。
ぜひ公式を読んでください。 または 詳細な理解のために。
Keycloakの起動
Keycloakの実行にはXNUMXつの永続データソースが必要です。
- ユーザー情報などの確立されたデータを保存するために使用されるデータベース
- Datagrid キャッシュは、データベースからデータをキャッシュするだけでなく、ユーザー セッションなど、有効期間が短く、頻繁に変更されるメタデータを保存するために使用されます。 実装済み これは通常、データベースよりも大幅に高速です。 ただし、いずれの場合も、Infinispan に保存されたデータは一時的なものであり、クラスターの再起動時にどこにも保存する必要はありません。
Keycloak は XNUMX つの異なるモードで動作します。
- ノーマル - 唯一のプロセス、ファイル経由で設定 スタンドアロン.xml
- 通常のクラスター (高可用性オプション) - すべてのプロセスは同じ構成を使用する必要があり、手動で同期する必要があります。 設定はファイルに保存されます スタンドアロン-ha.xmlさらに、データベースとロード バランサーへの共有アクセスを作成する必要があります。
- ドメインクラスタ — 通常モードでクラスターを起動することは、構成が変更されるたびに各クラスター ノードですべての変更を行う必要があるため、クラスターが成長するにつれてすぐに日常的で退屈な作業になります。 ドメイン操作モードでは、共有ストレージの場所を設定し、構成を公開することで、この問題を解決します。 これらの設定はファイルに保存されます ドメイン.xml
- データセンター間のレプリケーション — 複数のデータセンターのクラスター(ほとんどの場合、地理的に異なる場所)でKeycloakを実行する場合。 このオプションでは、各データセンターに Keycloak サーバーの独自のクラスターが存在します。
この記事では、XNUMX 番目のオプションについて詳しく検討します。 通常のクラスターまた、Kubernetes でこれら XNUMX つのオプションを実行することは理にかなっているため、データセンター間のレプリケーションのトピックについても少し触れます。 幸いなことに、Kubernetes では複数のポッド (Keycloak ノード) の設定を同期することに問題はありません。 ドメインクラスタ それほど難しいことではありません。
また、この言葉にも注意してください 集まる この記事の残りの部分は、連携して動作する Keycloak ノードのグループにのみ適用されます。Kubernetes クラスターについて言及する必要はありません。
通常のKeycloakクラスター
このモードで Keycloak を実行するには、次のものが必要です。
- 外部共有データベースを構成する
- ロードバランサーをインストールする
- IP マルチキャストをサポートする内部ネットワークがある
外部データベースの設定については、この記事の目的ではないため説明しません。 どこかに稼働中のデータベースがあり、そこへの接続ポイントがあると仮定しましょう。 このデータを環境変数に追加するだけです。
Keycloakがフェイルオーバー(HA)クラスターでどのように動作するかをよりよく理解するには、すべてがWildflyのクラスタリング機能にどの程度依存しているかを知ることが重要です。
Wildfly はいくつかのサブシステムを使用しており、その一部はロード バランサーとして使用され、一部はフォールト トレランスのために使用されます。 ロード バランサは、クラスタ ノードが過負荷になった場合でもアプリケーションの可用性を確保し、フォールト トレランスは、一部のクラスタ ノードに障害が発生した場合でもアプリケーションの可用性を確保します。 これらのサブシステムの一部は次のとおりです。
-
mod_cluster: HTTP ロード バランサとして Apache と連携して動作し、デフォルトで TCP マルチキャストに依存してホストを検索します。 外部バランサーへの置き換えも可能です。 -
infinispan: JGroups チャネルをトランスポート層として使用する分散キャッシュ。 さらに、HotRod プロトコルを使用して外部 Infinispan クラスターと通信し、キャッシュの内容を同期できます。 -
jgroups: JGroups チャネルに基づく高可用性サービスのグループ通信サポートを提供します。 名前付きパイプを使用すると、クラスター内のアプリケーション インスタンスをグループに接続できるため、通信の信頼性、秩序性、障害に対する感度などの特性が得られます。
ロードバランサ
Kubernetes クラスターにバランサーをイングレス コントローラーとしてインストールする場合は、次の点に留意することが重要です。
Keycloakは、HTTP経由で認証サーバーに接続するクライアントのリモート・アドレスがクライアント・コンピューターの実際のIPアドレスであると想定します。 バランサーとイングレス設定では HTTP ヘッダーを正しく設定する必要があります X-Forwarded-For и X-Forwarded-Proto、元のタイトルも保存します HOST。 最新バージョン ingress-nginx (> 0.22.0)
フラグをアクティブにする proxy-address-forwarding 環境変数を設定することによって PROXY_ADDRESS_FORWARDING в true Keycloak は、Keycloak がプロキシの背後で動作していることを理解できます。
有効にする必要もあります スティッキーセッション イングレスで。 Keycloakは分散Infinispanキャッシュを使用して、現在の認証セッションおよびユーザーセッションに関連付けられたデータを保存します。 キャッシュはデフォルトで単一の所有者で動作します。つまり、その特定のセッションはクラスター内の一部のノードに保存され、他のノードはそのセッションにアクセスする必要がある場合にリモートでクエリを実行する必要があります。
具体的には、ドキュメントに反して、Cookie という名前のセッションをアタッチしても機能しませんでした。
AUTH_SESSION_ID。 Keycloakにはリダイレクトループがあるため、スティッキーセッションには別のCookie名を選択することをお勧めします。
Keycloakは、最初に応答したノードの名前も付加します。 AUTH_SESSION_ID高可用性バージョンの各ノードは同じデータベースを使用するため、それぞれ トランザクションを管理するための個別の一意のノード識別子。 入れるのがオススメです JAVA_OPTS パラメータ jboss.node.name и jboss.tx.node.id 各ノードに一意です。たとえば、ポッドの名前を入力できます。 ポッド名を入力する場合は、jboss 変数の 23 文字制限を忘れないでください。そのため、Deployment ではなく StatefulSet を使用することをお勧めします。
別の rake - ポッドが削除または再起動されると、そのキャッシュは失われます。 これを考慮すると、キャッシュのコピーが残るように、すべてのキャッシュのキャッシュ所有者の数を少なくとも XNUMX に設定する価値があります。 解決策は実行することです ポッドを起動するときに、ポッドをディレクトリに配置します /opt/jboss/startup-scripts コンテナ内:
スクリプトの内容
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo * Setting CACHE_OWNERS to "${env.CACHE_OWNERS}" in all cache-containers
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
run-batch
stop-embedded-server
次に、環境変数の値を設定します CACHE_OWNERS 必要なものまで。
IPマルチキャストをサポートするプライベートネットワーク
Weavenet を CNI として使用する場合、マルチキャストはすぐに機能し、Keycloak ノードは起動するとすぐに相互に認識されます。
Kubernetes クラスターで IP マルチキャスト サポートがない場合は、他のプロトコルと連携してノードを検索するように JGroups を構成できます。
最初のオプションは使用することです KUBE_DNS使用する headless service Keycloak ノードを見つけるには、ノードの検索に使用されるサービスの名前を JGroups に渡すだけです。
別のオプションは、メソッドを使用することです KUBE_PING、API と連携してノードを検索します (設定する必要があります) serviceAccount 権利付き list и get、そしてこれと連携するようにポッドを設定します serviceAccount).
JGroups がノードを検索する方法は、環境変数を設定することで構成されます。 JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES。 のために KUBE_PING 質問してポッドを選択する必要があります namespace и labels.
️ マルチキャストを使用し、XNUMX つの Kubernetes クラスター (名前空間内に XNUMX つとしましょう) で XNUMX つ以上の Keycloak クラスターを実行する場合
production、 XNUMX番目 -staging) - XNUMX つの Keycloak クラスターのノードは別のクラスターに参加できます。 変数を設定して、各クラスターに必ず一意のマルチキャスト アドレスを使用してください。jboss.default.multicast.addressиjboss.modcluster.multicast.addressвJAVA_OPTS.
データセンター間のレプリケーション

Связь
Keycloakは、Keycloakノードで構成されるKeycloackクラスターが配置されているデータセンターごとに、複数の個別のInfinispanキャッシュクラスターを使用します。 ただし、異なるデータセンターの Keycloak ノード間に違いはありません。
Keycloak ノードは、データセンター間の通信に外部 Java Data Grid (Infinispan サーバー) を使用します。 通信はプロトコルに従って動作します .
Infinispan キャッシュは属性を使用して構成する必要があります remoteStore、データをリモート (別のデータセンター、 約。 翻訳者) キャッシュ。 JDG サーバー間には個別の infinispan クラスターが存在するため、サイト上の JDG1 にデータが保存されます。 site1 サイト上の JDG2 にレプリケートされます site2.
最後に、受信側の JDG サーバーはクライアント接続を通じてクラスターの Keycloak サーバーに通知します。これは HotRod プロトコルの機能です。 Keycloak ノード site2 Infinispan キャッシュを更新すると、特定のユーザー セッションが Keycloak ノードでも利用できるようになります。 site2.
一部のキャッシュでは、バックアップを作成せず、Infinispan サーバーを介したデータの書き込みを完全に回避することもできます。 これを行うには、設定を削除する必要があります remote-store 特定の Infinispan キャッシュ (ファイル内) スタンドアロン-ha.xml)、その後、いくつかの具体的な replicated-cache Infinispan サーバー側でも必要なくなります。
キャッシュのセットアップ
KeycloakにはXNUMX種類のキャッシュがあります。
-
地元。 これはデータベースの隣に配置され、データベースの負荷を軽減し、応答待ち時間を短縮するのに役立ちます。 このタイプのキャッシュには、レルム、クライアント、ロール、およびユーザーのメタデータが保存されます。 このタイプのキャッシュは、キャッシュがKeycloakクラスターの一部であってもレプリケートされません。 キャッシュ内のエントリが変更されると、変更に関するメッセージがクラスタ内の残りのサーバーに送信され、その後エントリはキャッシュから除外されます。 詳細参照
work手順の詳細については、以下を参照してください。 -
複製されました。 ユーザー セッション、オフライン トークンを処理し、ログイン エラーを監視してパスワード フィッシングの試みやその他の攻撃を検出します。 これらのキャッシュに保存されるデータは一時的なもので、RAM にのみ保存されますが、クラスター全体で複製できます。
インフィニスパンキャッシュ
セッション - Keycloakの概念、と呼ばれる個別のキャッシュ authenticationSessions、特定のユーザーのデータを保存するために使用されます。 これらのキャッシュからのリクエストは通常、アプリケーションではなくブラウザとKeycloakサーバーによって必要とされます。 ここでスティッキー セッションへの依存が関係し、アクティブ/アクティブ モードの場合でも、そのようなキャッシュ自体をレプリケートする必要はありません。
アクショントークン。 もう XNUMX つの概念は、通常、たとえばユーザーがメールで非同期に何かを行う必要がある場合など、さまざまなシナリオで使用されます。 たとえば、手続き中に forget password キャッシュ actionTokens 関連するトークンのメタデータを追跡するために使用されます。たとえば、トークンはすでに使用されており、再度アクティブ化することはできません。 このタイプのキャッシュは通常、データセンター間で複製する必要があります。
保存されたデータのキャッシュとエージング データベースの負荷を軽減するために機能します。 この種のキャッシュによりパフォーマンスは向上しますが、明らかな問題が追加されます。 XNUMXつのKeycloakサーバーがデータを更新する場合、他のサーバーがキャッシュ内のデータを更新できるように、他のサーバーに通知する必要があります。 Keycloakはローカルキャッシュを使用します realms, users и authorization データベースからデータをキャッシュするため。
別途キャッシュもあります work、すべてのデータセンターにわたってレプリケートされます。 それ自体はデータベースからのデータを保存しませんが、データの老化に関するメッセージをデータセンター間のクラスター ノードに送信する役割を果たします。 つまり、データが更新されるとすぐに、Keycloak ノードはそのデータセンター内の他のノードだけでなく、他のデータセンター内のノードにもメッセージを送信します。 このようなメッセージを受信した後、各ノードはローカル キャッシュ内の対応するデータをクリアします。
ユーザーセッション。 名前付きキャッシュ sessions, clientSessions, offlineSessions и offlineClientSessions、通常はデータセンター間でレプリケートされ、ユーザーがブラウザーでアクティブである間にアクティブなユーザー セッションに関するデータを保存するために機能します。 これらのキャッシュは、エンド ユーザーからの HTTP リクエストを処理するアプリケーションと連携して動作するため、スティッキー セッションに関連付けられ、データ センター間で複製する必要があります。
ブルートフォース保護。 キャッシュ loginFailures ユーザーが間違ったパスワードを入力した回数など、ログイン エラー データを追跡するために使用されます。 このキャッシュの複製は管理者の責任です。 ただし、正確な計算を行うには、データセンター間のレプリケーションを有効にする価値があります。 しかしその一方で、このデータをレプリケートしないとパフォーマンスが向上しますが、この問題が発生するとレプリケーションがアクティブ化されなくなる可能性があります。
Infinispan クラスターをロールアウトするときは、設定ファイルにキャッシュ定義を追加する必要があります。
<replicated-cache-configuration name="keycloak-sessions" mode="ASYNC" start="EAGER" batching="false">
</replicated-cache-configuration>
<replicated-cache name="work" configuration="keycloak-sessions" />
<replicated-cache name="sessions" configuration="keycloak-sessions" />
<replicated-cache name="offlineSessions" configuration="keycloak-sessions" />
<replicated-cache name="actionTokens" configuration="keycloak-sessions" />
<replicated-cache name="loginFailures" configuration="keycloak-sessions" />
<replicated-cache name="clientSessions" configuration="keycloak-sessions" />
<replicated-cache name="offlineClientSessions" configuration="keycloak-sessions" />
Keycloakクラスターを開始する前に、Infinispanクラスターを構成して開始する必要があります
次に、設定する必要があります remoteStore Keycloakキャッシュ用。 これを行うには、変数を設定するために使用される前のものと同様に実行されるスクリプトで十分です。 CACHE_OWNERS、ファイルに保存してディレクトリに置く必要があります /opt/jboss/startup-scripts:
スクリプトの内容
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo *** Update infinispan subsystem ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)
echo ** Add remote socket binding to infinispan server **
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})
echo ** Update replicated-cache work element **
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=work,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:write-attribute(name=statistics-enabled,value=true)
echo ** Update distributed-cache sessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=sessions,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=statistics-enabled,value=true)
echo ** Update distributed-cache offlineSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=offlineSessions,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Update distributed-cache clientSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=clientSessions,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Update distributed-cache offlineClientSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=offlineClientSessions,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Update distributed-cache loginFailures element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=loginFailures,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=statistics-enabled,value=true)
echo ** Update distributed-cache actionTokens element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
cache=actionTokens,
remote-servers=["remote-cache"],
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=statistics-enabled,value=true)
echo ** Update distributed-cache authenticationSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)
echo *** Update undertow subsystem ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)
run-batch
stop-embedded-server
忘れずにインストールしてください JAVA_OPTS Keycloak ノードで HotRod を実行するには: remote.cache.host, remote.cache.port とサービス名 jboss.site.name.
リンクと追加ドキュメント
この記事は従業員によって翻訳され、Habr 向けに作成されました。 — 実践スペシャリストによる集中コース、ビデオコース、企業トレーニング (Kubernetes、DevOps、Docker、Ansible、Ceph、SRE)
出所: habr.com
