AWS ネットワーク構成図生成ツール「CloudMapper」を試してみた

みんな なかなかの食いつきな [CloudMapper](https://github.com/duo-labs/cloudmapper) を試してみました。

概要は publickey さんの [こちらの記事](http://www.publickey1.jp/blog/18/awscloudmapper.html) を参照のこと。
おおむね公式の README.md のとおりですが、いくつか躓く箇所があったので共有します。

セットアップ
====

まず GitHub から clone します。
作業をすすめる中で、作業フォルダ直下に clone しないと後々ネットワーク構成図の生成処理を走らせたときに `IOError: [Errno 2] No such file or directory: 'web/data.json'` とか怒られたので以下のようにしてみました。
ツール本体と作業フォルダは分けたいところですがまぁとりあえず。

```
$ mkdir cloudmapper_test
$ cd $_
$ git clone git@github.com:duo-labs/cloudmapper.git .
```
で、その他必要なものをインストールします。

```
$ brew install autoconf automake libtool jq

$ virtualenv venv
$ source venv/bin/activate
(venv) $ pip install -r ~/Repogitory/github/cloudmapper/requirements.txt
```
ここまでは特に問題ないはず。

config.json を用意する
====

CloudMapper の設定ファイル、config.json を用意します。
ここで設定したアカウント名等がネットワーク構成図に描画されるようです。
（あくまで表示用なのでなんでもいいっぽい。違ったらごめんなさい。）

config.json.demo をコピーして以下のようにしました。

```config.json
{  "accounts":
    [
        {"id": "123456789012", "name": "aws_account_1", "default": true}
    ],
    "cidrs":
    {
        "1.1.1.1/32": {"name": "SF Office"},
        "2.2.2.2/28": {"name": "NY Office"}
    }
}
```

変更箇所は "name": を "demo" から "aws-account-1" にしただけ。
後述しますが、CloudMapper は指定した AWS アカウントの全ネットワーク構成図を生成しようとします。
よって AWS アカウントを一意に識別する名前をここで設定する、という考え方のようです。

なので複数の AWS アカウントを持っている場合、

```（抜粋）config.json
{  "accounts":
    [
        {"id": "123456789012", "name": "aws-account-1", "default": true},
        {"id": "234567890123", "name": "aws-account-2", "default": false},
        {"id": "345678901234", "name": "aws-account-3", "default": false}
    ],
    ...
}
```

のような形になるのかな。

"default": のところは cloudmapper.py の get_account() でこんな判定をしていたので、まぁなんでもいいんじゃないのかなーというところです。
（コメントは私が勝手にいれました）

```（抜粋）cloudmapper.py
# 第一引数が --account_name で指定するアカウント名。
# 第二引数が config.json の内容。
def get_account(account_name, config, config_filename):
    for account in config["accounts"]:
        if account["name"] == account_name:
            return account
        if account_name is None and account.get("default", False):
            # account_name が未指定なら default=true の定義を利用する。
            return account
```

~~"cidrs" はよくわからないのでこのままで。~~
外部と接続している IP アドレスがあれば、cidrs に定義しておけばネットワーク図中 IP アドレスでなくホスト名で表示してくれます（試してないけど、そのはず）。

AWS IAM ユーザを準備する
===

CloudMapper が AWS アカウントにアクセスするときに使う IAM ユーザを用意します。
admin 権限ユーザで動かしてツールのバグとかでリソース全削除とか目も当てられないので、この辺はきちんとします。

ユーザ名はわかりやすく「cloudmapper」にしました。
AWS CLI でつくります。大人なので。

```
（ユーザ作って）
$ aws --profile <プロファイル名> iam create-user --user-name cloudmapper

（ポリシーを適用する）
$ aws --profile <プロファイル名> iam put-user-policy --user-name cloudmapper --policy-name cloudmapper_readonly_policy --policy-document '{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Resource": "*",
      "Action": [
        "ec2:DescribeRegions",
        "ec2:DescribeAvailabilityZones",
        "ec2:DescribeVpcs",
        "ec2:DescribeSubnets",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeVpcPeeringConnections",
        "ec2:DescribeInstances",
        "ec2:DescribeNetworkInterfaces",
        "rds:DescribeDBInstances",
        "elasticloadbalancing:DescribeLoadBalancers"
      ]
    }
  ]
}'
```

（ところでちなみに私は事故防止のためデフォルトプロファイル使わない派です。）

ポリシーは README.md に載っていた「The minimal policy needed is:」というやつのまま。
EC2、RDS、ELB の describe 権限のみですね。これなら変なことできないので安心です。

で、アクセスキーを取得して ~/.aws/* に登録しておきます。
プロファイル名も cloudmapper とかにしておきましょうか。

```
$ aws --profile <プロファイル名> iam create-access-key --user-name cloudmapper
```

```~/.aws/config
[profile cloudmapper]
output = json
region = ap-northeast-1
```

```~/.aws/credentials
[cloudmapper]
aws_access_key_id = AKIA****************
aws_secret_access_key = ****************************************
```

これで準備完了。

AWS から情報を集める
====

さてここから CloudMapper を動かしていきます。まずは作図の元になる情報を集めるところから。
README.md には以下２つの方法が提示されていますが、ここで注意点。

```
(1) $ ./collect_data.sh --account my_account
(2) $ python cloudmapper.py gather --account-name my_account
```

両方とも内部で AWS CLI または boto3 で describeナントカ を実行しているだけですが、現時点では (2) の python 版だとプロファイルの指定ができないご様子。つまりデフォルトプロファイルで動いちゃう。--profile オプション的なのもない。

なので私は (1) 一択。以下のようにします。

```
(venv) $ ./collect_data.sh --account aws-account-1 --profile cloudmapper
```

すると「aws-account-1」というディレクトリが作られ、その配下にいろいろ describe した結果の json が保存されます。
（なのでお試しのつもりでアカウント名を「cloudmapper」とかにすると、cloudmapper のソースディレクトリに json が出力されてしまうのでご注意を）

ネットワーク構成を解析する
====

AWS から情報が describe できたら、作図のために解析処理を走らせます。

```
(venv) $ python cloudmapper.py prepare --account aws-account-1
```

実はここが地味にポイント。
前述の通り CloudMapper は指定した AWS アカウントの全貌を作図しようとします。
よって複数 VPC があるとかリソースが多すぎると、以下のように「ちょっと作図しきれないわー」と処理を諦めてしまいます。

```
WARNING: There are 182 total nodes and 7605 total edges.
  This will be difficult to display and may be too complex to make sense of.
  Consider reducing the number of items in the diagram by viewing a single
   region, ignoring internal edges, or other filtering.
```

というわけで「フィルタリングオプションつかってアイテム数を減らす」ことになるのですが、各フィルタリングオプションの具体的な効果は確認中。
この辺は今後の課題。

今回は、リソース数の控えめの とある sandbox 的な AWS アカウントで試してみました。

ネットワーク構成図を表示する
====

ここまできたら以下のようにして CloudMapper サーバを起動するだけ。

```
(venv) $ python cloudmapper.py serve

CloudMapper serving on 127.0.0.1:8000
```

ブラウザで http://127.0.0.1:8000 を見に行けばネットワーク構成図が見えます。

<img width="912" alt="cloudmapper.png" src="https://qiita-image-store.s3.amazonaws.com/0/116572/276634ba-e515-3aaf-6040-1f8d4603db63.png">

レイアウトは自由に変更でき、サブネットや VPC 単位で折りたたみ／展開表示も可能。
選択したノードの詳細情報も見えますし使い勝手は良いですね。
セキュリティチェック的な観点では AWS Config なりで問題検出と処置を行うべきでしょうが、やっぱり可視化大事。

インフラコードを push して CFn やら Terraform が動いた後にうまいことイベント拾って自動更新するようにしたら面白い気がします。

ちなみに「Dockerコンテナないのかよ」というあたりはすでに [pull request](https://github.com/duo-labs/cloudmapper/pull/54) がでていました。
これがマージされれば、もっと簡単に試せますね。

まとめ
====

よさげ。
セキュリティチェック的な動機でなくとも、とりあえず可視化したらなにか気づきが得られそう。
ECS で CloudMapper サーバを常時稼働させておいてシステムごとの最新ネットワーク構成図が見える、なんてことしたいっす。