Always Encrypted

Always Encryptedで暗号化されたカラムをマネージドIDを利用して復号してみた

以前、Always Encryptedで暗号化されたカラムを復号するサンプルプログラムについて記載したが、今回は、システム割り当てマネージドIDを利用して同等の処理を実行してみたので、そのサンプルプログラムを共有する。

なお、マネージド ID とは、Azureリソース自身で認証を行うことのできる、Azure ADで管理されるIDのことをいう。マネージドIDを利用すると、サービスプリンシパルの利用は不要になり、application.propertiesからサービスプリンシパルのクライアントID・パスワードを削除することができる。

前提条件

下記記事の実装が完了していること。

Always Encryptedで暗号化されたカラムを復号化して表示してみたAlways Encryptedで暗号化したカラムは、Always Encryptedにより暗号化されたカラムを復号化できる設定を追加す...

また、Azure FunctionsのKey Vaultのアクセスポリシーが以下のようになっていること。
前提条件_1

前提条件_2

作成したサンプルプログラム(Azure Functions側)の内容

作成したサンプルプログラム(Azure Functions側)の構成は以下の通り。なお、App Service側のプログラムは修正していない。
サンプルプログラムの構成
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。

<2021年4月13日 追記>
spring-cloud-function-dependenciesのバージョンは、2021年3月16日にリリースしたバージョン3.1.2を利用すると、1つのAzure Functions内に複数のファンクションを含む場合の不具合が解消できている。


その場合、Handlerクラスの継承するクラスを「AzureSpringBootRequestHandler」クラスから「FunctionInvoker」クラスに変更する。


spring-cloud-function-dependenciesの3.1.2を利用した実装サンプルは、以下の記事を参照のこと。

spring-cloud-function-dependenciesのバージョンを最新(3.1.2)にしてみたこれまでこのブログで取り上げてきたAzure Functionsのサンプルプログラムでは、spring-cloud-function-d...

pom.xmlの内容は以下の通りで、AppServiceMSICredentialsクラスを利用するための以下の設定を追加している。

<!-- AppServiceMSICredentialsクラスを利用するための設定 -->
<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>azure</artifactId>
    <version>1.36.3</version>
</dependency>

また、application.propertiesの内容は以下の通りで、サービスプリンシパルを利用してKey Vaultにアクセスするための設定を削除している。

# DB接続設定
spring.datasource.url=jdbc:sqlserver://azure-db-purinit.database.windows.net:1433;database=azureSqlDatabase;columnEncryptionSetting=Enabled
#spring.datasource.url=jdbc:sqlserver://azure-db-purinit.database.windows.net:1433;database=azureSqlDatabase
spring.datasource.username=purinit@azure-db-purinit
spring.datasource.password=(DBのパスワード)
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver

さらに、Azure Functionsのメインクラスは以下の通りで、postConstructメソッドのakvProviderオブジェクトの生成方法を変更している。

package com.example;

import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.example.model.GetUserPassParam;
import com.example.model.GetUserPassResult;
import com.example.service.GetUserPassService;
import com.microsoft.azure.AzureEnvironment;
import com.microsoft.azure.credentials.AppServiceMSICredentials;
import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionAzureKeyVaultProvider;
import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionKeyStoreProvider;
import com.microsoft.sqlserver.jdbc.SQLServerConnection;
import com.microsoft.sqlserver.jdbc.SQLServerException;
import com.microsoft.sqlserver.jdbc.SQLServerKeyVaultAuthenticationCallback;

@SpringBootApplication
public class DemoAzureFunction {

    /** Key Vaultの認証の接続設定が終わっているかどうかを判定するフラグ */
    private static boolean keyVaultProviderFlg = false;

    /** USER_PASSデータ取得サービスクラスのオブジェクト */
    @Autowired
    private GetUserPassService getUserPassService;

    public static void main(String[] args) throws Exception {
        SpringApplication.run(DemoAzureFunction.class, args);
    }

    /**
     * Key Vaultの認証の接続設定を登録する
     * @throws SQLException       SQL例外
     * @throws URISyntaxException URI文法エラー
     */
    @PostConstruct
    public void postConstruct() throws SQLException, URISyntaxException {
        if (!keyVaultProviderFlg) {
           SQLServerColumnEncryptionAzureKeyVaultProvider akvProvider 
              = new SQLServerColumnEncryptionAzureKeyVaultProvider(
                      tryAuthenticationCallback());
           Map<String, SQLServerColumnEncryptionKeyStoreProvider> keyStoreMap 
              = new HashMap<String, SQLServerColumnEncryptionKeyStoreProvider>();
           keyStoreMap.put(akvProvider.getName(), akvProvider);
           SQLServerConnection.registerColumnEncryptionKeyStoreProviders(keyStoreMap);
           keyVaultProviderFlg = true;
        }
    }

    /**
     * USER_PASSテーブルのデータを取得し結果を返却する関数
     * @return USER_PASSテーブルデータ取得サービスクラスの呼出結果
     */
    @Bean
    public Function<GetUserPassParam, GetUserPassResult> getUserPass() {
        return getUserPassParam -> getUserPassService.getUserPass(getUserPassParam);
    }

    /**
     * SQLServerKeyVaultAuthenticationCallbackオブジェクトを生成する
     * @return SQLServerKeyVaultAuthenticationCallbackオブジェクト
     * @throws URISyntaxException URI文法エラー
     * @throws SQLServerException SQLServerエラー
     */
    private static SQLServerKeyVaultAuthenticationCallback tryAuthenticationCallback()
        throws URISyntaxException, SQLServerException {
        SQLServerKeyVaultAuthenticationCallback authenticationCallback 
          = new SQLServerKeyVaultAuthenticationCallback() {
             @Override
             public String getAccessToken(String authority, String resource
                                        , String scope) {
                 AppServiceMSICredentials credentials 
                     = new AppServiceMSICredentials(AzureEnvironment.AZURE);
                 try {
                     return credentials.getToken(resource);
                 } catch (IOException ex) {
                     throw new RuntimeException(ex);
                 }
             }
        };
        return authenticationCallback;
    }
}

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/always-encrypted-decrypt-2/demoAzureFunc

サンプルプログラムの実行結果

サンプルプログラムの実行結果は、以下の通り。

1) App Service側のapplication.propertiesは以下の通り。
サンプルプログラムの実行結果_1

2) Azure Functions側のapplication.propertiesは以下の通り。
サンプルプログラムの実行結果_2

3)「mvn azure-functions:deploy」コマンドによって、Azure Functions上にサンプルプログラムをデプロイする。
サンプルプログラムの実行結果_3

なお、Azure Functionsにデプロイする過程は、以下の記事の「Azure FunctionsへのSpring Bootを利用したJavaアプリケーションのデプロイ」を参照のこと。

Azure Functions上でSpring Bootを利用したJavaアプリケーションを作成してみた前回は、Azure Potal上でAzure Functionsを作成してみたが、今回は、前回作成したAzure FunctionsにS...

4) Azure App ServiceのURL「https://azureappdemoservice.azurewebsites.net/」とアクセスした場合の実行結果は、以下の通り。
サンプルプログラムの実行結果_4_1

なお、上記URLは、下記Azure App ServiceのURLから確認できる。
サンプルプログラムの実行結果_4_2

5)「getUserPassの値を取得」ボタンを押下すると、以下の画面が表示され、Always Encryptedで暗号化したカラムが復号化し表示されることが確認できる。
サンプルプログラムの実行結果_5_1

サンプルプログラムの実行結果_5_2

要点まとめ

  • SQLServerColumnEncryptionAzureKeyVaultProviderクラスのコンストラクタの引数に、SQLServerKeyVaultAuthenticationCallbackクラスのオブジェクトを指定することで、システム割り当てマネージドIDを利用してAlways Encryptedで暗号化されたカラムを復号することができる。