CLOVER🍀

That was when it all began.

Infinispan Server(Hot Rod)で、認証・認可設定を行う

これは、なにをしたくて書いたもの?

Infinispan Serverを使った、認証・認可まわりについて、ちゃんと設定したことがなかった気がするので、少しやってみようかなと。

ServerNGになって、ユーザーを追加するためのスクリプトも変わったりしているので、確認の機会としても良いでしょう。

認証・認可といっても、今回はHot Rodでのケースにフォーカスしてみていきます。

Infinispan Server(Hot Rod)の認証・認可まわりの設定

なんの話かというと、Infinispan Serverへアクセスする際、認証・認可をどのように設定するか、という話ですね。

Infinispan Serverを使った認証・認可まわりの設定は、大きく以下のことをする必要があります。

  • Infinispan Server
    • Cache Containerの認可設定
    • Cacheの認可設定
    • Security Realmの設定
    • Endpointの認証設定
  • Infinispan Client
    • 認証設定

今回は、上記のRealmまわりを除いて扱っていきます。また、SSL/TLSや認証方法も絞ったり外したりします。

認証・認可に関する、Infinispan Serverのドキュメントはこちら。

Securing Infinispan Servers

ざっくり、認証、認可まわりの設定の流れを書いていきます。

ユーザーはInfinispan Server組み込みのプロパティファイルで管理する場合、コマンドでユーザーを作成し、所属するグループを指定します。

Property Realms

ここで、ユーザーが属するグループに対して、権限を割り当てます。
※後述しますが、1番シンプルな権限の管理方法の場合、です

割り当てられる権限は、設定要素「infinispan/cache-container/security/authorization/role」で指定していきます。

一覧は、以下に記載があります。

urn:infinispan:config:10.1

  • LIFECYCLE … Cacheのライフサイクルをコントロールすることを許可(たとえば、Cacheの起動・停止)
  • READ … Cacheからのデータの読み込みを許可
  • WRITE … Cacheへのデータの書き込みを許可
  • EXEC … Cache上でのタスクの実行を許可(たとえば、Executorsなど)
  • LISTEN … CacheへのListenerのアタッチを許可
  • BULK_READ … バルクRead操作を許可(たとえば、Cacheの全キーを取得するなど)
  • BULK_WRITE … バルクWrite操作を許可(たとえば、Cacheをクリアするなど)
  • ADMIN … キャッシュに対する管理上の操作を許可する
  • ALL … すべての権限をまとめた
  • ALL_READ … Readに関する権限をまとめたもの(READとBULK_READ)
  • ALL_WRITE … Writeに関する権限をまとめたもの(WRITEとBULK_WRITE)
  • NONE … なにも権限を持っていないことを表す権限

ここで作成したユーザーを使って、Hot Rod Clientからアクセスします。

Configuring Authentication Mechanisms for Hot Rod Clients

この時、認証メカニズムの指定が必要です。

Hot Rod Endpoint Authentication Mechanisms

認証メカニズムは、ざっと以下の種類があります(*がある部分は細かい種類を省略しています)。

  • PLAIN
  • DIGEST-*
  • SCRAM-*
  • GSSAPI
  • GS2-KRB5
  • EXTERNAL
  • OAUTHBEARER

認証時にどのようなメカニズムをクライアントが指定できるかは、Infinspan Server側のEndpointで設定します。

Setting Up Hot Rod Authentication

今回は、いろいろあってPLAINとDIGEST-*を試しています(ソースコードとして載せているのはPLAINだけですが)。

では、設定していってみましょう。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.6 2020-01-14
OpenJDK Runtime Environment (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1)
OpenJDK 64-Bit Server VM (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.6, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.3.0-40-generic", arch: "amd64", family: "unix"

Infinispan Serverは、10.1.3.Finalを使用し、動作しているホストのIPアドレスは172.17.0.2とします。

準備

クライアント側で使用する、Maven依存関係とプラグインはこちら。

    <dependencies>
        <dependency>
          <groupId>org.infinispan</groupId>
          <artifactId>infinispan-client-hotrod</artifactId>
          <version>10.1.3.Final</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.6.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.6.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.15.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

クライアントは、infinispan-client-hotrodを使用します。あとは、JUnit 5とAssertJをテストコード用に追加して、合わせてMaven Surefire Pluginも設定。

また、サーバー側については、デフォルトのInfinispan Serverの設定ファイル「server/conf/infinispan.xml」を確認しておきましょう。
特に関連しそうな箇所を見ておきます。

Cache Container/Cacheの設定(Cacheはまだありませんが)。

   <cache-container name="default" statistics="true">
      <transport cluster="${infinispan.cluster.name}" stack="${infinispan.cluster.stack:tcp}" node-name="${infinispan.node.name:}"/>
      <metrics gauges="true" histograms="true"/>
   </cache-container>

Security Realmの設定(今回は触りません)。

      <security>
         <security-realms>
            <security-realm name="default">
               <!-- Uncomment to enable TLS on the realm -->
               <!-- server-identities>
                  <ssl>
                     <keystore path="application.keystore" relative-to="infinispan.server.config.path"
                               keystore-password="password" alias="server" key-password="password"
                               generate-self-signed-certificate-host="localhost"/>
                  </ssl>
               </server-identities-->
               <properties-realm groups-attribute="Roles">
                  <user-properties path="users.properties" relative-to="infinispan.server.config.path" plain-text="true"/>
                  <group-properties path="groups.properties" relative-to="infinispan.server.config.path" />
               </properties-realm>
            </security-realm>
         </security-realms>
      </security>

Endpointの設定。

      <endpoints socket-binding="default" security-realm="default">
         <hotrod-connector name="hotrod"/>
         <rest-connector name="rest"/>
      </endpoints>

テストコードの雛形

Infinispan Serverへのアクセスは、Hot Rod Clientを使ったテストコードで見ていくことにします。

雛形は、こんな感じで。
src/test/java/org/littlewings/infinispan/authentication/AuthnAuthzTest.java

package org.littlewings.infinispan.authentication;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.Configuration;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class AuthnAuthzTest {

    // ここに、テストを書く!
}

まずは、認証・認可なしで

最初は、認証・認可に関する設定を行わずに確認してみましょう。

ひとつ、シンプルなCacheを追加。
※まだCLIを使いこなせいないので、今回は手で作成…(ServerNGで、このあたりができるかどうかもわかっていません)

   <cache-container name="default" statistics="true">
      <transport cluster="${infinispan.cluster.name}" stack="${infinispan.cluster.stack:tcp}" node-name="${infinispan.node.name:}"/>
      <metrics gauges="true" histograms="true"/>

      <distributed-cache name="plainCache"/>
   </cache-container>

1度、Infinispan Serverを再起動します。

このCacheを使うテストコードを書いてみます。

    @Test
    public void plainCache() {
        Configuration configuration =
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .build();

        RemoteCacheManager manager = new RemoteCacheManager(configuration);

        try {
            RemoteCache<String, String> cache = manager.getCache("plainCache");

            cache.put("key1", "value1");
            assertThat(cache.get("key1")).isEqualTo("value1");
            assertThat(cache.size()).isEqualTo(1L);

            cache.clear();
            assertThat(cache.size()).isEqualTo(0L);
        } finally {
            manager.stop();
        }
    }

まあ、こちらはふつうに動きます。

Infinispan Serverに、認証・認可設定を行う

では、Infinispan Serverに認証・認可まわりの設定を行っていきます。

まず、(設定がわかりやすいので)Cacheに対する認可設定を行いましょう。

以下のドキュメントを見つつ。

Cache Authorization Configuration

以下のように、「cache-container/security/authorization」要素内にroleを作成して、「cache-container/cache/security/authorization」要素から
参照するように設定します。

   <cache-container name="default" statistics="true">
      <transport cluster="${infinispan.cluster.name}" stack="${infinispan.cluster.stack:tcp}" node-name="${infinispan.node.name:}"/>
      <metrics gauges="true" histograms="true"/>

      <security>
         <authorization>
            <identity-role-mapper/>
            <role name="admin" permissions="ALL"/>
            <role name="reader-writer" permissions="READ WRITE"/>
            <role name="writer" permissions="WRITE"/>
            <role name="reader" permissions="READ"/>
         </authorization>
      </security>

      <distributed-cache name="plainCache"/>

      <distributed-cache name="securedCache">
         <security>
            <authorization roles="admin reader-writer writer reader"/> 
         </security>
      </distributed-cache>
   </cache-container>

ロールの設定はこちらですね。

      <security>
         <authorization>
            <identity-role-mapper/>
            <role name="admin" permissions="ALL"/>
            <role name="reader-writer" permissions="READ WRITE"/>
            <role name="writer" permissions="WRITE"/>
            <role name="reader" permissions="READ"/>
         </authorization>
      </security>

ロール名は任意で、ロールに対してどのような権限を割り当てるかをpermissions属性で指定します。複数の権限を指定する場合は、
スペース区切りで記載します。

作成したロールを、Cacheに対して割り当てます。「security/authorization」要素のrolesで指定します。複数のroleを指定する場合は、
スペース区切りで指定します。

      <distributed-cache name="securedCache">
         <security>
            <authorization roles="admin reader-writer writer reader"/> 
         </security>
      </distributed-cache>

「identity-role-mapper」というのは、ロールとユーザーの紐付けに関する設定です。

PrincipalRoleMapper (Infinispan JavaDoc All 10.1.3.Final API)

「identity-role-mapper」は1番シンプルな設定で、ロール名をPrincipalの名前(グループ名)と紐付けます。

IdentityRoleMapper (Infinispan JavaDoc All 10.1.3.Final API)

他には、「common-name-role-mapper」や「cluster-role-mapper」、自分でPrincipalRoleMapper自体を指定する「custom-role-mapper」が
指定できます。

指定方法自体はこちら。

urn:infinispan:config:10.1

実装は、こちら。

https://github.com/infinispan/infinispan/blob/10.1.3.Final/core/src/main/java/org/infinispan/security/mappers/IdentityRoleMapper.java

https://github.com/infinispan/infinispan/blob/10.1.3.Final/core/src/main/java/org/infinispan/security/mappers/CommonNameRoleMapper.java

https://github.com/infinispan/infinispan/blob/10.1.3.Final/core/src/main/java/org/infinispan/security/mappers/ClusterRoleMapper.java

というわけで、今回は「identity-role-mapper」を使っているので、ユーザーの所属するグループがそのままロールの名前になります。

次に、ユーザーを作成します。今回は、Infinispan Serverでデフォルトで使える、プロパティファイルでのユーザー・グループの管理とします。

Adding Users to Property Realms

先ほど記載したとおり、グループ名がそのままロール名になるため、ロールに合わせた形でグループを管理していけばOKです。

以下のスクリプトを使うことになります。

$ bin/user-tool.sh -h
Infinispan User Tool 10.1.3.Final (Turia)
Copyright (C) Red Hat Inc. and/or its affiliates and other contributors
License Apache License, v. 2.0. http://www.apache.org/licenses/LICENSE-2.0
Usage:
  -u, --user=<name>                  Specifies the name of the user to add.
  -p, --password=<password>          Specifies the password for the user.
  -d, --digest                       Store passwords in digest format (WARN: works only with DIGEST-MD5/Digest authentication).
  -g, --groups=<group1[,group2...]>  Adds the user to a comma-separated list of groups.
  -f, --users-file=<file>            Sets the name of the users properties file relative to the server configuration path. Defaults to `users.properties`.
  -w, --groups-file=<file>           Sets the name of the groups properties file relative to the server configuration path. Defaults to `groups.properties`.
  -r, --realm=<realm>                Sets the name of the realm. Defaults to `default`.
  -s, --server-root=<path>           Specifies the root path for the server. Defaults to `server`.
  -b, --batch-mode                   Do not ask for confirmation when overwriting existing users.
  -h, --help                         Displays usage information and exits.
  -v, --version                      Displays version information and exits.

先ほど作成したロールを、グループにマッピングしてユーザーを登録していきます。先にヘルプで表示したとおり、「-u」でユーザー名、
「-p」でパスワード、「-g」で所属するグループを指定します。グループは、カンマ区切りで複数指定することができます。

# 「admin」グループに属するユーザー
$ bin/user-tool.sh -u admin -p password -g admin

# 「reader」グループに属するユーザー
$ bin/user-tool.sh -u read-only-user -p password -g reader

# 「writer」グループに属するユーザー
$ bin/user-tool.sh -u write-only-user -p password -g writer

# 「reader-writer」グループに属するユーザー
$ bin/user-tool.sh -u read-write-user-simple -p password -g reader-writer

# 「reader」と「writer」グループの両方に属するユーザー
$ bin/user-tool.sh -u read-write-user-multi -p password -g reader,writer

ところで、ここで作成したユーザーおよびグループは、Security Realmに記載してあった以下のファイルに追加されます。

               <properties-realm groups-attribute="Roles">
                  <user-properties path="users.properties" relative-to="infinispan.server.config.path" plain-text="true"/>
                  <group-properties path="groups.properties" relative-to="infinispan.server.config.path" />
               </properties-realm>

ちょっとファイルを見てみましょう。
server/conf/groups.properties

#Sun Mar 01 03:45:38 UTC 2020
write-only-user=writer
read-write-user-multi=reader,writer
read-write-user-simple=reader-writer
read-only-user=reader
admin=admin

server/conf/users.properties

#$REALM_NAME=default$
#Sun Mar 01 03:45:38 UTC 2020
write-only-user=password
read-write-user-multi=password
read-write-user-simple=password
read-only-user=password
admin=password

とってもシンプル。

その他のRealmを使いたい場合は、こちらを参照して設定していくようですが、今回はパスです。

Defining Infinispan Server Security Realms

最後に、Hot Rod Endpointの設定を行います。こちらで認証メカニズムの設定を行います。

Setting Up Hot Rod Authentication

ドキュメント通りですが、以下のように設定。

      <endpoints socket-binding="default" security-realm="default">
         <hotrod-connector name="hotrod">
             <authentication>
                <sasl mechanisms="SCRAM-SHA-512 SCRAM-SHA-384 SCRAM-SHA-256 
                                  SCRAM-SHA-1 DIGEST-SHA-512 DIGEST-SHA-384
                                  DIGEST-SHA-256 DIGEST-SHA DIGEST-MD5 PLAIN"
                      server-name="infinispan" 
                      qop="auth"/> 
             </authentication>
         </hotrod-connector>
         <rest-connector name="rest"/>
      </endpoints>

このmechanismsで列挙したのが、クライアントから指定する認証メカニズムで、クライアントはこの中からひとつ選んで使うことに
なります。

server-nameは、クライアントから見た時のサーバー名で、クライアントが認識しているサーバー名と一致させておくべきだとか。
※接続先のサーバーリストとして指定するものとはまた別で、認証に特化したものです

qopというのは、Quality of Protectionの略です。

Ldapwiki: Quality of Protection

HTTP クライアントを作ってみよう(6) - Digest 認証編 -

security-reamについては、今回は触らないと記載したSecurity Reamの設定と参照関係にありそうな気がしますが、今回は追って
いません。クライアント側でもRealmの指定はできそうなので、関連がまだ見えていません…。

ここまで設定したら、Infinispan Serverを再起動します。

ちなみに、このHot Rod Endpointの設定をした時点で、Cacheへのアクセスに認証・認可設定が必要になってしまい、最初に書いた
Cacheとコードは使えなくなるのでテストは無効にしておきます。

    @Disabled
    @Test
    public void plainCache() {

そのままにしておくと、こんな感じにエラーになります。どちらかというと、認可で弾かれているようですね。
Cacheの「authorization」要素の「enabled」属性をfalseにしても、この挙動は変わりません。

org.infinispan.client.hotrod.exceptions.HotRodClientException:Request for messageId=84 returned server error (status=0x85): java.lang.SecurityException: ISPN006017: Unauthorized 'PUT' operation

Endpointに認証設定を入れると、Cacheには認可設定が必須になるのだなぁ、という感じでしょうかね。

Hot Rod Clientを使って認証設定を行う

では、認証設定を使ったコードを書いていってみます。

認証の確認

まずは簡単に、認証できるパターンとそうでないパターンを。

    @Test
    public void authentication() {
        RemoteCacheManager manager1 = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("admin")
                        .password("password")
                        .build());

        try {
            assertThat(manager1.getCache("securedCache")).isNotNull();
        } finally {
            manager1.stop();
        }

        //////////////////////

        RemoteCacheManager manager2 = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("unknown")
                        .password("bad-password")
                        .build());

        try {
            assertThatThrownBy(() -> manager2.getCache("securedCache"))
                    .hasMessage("org.infinispan.client.hotrod.exceptions.HotRodClientException:Request for messageId=80 returned server error (status=0x84): javax.security.sasl.SaslException: ELY05013: Authentication mechanism password not verified");
        } finally {
            manager2.stop();
        }
    }

最初に、「admin」ユーザーで認証を行うコードを書いています。認証メカニズムは、「PLAIN」を選択。認可設定を行ったCacheを、
問題なく取得することが確認できます。

        RemoteCacheManager manager1 = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("admin")
                        .password("password")
                        .build());

        try {
            assertThat(manager1.getCache("securedCache")).isNotNull();
        } finally {
            manager1.stop();
        }

反対に、認証に失敗するように存在しないユーザーを指定すると、Cacheの取得時に失敗します。

        RemoteCacheManager manager2 = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("unknown")
                        .password("bad-password")
                        .build());

        try {
            assertThatThrownBy(() -> manager2.getCache("securedCache"))
                    .hasMessage("org.infinispan.client.hotrod.exceptions.HotRodClientException:Request for messageId=80 returned server error (status=0x84): javax.security.sasl.SaslException: ELY05013: Authentication mechanism password not verified");
        } finally {
            manager2.stop();
        }

どうやら、認証についてもCacheを取得する時がポイントになるようですね。

認可の確認1

次に、認可についてを見ていきましょう。

「read-only-user」と「write-only-user」の読み込み、書き込みのどちらかしかできないユーザーを使って確認。

    @Test
    public void readWriteOnlyUser() {
        RemoteCacheManager manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("read-only-user")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            assertThatThrownBy(() -> cache.put("key1", "value1"))
                    .hasMessage("java.lang.SecurityException: ISPN000287: Unauthorized access: subject 'Subject with principal(s): [read-only-user, RolePrincipal{name='reader'}, InetAddressPrincipal [address=172.17.0.1/172.17.0.1]]' lacks 'WRITE' permission");
        } finally {
            manager.stop();
        }

        //////////////////////

        manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("write-only-user")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            cache.put("key1", "value1");
            assertThatThrownBy(() -> cache.get("key1"))
                    .hasMessage("java.lang.SecurityException: ISPN000287: Unauthorized access: subject 'Subject with principal(s): [write-only-user, RolePrincipal{name='writer'}, InetAddressPrincipal [address=172.17.0.1/172.17.0.1]]' lacks 'READ' permission");
        } finally {
            manager.stop();
        }

        //////////////////////

        manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("read-only-user")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            assertThat(cache.get("key1")).isEqualTo("value1");
        } finally {
            manager.stop();
        }

        //////////////////////

        manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("admin")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            cache.clear();
        } finally {
            manager.stop();
        }
    }

まず、READ権限しか持たない「read-only-user」では、Cache自体の取得はできるものの、書き込みができないことを確認。

        RemoteCacheManager manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("read-only-user")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            assertThatThrownBy(() -> cache.put("key1", "value1"))
                    .hasMessage("java.lang.SecurityException: ISPN000287: Unauthorized access: subject 'Subject with principal(s): [read-only-user, RolePrincipal{name='reader'}, InetAddressPrincipal [address=172.17.0.1/172.17.0.1]]' lacks 'WRITE' permission");
        } finally {
            manager.stop();
        }

「write-only-user」であれば、WRITE権限があるので書き込みが可能です。ですが、READ権限がないので、登録したエントリの
取得には失敗します。

        manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("write-only-user")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            cache.put("key1", "value1");
            assertThatThrownBy(() -> cache.get("key1"))
                    .hasMessage("java.lang.SecurityException: ISPN000287: Unauthorized access: subject 'Subject with principal(s): [write-only-user, RolePrincipal{name='writer'}, InetAddressPrincipal [address=172.17.0.1/172.17.0.1]]' lacks 'READ' permission");
        } finally {
            manager.stop();
        }

Cacheに登録したエントリの取得は、「read-only-user」で。

        manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("read-only-user")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            assertThat(cache.get("key1")).isEqualTo("value1");
        } finally {
            manager.stop();
        }

最後にCacheエントリのクリアをしているのですが、これにはBULK_WRITE権限が必要なので、今回は「admin」ユーザーで行っています。

        manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("admin")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            cache.clear();
        } finally {
            manager.stop();
        }
認可の確認2

続いて、READ権限とWRITE権限の両方を持たせたロール(グループ)に所属するユーザーで、READもWRITEもできることを確認。

    @Test
    public void readWriteRole1() {
        RemoteCacheManager manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("read-write-user-simple")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            cache.put("key1", "value1");
            assertThat(cache.get("key1")).isEqualTo("value1");
        } finally {
            manager.stop();
        }

        //////////////////////

        manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("admin")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            cache.clear();
        } finally {
            manager.stop();
        }
    }

もう1パターン。ユーザーを複数のロール(グループ)に所属させるパターン。

    @Test
    public void readWriteRole2() {
        RemoteCacheManager manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("read-write-user-multi")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            cache.put("key1", "value1");
            assertThat(cache.get("key1")).isEqualTo("value1");
        } finally {
            manager.stop();
        }

        //////////////////////

        manager = new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("172.17.0.2:11222")
                        .security()
                        .authentication()
                        .saslMechanism("PLAIN")
                        .username("admin")
                        .password("password")
                        .build());

        try {
            RemoteCache<String, String> cache = manager.getCache("securedCache");

            cache.clear();
        } finally {
            manager.stop();
        }
    }

いずれも、CacheへのWRITE、READができることが確認できました。

簡単な認証・認可の確認としては、こんなところでしょうか?

権限について

ここまで、Cache#putならWRITE、Cache#getならREAD、Cache#clearならBULK_WRITEとドキュメントからの記載や類推から
使ってきましたが、これを正確にマッピングを取りたい場合は、ソースコードを見るのかなぁと思います。

SecureCacheの実装を見ればよいと思います。

https://github.com/infinispan/infinispan/blob/10.1.3.Final/core/src/main/java/org/infinispan/security/impl/SecureCacheImpl.java

各メソッドが、どのような権限を必要とするかがわかります。

   @Override
   public String getVersion() {
      authzManager.checkPermission(subject, AuthorizationPermission.ADMIN);
      return delegate.getVersion();
   }

   @Override
   public V put(K key, V value) {
      authzManager.checkPermission(subject, AuthorizationPermission.WRITE);
      return delegate.put(key, value);
   }

   @Override
   public CompletableFuture<Void> putAllAsync(Map<? extends K, ? extends V> data) {
      authzManager.checkPermission(subject, AuthorizationPermission.WRITE);
      return delegate.putAllAsync(data);
   }

認証メカニズムについて

ところで、今回は認証メカニズムを「PLAIN」と明示しました。

Infinispan 10.1.3.Finalでは、認証メカニズムを指定しない場合、デフォルトでは「SCRAM-SHA-512」が使われます。

https://github.com/infinispan/infinispan/blob/10.1.3.Final/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/configuration/AuthenticationConfigurationBuilder.java#L33

なのですが、なにも考えずに使うとこんな感じで怒られるので、今回は1度パスしました…。

org.infinispan.client.hotrod.exceptions.TransportException:: javax.security.sasl.SaslException: ELY05051: Callback handler does not support credential acquisition [Caused by java.security.NoSuchAlgorithmException: class configured for PasswordFactory (provider: WildFlyElytron) cannot be found.]

今回は、とりあえずなんらかの形でも認証・認可の設定をまとめることを目標に、としたので。

まとめ

今回は、Infinispan Serverを使った認証・認可の設定を行ってみました。

最初、Hot Rod Connectorの部分の設定を見落としていてけっこうハマったのですが、今回で設定として必要な要素は概ね把握できた
気がするので、いったんこれでOKかなと。

気が向いたら、もうちょっと深堀りしてみましょう。

今回作成したソースコードは、こちらに置いています。

https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-authn-authz