Tomcatのデータソース設定を暗号化しよう。

# はじめに

Tomcatのデータソースの設定（context.xml)は平文でパスワード丸見えなので暗号化してみようと思います。
パスワードの暗号化・復号化については２つの方法で実装しました。
（１）設定ファイルに記載された暗号化キーで暗号化・復号化
（２）AWSKeyManagementService、S3を組み合わせての暗号化・復号化
　（キー暗号化キーの管理について合わせて考えてみたパターン）

ソースはこちらにあります。
https://github.com/uzresk/tomcat-encrypt-datasouce

仕組みなんかしらなくていい。設定の仕方おしえてくれ。という方は解説をすっとばして設定方法を見てください。

---

# 環境

* Java1.8.0_25(64bit)
* Tomcat8.0.22
* ※Java8の記法で書いてあるところがあるのでJava8でしか動きません。

---

# 解説

## （１）設定ファイルに記載された暗号化キーで暗号化・復号化

### 暗号化

プロパティファイルに設定されているキーを使ってAESで可逆変換します。
あえて解説するほどでもないので[こちらのソース](https://github.com/uzresk/tomcat-encrypt-datasouce/blob/master/src/main/java/uzresk/crypto/main/StdEncryptorMain.java)をご覧ください。

### 復号化

こんな流れになります。
1. tomcat起動引数に指定されているプロパティファイルから暗号化キーを取得
2. context.xml内に指定されている暗号化されたパスワードを取得。
3. 復号化してPoolConfigurationに再設定。

```(抜粋)StdEncryptPassordDataSourceFactory.java
		PoolConfiguration poolProperties = StdEncryptPasswordDataSourceFactory
				.parsePoolProperties(properties);
		poolProperties.setPassword(encryptor.decrypt(poolProperties
				.getPassword()));
```

![image](https://qiita-image-store.s3.amazonaws.com/0/57686/124bcd83-5894-5ee4-14bf-b05e82e380ba.png)

# （２）AWS KeyManagementService、S3を組み合わせての暗号化・復号化

## 実現イメージ

まずはAWS KeyManagementServiceとはなんぞや？を勉強します。
私が読んだ中ではKeyManagementServiceの解説は下記がベストだと思いますので、まずはこちらを読んでみてください。
[10分でわかる！Key Management Serviceの仕組み](http://dev.classmethod.jp/cloud/aws/10minutes-kms/)

データキーの保管先については同一のサーバであるよりは別のサーバである方が望ましいですよね。そしてディスクだけではなく入り口も冗長化されているべきでしょう。そこで今回は3つのAZ（データセンター）で冗長化されているS3を使います。
S3の可用性はイレブンナイン(99.999999999%)ですし、保管先としては言うこと無いんですが、インターネット経由でデータキーを取りに行くためちょっぴり時間がかかるのがデメリットです。

### 暗号化

事前にIAMからCreateKeyしておくことと、S3のBucketを作成しておきます。（詳しい設定方法については後々の章で解説します）

１．暗号化する前の事前準備として、KMSのマスタキーで暗号化されたキー（データキー）をS3に保管する必要があります。KMSのAPIをCallし、KMSのマスターキーで暗号化されたデータキーを取得します。

```GenerateKeySaveS3.java
	protected ByteBuffer generateEncryptKey() {
		// get data key
		AWSKMSClient kmsClient = new AWSKMSClient(
				new ClasspathPropertiesFileCredentialsProvider());
		kmsClient.setEndpoint(kmsEndpoint);
		GenerateDataKeyRequest dataKeyRequest = new GenerateDataKeyRequest();
		dataKeyRequest.setKeyId(keyId);
		dataKeyRequest.setKeySpec("AES_128");
		GenerateDataKeyResult dataKeyResult = kmsClient
				.generateDataKey(dataKeyRequest);

		return dataKeyResult.getCiphertextBlob();
	}
``` 

２．S3のBucketにデータキーを保管します。
　　ソースはこちら[GenerateKeySaveS3#saveEncryptKey](https://github.com/uzresk/tomcat-encrypt-datasouce/blob/master/src/main/java/uzresk/crypto/main/GenerateKeySaveS3.java)


３．次にデータベースのパスワードを復号化されたデータキーを使って暗号化します。
　　処理の流れとしては以下のとおりです。（対象のソースはこちら[KmsEncryptor#provideDataKey](https://github.com/uzresk/tomcat-encrypt-datasouce/blob/master/src/main/java/uzresk/crypto/KmsEncryptor.java)）
　　①S3からデータキーを取得
　　②データキーをKMSに渡してマスターキーでデータキーを復号化
　　③復号化されたデータキーを使って暗号化

```(②データキーの復号化の部分を抜粋）KmsEncryptor.java
		AWSKMSClient kmsClient = new AWSKMSClient(
				new ClasspathPropertiesFileCredentialsProvider());
		kmsClient.setEndpoint(kmsEndpoint);

		DecryptRequest decryptRequest = new DecryptRequest()
				.withCiphertextBlob(decodedKey);
		ByteBuffer plainText = kmsClient.decrypt(decryptRequest).getPlaintext();

```

### 復号化

先ほどの流れと似ていますが復号化はこんな流れになります。
①S3からデータキーを取得
②データキーをKMSに渡してマスターキーでデータキーを復号化
③context.xml内に記載されている暗号化されたパスワードを取得し、②で取得した復号化されたデータキーを使って復号化します。

![image](https://qiita-image-store.s3.amazonaws.com/0/57686/c7fd6a92-27f3-3104-cc58-af4919f1e4fb.png)

復号化されたパスワードは、[KmsEncryptPasswordDataSourceFactory](https://github.com/uzresk/tomcat-encrypt-datasouce/blob/master/src/main/java/uzresk/tomcat/KmsEncryptPasswordDataSourceFactory.java)でPoolConfigurationに再設定してあげます。

実装している内容は暗号化とほぼ同じ内容なので割愛します。

---

# 設定方法

ここからは実際にcontext.xmlに記載されているデータベースのパスワードを暗号化するための設定方法について記載します。

## （１）設定ファイルに記載された暗号化キーで暗号化・復号化

１．[tomcat-encrypt-datasouce-1.0-jar-with-dependencies.jar](https://github.com/uzresk/tomcat-encrypt-datasouce/blob/master/dist/tomcat-encrypt-datasouce-1.0-jar-with-dependencies.jar)をダウンロードします。

２．暗号化キーが記載されたプロパティファイルを用意します。

``` key.properties
# use StdEncryptPasswordDataSourceFactory
secret.key=hoge
```

３．データベースのパスワードを暗号化します。コマンドを実行するとデータベースのパスワードの入力を求められるので入力すると暗号化されたパスワードが表示されます。（★暗号化されたパスワード★）

```実行コマンド.sh
java -Dkey.propPath=./key.properties -cp tomcat-encrypt-datasouce-1.0-jar-with-dependencies.jar uzresk.crypto.main.StdEncryptorMain
```

```実行結果.sh
input database password.
[データベースのパスワードを入力]
encrypted[★暗号化されたパスワード★]
decrypted check.[true]
```

４．context.xmlにDataSourceFactoryの設定と、暗号化されたパスワードの設定を行います。（←の部分）

``` context.xml
  <Resource name="jdbc/testdb"
            factory="uzresk.tomcat.StdEncryptPasswordDataSourceFactory"←
            description="test db connection pool"
            auth="Container"
            type="javax.sql.DataSource"
            username="username"
            password="★暗号化されたパスワード★"←
            driverClassName="org.postgresql.Driver"
            url="jdbc:postgresql://xxxxxxx:5432/cmp"
            maxActive="20"
            maxIdle="10"
            maxWait="-1"
            logAbandoned="true"
  />
```

５．１で取得したjarとJDBCDriverをtomcatに配置します。
$catalina.home/libの下に直接置いてもよいですし、tomcatのライブラリと混ざるが嫌であればcatalina.propertiesのcommon.loaderを編集してlibextとかにおいても良いと思います。

```catalina.properties
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar","${catalina.home}/libext/*.jar"
```

６．tomcatを起動します。このときTomcatの起動引数（-Dkey.propPath）に２で作ったkey.propertiesへのパスを記載してあげることで、Tomcat側で設定ファイルに記載された暗号化キーを読むことができるようになります。

７．起動時にエラーがでていなければ成功です。

---

## （２）AWSKeyManagementService、S3を組み合わせての暗号化・復号化

１．KMSでマスターキーを作成します。

* [AWS ManagementConsole]→[IAM]→[Encryption Keys]→[FilterでRegionを指定]→[Create Key]→[Alias (required)とDescriptionを適当に入力]→[Next]
* 次の画面の[Define Key Administrators]ではユーザまたはロールに権限付与を行います。
* ロールを付与する対象は２のパターンがあります。APIをcallするためのユーザに権限を付与するか、Tomcatが稼働するEC2に付与する予定のロールに権限付与を行います。
* 今回はローカルのTomcatからアクセスしますのでAPIアクセス用のユーザに付与しましたが、インスタンスプロファイルを利用すれば３で説明するAPIキーの発行が不要になります。
* あとは[Finish]するだけです。
* 鍵ができていることが確認できたら、画面上に表示されている[ARN of key]をメモしておきます。

```
arn:aws:kms:ap-northeast-1:xxxxxxxxxxxx:key/xxxxxxxxxxxxxxxxx
```

２．S3にbucketを作ります。(Bucket名も後の設定で使いますのでメモしておきます）

３．AWSにアクセスするためのAPIキーを発行し、AwsCredentials.propertiesという名前のファイルを作成します。

```AwsCredentials.properties
accessKey=[アクセスキー]
secretKey=[シークレットキー]

```

４．暗号化・復号化するためのプロパティファイルを作成します。

```key.properties
# use KmsEncryptPasswordDataSourceFactory
# データキーを格納するS3のバケット名
bucket.name=kms.cmp.org
# S3のRegion
s3.region=ap-northeast-1
# データキーの名前
key.name=key
# KMSAPIのエンドポイントURL
kms.endpoint=https://kms.ap-northeast-1.amazonaws.com
```

５．[tomcat-encrypt-datasouce-1.0-jar-with-dependencies.jar](https://github.com/uzresk/tomcat-encrypt-datasouce/blob/master/dist/tomcat-encrypt-datasouce-1.0-jar-with-dependencies.jar)をダウンロードします。

次から実際にデータベースのパスワードの暗号化、DataSourceFactoryを使った復号化の設定をしていきます。

６．KMSを使ってS3に鍵を保管します。

* ３で作成したAwsCredentials.properties、４で作成したkey.properties、5で取得したjarを同一ディレクトリに配置します。
* コマンドを実行することでS3に鍵が保存されます。

```実行コマンド.sh
java -Dkey.propPath=./key.properties -cp tomcat-encrypt-datasouce-1.0-jar-with-dependencies.jar uzresk.crypto.main.GenerateKeySaveS3
```

```実行結果.sh
KMSにアクセスするためのKEYIDを入力して下さい(ex.arn:aws:kms:ap-northeast-1:xxxxxxxxxxxxxx:key/xxxxxxxxxxxxxxxxxx)
[１でメモしたARN of keyを入力]
encrypted key upload s3.
```

７．データベースのパスワードを暗号化します。コマンドを実行するとデータベースのパスワードの入力を求められるので入力すると暗号化されたパスワードが表示されます。（★暗号化されたパスワード★）

```実行コマンド.sh
java -Dkey.propPath=./key.properties -cp tomcat-encrypt-datasouce-1.0-jar-with-dependencies.jar uzresk.crypto.main.KmsEncryptorMain
```

```実行結果.sh
input database password.
[データベースのパスワードを入力]
encrypted[★暗号化されたパスワード★]
decrypted check.[true]
```

８．context.xmlにDataSourceFactoryの設定と、暗号化されたパスワードの設定を行います。（←の部分）

``` context.xml
  <Resource name="jdbc/testdb"
            factory="uzresk.tomcat.KmsEncryptPasswordDataSourceFactory"←
            description="test db connection pool"
            auth="Container"
            type="javax.sql.DataSource"
            username="username"
            password="★暗号化されたパスワード★"←
            driverClassName="org.postgresql.Driver"
            url="jdbc:postgresql://xxxxxxx:5432/cmp"
            maxActive="20"
            maxIdle="10"
            maxWait="-1"
            logAbandoned="true"
  />
```

９．１で取得したjarとJDBCDriverを配置します。
$catalina.home/libの下に直接置いてもよいですし、tomcatのライブラリと混ざるが嫌であればcatalina.propertiesのcommon.loaderを編集してlibextとかにおいても良いと思います。

```catalina.properties
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar","${catalina.home}/libext/*.jar"
```

１０．tomcatを起動します。このときTomcatの起動引数（-Dkey.propPath）に４で作ったkey.propertiesへのパスを記載します。

１１．起動時にエラーがでていなければ成功です。

# まとめ

* DataSource設定のパスワードを暗号化するためのちょっとした記事を書こうとしたらKeyManagementServiceを知って盛り上がってしまいました。
* これまでキー暗号化するための暗号化する鍵をどこに保管するかは悩みどころでしたが、KeyManagementServiceは良いソリューションの一つになりそうです。