AWS WAFのIP match conditionsに多量のアドレスを登録する

この記事は [Relux Advent Calendar 2017](https://qiita.com/advent-calendar/2017/relux) 1日目の記事です。

## AWS WAFについて
弊社ではAWS WAFを利用しています。
CloudFrontやALBに設置することのできる、AWSマネージドなWAFサービスです。

AWS WAFにはIPアドレスのリストによるホワイトリストまたはブラックリストを設定することができます。
登録するアドレスが少なければ良いのですが、登録するアドレスが多い場合、WebのコンソールやCLIどちらを使っても設定が大変です。
大変なのは主に次の2つの理由によります。

- 登録できるアドレスは /8, /16, /24, /32 のCIDRアドレスのみ
- CLIから登録する場合、リストの作成やリストへの登録操作のたびにトークン発行が必要

例えば /25 のネットワークを登録したい場合には 127個の /32 のアドレスを登録するという操作が必要です。
そしてCLIで操作をする場合、1つのIP match conditionを作成するためには

1. get-change-token でトークン発行
2. create-ip-set でリストを作成
3. get-change-toke でトークン発行
4. update-ip-set でリストにアドレスを追加

と、最低で4回のコマンドを実行する必要があります。

## 登録作業を楽にするスクリプトを作成した
複数のリストにたくさんのIPアドレスを登録する、という作業が必要だったため、簡略化するためにスクリプトを作成しました。
作成したいリストの名前とCIDRアドレスを列挙したファイルの名前を引数に指定して実行すると、
IP match conditionsが作成できます。

1. ファイルからCIDRアドレスを読み込む
2. CIDRアドレスをWAFに登録できるブロックに分解する
3. 登録するためのJSONを生成
4. 作成したJSONを用い、リストにアドレスを登録

といったフローで動作をします。

IPアドレスの計算をする箇所はこちらの記事を参考にしました。
https://qiita.com/harasou/items/5c14c335388f70e178f5

JSONの生成にはPHPのjson_encode()関数を使用しています。

```bash:create-ip-set.bash
#!/bin/bash

# AWS APIからChangeTokenを取得する
function get-change-token () {
    echo `aws waf get-change-token | jq -r '.ChangeToken'`
}

# WAFに空のIP-Setを作成する
function create-ip-set () {
    local changeToken=`get-change-token`
    echo `aws waf create-ip-set --name ${1} --change-token ${changeToken} | jq -r '.IPSet.IPSetId'`
}

# WAFのIP-SetにCIDRアドレスのリストを登録する
function update-ip-set () {
    local changeToken=`get-change-token`
    local addressList=''

    for row in `cat ${2}`
    do
        local cidrAddress=''
        echo $row | grep -oE '/(8|16|24|32)' > /dev/null
        if test $? -eq 0; then
            cidrAddress="['Action'=>'INSERT','IPSetDescriptor'=>['Type'=>'IPV4', 'Value'=> '${row}']],"
        else
            cidrAddress=`disassembleCidrAddress $row`
        fi
        addressList="${addressList}${cidrAddress}"
    done

    aws waf update-ip-set --ip-set-id ${1} --change-token ${changeToken} --updates `php -r "echo json_encode([${addressList}]);"`
}

# /8, /16, /24, /32 以外のCIDRアドレスを/8, /16, /24, /32に分解する
function disassembleCidrAddress () {
    local baseAddress=''
    local networkLength=''
    local step=''
    local disassembledLength=''
    local numberOfAddrs=''
    local count=0

    baseAddress=`echo ${1} | sed -E 's/\/[0-9]+//'`
    baseAddressDecimal=`ip2decimal $baseAddress`
    networkLength=`echo ${1} | grep -oE '/[0-9]+' | sed -E 's/\///'`

    if test $networkLength -lt 8; then
        step=16777216
        disassembledLength=8
        numberOfAddrs=`expr $disassembledLength - $networkLength`
    elif test $networkLength -lt 16; then
        step=65536
        disassembledLength=16
    elif test $networkLength -lt 24; then
        step=256
        disassembledLength=24
    else
        step=1
        disassembledLength=32
    fi
    numberOfAddrs=`expr $disassembledLength - $networkLength`
    numberOfAddrs=`echo $numberOfAddrs' ^ 2 -1' | bc`

    while test $count -le $numberOfAddrs
    do
        echo -n "['Action'=>'INSERT','IPSetDescriptor'=>['Type'=>'IPV4', 'Value'=> '"`decimal2ip baseAddressDecimal`'/'$disassembledLength"']],"
        baseAddressDecimal=`expr $baseAddressDecimal + $step`
        count=`expr $count + 1`
    done
}

#
# IPアドレス計算
# https://qiita.com/harasou/items/5c14c335388f70e178f5
#
# IPアドレス表記 -> 32bit値 に変換
function ip2decimal () {
    local IFS=.
    local c=($1)
    printf "%s\n" $(( (${c[0]} << 24) | (${c[1]} << 16) | (${c[2]} << 8) | ${c[3]} ))
}

# 32bit値 -> IPアドレス表記 に変換
function decimal2ip () {
    local n=$1
    printf "%d.%d.%d.%d\n" $(($n >> 24)) $(( ($n >> 16) & 0xFF)) $(( ($n >> 8) & 0xFF)) $(($n & 0xFF))
}

IpSetId=`create-ip-set ${1}`
update-ip-set $IpSetId $2
```

### 使用方法 

```text:addresslist.txt
192.168.0.0/30
172.20.0.0/21
```

登録したいアドレス一覧を、このようなフォーマットで用意します。
test-address-listというリストを作成する場合、下記のように実行します。

```bash
create-ip-set.bash test-address-list addresslist.txt
```

作成したリストは、Webのマネジメントコンソールでこのように確認できます。

<img width="759" alt="Screen Shot 0029-12-01 at 0.28.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/13038/66373a07-675a-5aa8-5669-187300583170.png">

## 明日は

iOSエンジニア @wootan による「iOS FirebaseRemoteConfigをつかって強制アップデートを実現する方法について」です。
お楽しみに！