Azure Cache for RedisでEntra ID認証

このエントリは2023/07/20現在の情報に基づいています。将来の機能追加や変更に伴い、記載内容からの乖離が発生する可能性があります。

先日、Azure Cache for RedisでEntra ID (旧Azure AD) 認証がPublic Previewになった。

キャッシュの認証に Azure Active Directory を使用する / Use Azure Active Directory for cache authentication
https://learn.microsoft.com/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication

これまで接続文字列だけだったが、このアップデートのおかげで、Managed Identityを使ったEntra ID認証が利用可能になったことで、パスワード漏洩の心配をしなくてすむようになった。

前提条件・制約事項

ドキュメントに記載の通り、以下の点に注意が必要。

  • Redis 4はサポート対象外
  • Enterprise SKUはサポート対象外
  • 現時点ではService ConnectorはAzure AD認証にまだ対応していない

Service Connector を使用して Azure Cache for Redis を統合する / Integrate Azure Cache for Redis with Service Connector
https://learn.microsoft.com/azure/service-connector/how-to-integrate-redis-cache

あと記載はないが、現時点ではFunctionのTriggerの認証機構としても使えない(Functionsでは現時点では接続文字列のみをサポート)。

前提条件と制限事項 / Prerequisites and limitations
https://learn.microsoft.com/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication#prerequisites-and-limitations

構成するにあたっては、Entra ID認証が有効化されている必要がある。デフォルトで有効になっているはずだが、念のため、Azure Portalで設定 > 詳細設定(Settings > Advanced Settings)とたどり、有効になっていることを確認する。チェックが入っていない場合には、チェックを入れて保存しておく。

RBACの構成

通常のManaged Identityを使ったRBACを構成する場合、Access Control (IAM) を使うが、Azure Cache for Redisの場合はちょっと違って【データアクセスの構成】 (Data Access Configuration) でRBACを構成する。

追加する際に、”New Redis User”とあるのでService PrincipalやManaged Identityではだめなのか、とも思えるが、もちろん大丈夫。Access Policyに各ユーザーを割り当てる点は、Managed Identityに対するRBACの構成と同じ。

一般的には、書き込みアプリケーションはContributor、読み取りアプリケーションはReaderでよい。必要であれば、カスタムポリシーを作成できる。

データ アクセス ポリシーを使用してロールベースのアクセス制御を構成する / Configure role-based access control with Data Access Policy
https://learn.microsoft.com/azure/azure-cache-for-redis/cache-configure-role-based-access-control

認証のしくみ

以下のURLに記載の通り、MSAL(JavaだとMSAL4J)で認証を構成するのは同じだが、

  • ポリシーに追加したユーザー名を使ってTokenを取得する必要がある
  • 取得したTokenを定期的に更新する必要がある

の2点が、他のサービスの場合と異なり、少々癖がある。

Azure Active Directory を使用するように Redis クライアントを構成する / Configure your Redis client to use Azure Active Directory
https://learn.microsoft.com/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication#configure-your-redis-client-to-use-azure-active-directory

特に前者は、下図のようにユーザー名として表示される値を使う必要があるが、この値の実体はManaged IdentityやService PrincipalのObject IDである。なので、App Serviceなどであればアプリケーションの設定などを使い、環境変数として渡すなどの配慮が必要。

Entra ID認証を各種ライブラリで使う場合のガイド

Javaの場合、Lettuce、Jedis、Redissonなどのライブラリを使ってRedisを操作することが多いが、Entra ID認証を使う場合のガイドがGitHub上にある。この中にToken Cacheの実装例もある。

Azure Cache for Redis: Azure AD with Jedis client library
https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/identity/azure-identity/src/samples/Azure-Cache-For-Redis/Jedis/Azure-AAD-Authentication-With-Jedis.md
Azure Cache for Redis: Azure AD with Lettuce client library
https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/identity/azure-identity/src/samples/Azure-Cache-For-Redis/Lettuce/Azure-AAD-Authentication-With-Lettuce.md
Azure Cache for Redis: Azure AD with Redisson client library
https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/identity/azure-identity/src/samples/Azure-Cache-For-Redis/Redisson/Azure-AAD-Authentication-With-Redisson.md

試してみる

Azure App Serviceで書き込み、読み取りのサービスを作成し、そのアプリのSystem assigned Managed IdentityをContributorのポリシーに割り当て、動作確認する。

アプリケーションとしては非常にシンプルで、Cacheへの読み書きをするだけ。今回はLettuceを使うMicronaut Redisで試す。

Micronaut Redis
https://micronaut-projects.github.io/micronaut-redis/latest/guide/

認証部分

MSAL4Jの箇所は基本的に同じで、DefaultAzureCredentialを使えばOK。今回はシンプルに逐一RedisClientを作成しているが、当然ながらパフォーマンスが出ないので、上記のドキュメントを参考にAccess Tokenを定期的に更新する仕組みを実装する必要がある。

DefaultAzureCredential defaultAzureCredential = new DefaultAzureCredentialBuilder().build();

RedisURI redisURI = RedisURI.Builder.redis(defaultRedisConfiguration.getHost())
        .withPort(defaultRedisConfiguration.getPort())
        .withSsl(defaultRedisConfiguration.isSsl())
        .withClientName(defaultRedisConfiguration.getClientName())
        .withAuthentication(RedisCredentialsProvider.from(() -> new AzureRedisCredentials(<Username>, defaultAzureCredential)))
        .build();

RedisClient redisClient = RedisClient.create(redisURI);
redisClient.setOptions(ClientOptions.builder()
        .socketOptions(SocketOptions.builder()
                .keepAlive(true)
                .build())
        .protocolVersion(ProtocolVersion.RESP2)
        .build());
return redisClient;

この中のAzureRedisCredentialsは、上記の「Azure Cache for Redis: Azure AD with Lettuce client library」の説明のまんまである。

あとは、Micronautのお作法でGET/POSTでCacheの読み書きをする実装をしている。POSTではJSONで渡ってきたデータを格納している。GETではnameというQuery Parameterを使ってKeyを検索し、結果を返している(応答が複数ある可能性があるので、Listで返すようにしている)。

// POST
try (RedisClient redisClient = this.getRedisClient()) {
    StatefulRedisConnection<String, String> connection = redisClient.connect(StringCodec.UTF8);
    RedisStringCommands<String, String> sync = connection.sync();
    System.out.println(sync.set(_greeting.getName(), _greeting.getMessage()));
    connection.close();
}
catch (Exception e) {
    e.printStackTrace();
    return HttpResponseFactory.INSTANCE
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(String.format(errorMessage, e.getMessage()));
}
//GET
try (RedisClient redisClient = this.getRedisClient()) {
    StatefulRedisConnection<String, String> connection = redisClient.connect(StringCodec.UTF8);
    RedisStringCommands<String, String> sync = connection.sync();
    List<KeyValue<String, String>> keyValueArrayList = sync.mget(name.get());
    if(keyValueArrayList.isEmpty()) {
        return HttpResponseFactory.INSTANCE
                .status(HttpStatus.NOT_FOUND)
                .body(String.format(errorMessage,
                        String.format("No data related with specified key(%s) is not found.", name.get())));
    }
    ArrayList<Greeting> greetingArrayList = keyValueArrayList.stream()
            .map(keyValue -> new Greeting(keyValue.getKey(), keyValue.getValue()))
            .collect(Collectors.toCollection(ArrayList::new));
    connection.close();
    return greetingArrayList;
}
catch (Exception e) {
    e.printStackTrace();
    return HttpResponseFactory.INSTANCE
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(String.format(errorMessage, e.getMessage()));
}

このエントリで使ったコードは以下にある。

Entra ID (a.k.a. Azure AD) authentication for Azure Cache for Redis
https://github.com/anishi1222/EntraID-Authentication-for-Azure-Cache-for-Redis

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください