ローカル環境 KMS で暗号鍵を固定化する (LocalStack)

[AWS Key Management Service (KMS)](https://aws.amazon.com/jp/kms/)は暗号鍵の生成・管理と、アプリケーションデータの暗号化・復号を提供してくれるサービスです。KMS を利用することで、暗号鍵ファイルを直接扱うリスクを回避することでできます。

[以前の記事](https://qiita.com/rkunihiro/items/646d9c39923cf8b23533)で紹介した [LocalStack](https://localstack.cloud/) Docker コンテナで KMS モックサーバを実行し KMS を利用するアプリケーションを開発した際、他の開発メンバーの環境で暗号化したテストデータを復号できるよう、鍵ファイルを固定する必要があり、事前に作成した暗号鍵ファイルをインポートした KMS キーを生成するよう自動化しました。
その手順を共有します。

### Docker compose file 設定

```yaml:docker-compose.yml
version: '3.8'

services:
  localstack:
    container_name: localaws-container-localstack
    image: localstack/localstack:0.13.3
    platform: linux/amd64
    environment:
      SERVICES: kms
      EDGE_PORT: 4566
      INIT_SCRIPTS_PATH: /docker-entrypoint-initaws.d
      DEFAULT_REGION: 'ap-northeast-1'
      KMS_PROVIDER: 'local-kms'
    ports:
      - '4566:4566'
    volumes:
      - ./init:/docker-entrypoint-initaws.d:ro
      - ./data:/data:rw
```

-   環境変数 `SERVICES` に `kms` を追加
-   環境変数 `KMS_PROVIDER` に `local-kms` 設定
-   初期化スクリプトを配置する init ディレクトリを /docker-entrypoint-initaws.d にマウント
-   インポートする鍵ファイルを配置する data ディレクトリを /data にマウント

※Apple Silicon (M1)で実行する場合、通常 ARM 向けイメージが使用されますが、ARM 向けイメージでは本手順が実行できない不具合があるため `platform: linux/amd64` で AMD64 用イメージを実行しています

### インポートする鍵ファイルを作成

以下のコマンドで生成した PlaintextKeyMaterial.b64 を data ディレクトリに配置

```sh:
openssl rand -base64 -out PlaintextKeyMaterial.b64 32
```

docker-compose.ymlや初期化スクリプトとともに、このファイルをGitリポジトリにコミットし、開発メンバー間で共有

### 初期化スクリプト

以下のようなスクリプトを init ディレクトリに配置

```sh:init/kms.sh
#!/bin/bash

# JSONファイルを扱うために jq をインストール
apt update -y && apt install jq -y

cd /data

# 暗号鍵をバイナリファイルに変換
openssl enc -d -base64 -A -in PlaintextKeyMaterial.b64 -out PlaintextKeyMaterial.bin

# KMSキーを生成しKeyId取得
awslocal kms create-key --origin EXTERNAL > create-key-output.json
KeyId=`jq -r '.KeyMetadata.KeyId' create-key-output.json`

# PublicKeyとImportTokenを取得
awslocal kms get-parameters-for-import \
  --key-id ${KeyId} \
  --wrapping-algorithm RSAES_OAEP_SHA_1 \
  --wrapping-key-spec RSA_2048 \
  > get-parameters-for-import-output.json

jq -r '.PublicKey' get-parameters-for-import-output.json > PublicKey.b64
openssl enc -d -base64 -A -in PublicKey.b64 -out PublicKey.bin

jq -r '.ImportToken' get-parameters-for-import-output.json > ImportToken.b64
openssl enc -d -base64 -A -in ImportToken.b64 -out ImportToken.bin

# 暗号鍵をインポート用に暗号化
openssl rsautl -encrypt \
  -in PlaintextKeyMaterial.bin \
  -oaep \
  -inkey PublicKey.bin \
  -keyform DER \
  -pubin \
  -out EncryptedKeyMaterial.bin

# 暗号化した鍵マテリアルをインポート
awslocal kms import-key-material \
  --key-id ${KeyId} \
  --encrypted-key-material fileb://EncryptedKeyMaterial.bin \
  --import-token fileb://ImportToken.bin \
  --expiration-model KEY_MATERIAL_DOES_NOT_EXPIRE

# KMSキーのエイリアス
awslocal kms create-alias --target-key-id ${KeyId} --alias-name 'alias/local-kms-key'

# KMSキーの情報を確認
awslocal kms describe-key --key-id 'alias/local-kms-key'
```

docker compose up でコンテナを起動すると
シェルスクリプトが自動的に実行され、KMS キーを生成し鍵ファイルをインポートします

KMS キー ID は毎回変わりますが、アプリケーションからはエイリアス 'alias/local-kms-key' を指定して利用できます

```sh:暗号化
aws kms encrypt --key-id 'alias/local-kms-key' --plaintext 'プレインテキスト' --endpoint http://localhost:4566 --region ap-northeast-1
```

```sh:復号
aws kms decrypt --key-id 'alias/local-kms-key' --ciphertext-blob '暗号化文字列(base64)' --endpoint http://localhost:4566 --region ap-northeast-1
```

## 参考資料

https://docs.aws.amazon.com/kms/latest/developerguide/importing-keys.html