RDSスナップショットを、テスト用にマスクする、CodeBuildとdbtestdataで

本番RDSスナップショットをそのままテスト用に使うわけにいかない。個人情報とか業務上の機密とか。マスクします。みなさんどうやってるんですかね。

# 全体像

こんな流れで作ります。

1. create RDS Instance
2. Data masking
3. RDS create snapshot
4. RDS instance shutdown

この記事では 2. のところを扱います。ほかは手作業。そのうちawscliとCodeBuildで自動化する。

<img width="1110" alt="スクリーンショット 2019-06-24 19.00.07 (1).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22947/be1a97f9-3d39-d6d0-de6e-5965e5c7bf7e.png">

## マスク設定ファイルをつくるのに必要な情報を用意する

- information_schema.tables, columnsを漁る
- テーブルそのものの要否をふりわける
- 必要なテーブルについて、マスクすべきカラムを選別する
- カラムごとに、どんなデータパターンでマスクするか決める

## マスク設定ファイルをつくる

マスクツールは dbtestdata を使います。dbtestdata用のマスク設定ファイルを作ります。dbtestdataがMySQLしか対応してないので、MySQLかAurora(MySQL互換)にしか使えないのがツラい。

- https://github.com/dino-tools/dbtestdata

DBマイグレーションを管理しているgitリポジトリに、 `dbmask/dbtestdata.update.${TABLE_NAME}.conf` と作っていくことにします。テーブルごとに1ファイル作ります。このように。

```perl
package sample;

use strict;
use warnings;
use data::VariableDataGenerator;
use sql::VariableSQLGenerator;
use utf8;

return {
    name => __PACKAGE__,
    update => {
        users => {
            primary => "id",
            clazz => {
                name => RANDOM_JA_NAME_KAN,
                email => RANDOM_EMAIL,
                address => RANDOM_JA_PREF,
                tel => RANDOM_JA_TEL,
            }
        }
    }
};
```

カラムごとに、どのようにマスクするかをチクチク書いていきます。マスク不要なカラムは記載不要です。なにがどうなってるのかは、 [GitHub - dino-tools/dbtestdata: database testdata generator](https://github.com/dino-tools/dbtestdata) を漁ってください。

DBマイグレのgitリポジトリに入れておきたいのは、DBマイグレによるスキーマ変更と同調して、マスク設定もアップデートしていきたいからです。テーブルごとにしたいのは、gitでコンフリクトを少なくしたいのと、CodeBuildで並列実行させたいからです。


## RDSインスタンスを立てる

RDSスナップショットから、RDSインスタンスを立てます。本番環境とは別のVPCにするのが事故防止のためにも良いでしょう。CodeBuildから接続できるよう、RDSインスタンス側のセキュリティグループを調整しておきます。

Auroraなら、たとえばこのように。

* [CodeBuild で Aurora スナップショットから Aurora クラスタを作る](https://qiita.com/sasasin/items/4869d64b4648486de9a2)

## CodeBuildビルドプロジェクトをつくる

buildspec.ymlはこんな。 envの部分は、よしなに置き換えて使ってください。RDSに接続するので、このCodeBuildはVPC内で動かします。Dockerイメージは、惰性で [python:3.8-buster](https://hub.docker.com/_/python) を利用してます。wgetとか要るのか、、、なんか要らない気がしてきた。

```yaml
version: 0.2

env:
  variables:
    RDS_ENDPOINT: "hoge"
    RDS_USER: "hoge"
    CONF_FILE: "hoge"
    PERL5LIB: "."
  parameter-store:
    RDS_PASSWD: /CodeBuild/RDS_PASSWD

phases:
  install:
    commands:
      - env | sort
      - echo "dash dash/sh boolean false" | debconf-set-selections
      - DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash
      - apt update
      - apt install -y wget unzip jq mariadb-client libdbi-perl libdbd-mariadb-perl
      - git clone https://github.com/dino-tools/dbtestdata.git /usr/local/dbtestdata
  build:
    commands:
      - |
        cd /usr/local/dbtestdata
        echo ${RDS_PASSWD} \
        | perl dbtestdata.pl update \
            --hostname=${RDS_ENDPOINT} \
            --database=production_asp \
            --username=${RDS_USER} \
            --password \
            --conf=${CODEBUILD_SRC_DIR}/dbmask/${CONF_FILE}
```

ちなみにCodeBuildは実行時間が8時間までの制限があるので、クソデカいテーブルには制限時間オーバーで駄目になります。ラッキーなことに、8時間以内に終わるテーブルだけだったので、今後そのうち考えます。たぶんFargateとかでやると思います。いやどうだろう、わからない。

- [CodeBuild の制限](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/limits.html)

## マスクする

こちらもCodeBuildにやらせます。テーブル数が多い場合は、同時実行数を絞ったり、RDBのグレードを上げたりの工夫が必要になるかと思います。両方で同じバージョンを利用するよう、 `--source-version "${CODEBUILD_SOURCE_VERSION}"` を付けてます。

```yaml
phases:
  install:
    commands:
      - env | sort
      - echo "dash dash/sh boolean false" | debconf-set-selections
      - DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash
      - apt update
      - apt install -y wget unzip jq
      - pip install --upgrade --quiet pip
      - pip install --quiet awscli
      - aws --version
  build:
    commands:
      - |
        cd ${CODEBUILD_SRC_DIR}/dbmask;
        ls dbtestdata.update.*.conf \
        | sort \
        | while read CONF_FILE; do
            aws codebuild start-build \
                --source-version "${CODEBUILD_SOURCE_VERSION}" \
                --project-name dbtestdata-runner \
                --environment-variables-override name="CONF_FILE",value="${CONF_FILE}",type="PLAINTEXT"
        done
```

## 要らんテーブルは空にする

テストの要件などから、空でいいテーブルが多々あります。dbtestdataにもdeleteする機能はありますが使いません。外部キー制約の on delete が動いてしまったりで厄介だったので、mysqlコマンドでtruncateすることにしました。 `--init-command="SET SESSION FOREIGN_KEY_CHECKS=0;"` を付けたかった。 `${CODEBUILD_SRC_DIR}/dbmask/delete.conf` は、1行1テーブル名の、ただのテキストファイルです。

```yaml
version: 0.2

phases:
  install:
    commands:
      - env | sort
      - echo "dash dash/sh boolean false" | debconf-set-selections
      - DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash
      - apt update
      - apt install -y wget unzip jq mariadb-client
  build:
    commands:
      - |
        cat ${CODEBUILD_SRC_DIR}/dbmask/dbtestdata.delete.conf \
        | sort -u \
        | while read TABLE_NAME; do
            mysql -u ${RDS_USER} -p${RDS_PASS} -h ${RDS_HOST} production_asp \
                --init-command="SET SESSION FOREIGN_KEY_CHECKS=0;" \
                -e "truncate table ${TABLE_NAME};"
        done
```


## RDSスナップショットを作って、開発アカウントに共有し、RDSを立てる

他のアカウントに共有するにはこのように。

```bash
aws rds modify-db-snapshot-attribute \ 
    --db-snapshot-identifier masked-snapshot \ 
    --attribute-name restore \ 
    --values-to-add '["xxxxxxxxxxxxxxxxx"]'
```

## DBマイグレーションに並走する

ほっとくとマスク定義が古くなっていきます。テーブルの追加削除もあります。カラムの追加削除もあります。アプリケーションチームと、DBマイグレの都度、マスク定義ファイルのアップデートもしていくよう、ルール作りを忘れずに。