AWS CLIをWindows(でpipを使わずに)、cygwin環境下で使えるようにして、自宅IP(非固定)が変わったらスクリプト一発でEC2のセキュリティグループをアップデートする


何言ってるかよくわからない？俺もなんでこんなことしてるのかようわからん。。:frowning2:

趣味程度に使うAWSなのでそんなにがっつり使ってるわけではないんだけど、windows(cygwin)環境下で使えるようにしたのでメモ　※python(pip)使ったほうがいいよ？→正解

## 環境
Windows10(64bit)
cygwin
※jsonの整形に jq と jo を使うので、cygwinから使えるようにパス通しておく


## 手順

### AWS CLI for Windowsを入手
 https://aws.amazon.com/jp/cli/
 32bitか64bit好きなほう持ってくる
 Windows上でてきとうなところへ展開しインストール。どこでもいい。
 ※cygwinから辿れるパスで

### cygwinから使えるようにする

展開したディレクトリに aws.exe　があるので、こいつをcygwin上で実行できるようにする。
自分は単純に展開したディレクトリを.bashrcに追記してパス通した。
ついでにaliasに書き足して aws-cli で実行できるように。
※あとで気付いたけどこのaliasいらなくてそのままパス通して aws で実行するだけでよかった

```bash:.bashrc
# for aws-cli
AWSCLI_HOME=[aws-cliを展開したディレクトリ、cygwinが辿れるように書く]
export PATH=$PATH:$AWSCLI_HOME
alias aws-cli='aws.exe'
```

これでcygwinからaws-cliを実行できるようになった。
configure忘れずに。

```
$ aws-cli --version
aws-cli/1.11.175 Python/2.7.9 Windows/8 botocore/1.7.33
```

### やりたいこと
EC2インスタンスに設定しているセキュリティグループについて、自宅のIPが固定じゃないのでIPが変わったらスクリプト一発でセキュリティグループをアップデートしたい。以下の流れで実行する。

・新しいIPを取得する
インターネットに公開されてる自分のGIPを教えてくれるサービスにcurlで接続しレスポンスボディから抜き出す
取得したIPの末尾に /32 を補完してCIDR表記をゲット（具体的なやりかたは省略）

・セキュリティグループをアップデートするためのjsonを作る
aws-cli ec2 authorize-security-group-ingress
が使いたいコマンド。リファレンス →  http://docs.aws.amazon.com/cli/latest/reference/ec2/authorize-security-group-ingress.html

使い方のイメージはリファレンスにある

```bash
$ aws ec2 authorize-security-group-ingress --group-id sg-123abc12 --ip-permissions '[{"IpProtocol": "tcp", "FromPort": 3389, "ToPort": 3389, "IpRanges": [{"CidrIp": "203.0.113.0/24", "Description": "RDP access from NY office"}]}]'
```

これなんだけど、ちょっとごちゃごちゃやってみたかぎり、投げるjsonをシェル変数に埋めてそのままPOSTするのムリっぽい（シングルクォートとかのエスケープを頑張ればいける・・？）ので、おとなしくjsonをファイルに生成して、それを --cli-input-json オプションに渡すのがよさそう。
※どういうjsonを投げたのか？をファイルとして残しておけるしね！

というわけで、シェルでjsonを生成する。
joコマンドを使う。 https://qiita.com/yangci/items/4a7602df4faae5773532 を参照。

生成する必要があるjsonフォーマットは以下の内容。

```json

{
    "GroupId": "xx-xxxxxxx",
    "IpPermissions": [
        {
            "IpProtocol": "-1", #「全てのプロトコル」は -1 で指定
            "IpRanges": [
                {
                    "CidrIp": "203.0.113.0/24", #取得した自分のIP　※このIPはリファレンスに書いてあったもの
                    "Description": "aws-cli(test)" #説明文
                }
            ]
        }
    ]
}
```

以下のjoコマンドで生成する

```bash
TARGET_INFO=$(jo -p GroupId="${GROUP_ID}" IpPermissions[]=$(jo IpProtocol="${IP_PROTOCOL}" IpRanges[]=$(jo CidrIp="${MYIP}" Description="MyHomeIP($TODAY)")))
```
このjoだと、${IP_PROTOCOL}に"-1"をいれてもどうもjoに勝手に -1 に変えられてしまう（ダブルクォートが亡き者にされる）ので、sedを一個かまして無理やりダブルクォートを足す。

```bash
TARGET_INFO=$(jo -p GroupId="${GROUP_ID}" IpPermissions[]=$(jo IpProtocol="${IP_PROTOCOL}" IpRanges[]=$(jo CidrIp="${MYIP}" Description="MyHomeIP($TODAY)")))
echo ${TARGET_INFO} | jq '.' > ${TARGET_INFO_JSON}
TMP=$(cat ${TARGET_INFO_JSON} | sed -e "s/-1/\"-1\"/g")
echo -e "${TMP}" >${TARGET_INFO_JSON}
```
最初jqはいらない予定だったけど、ここでjsonの整形が必要になったので最初のjoの出力時点で再度jqで整形しておく。
joの -p オプションがこれやってくれる気がしたけど、うまく動かなかったのでそのままjqにパイプで渡してる


参考までに、--generate-cli-skeletonの出力は以下

```bash
$ aws ec2 authorize-security-group-ingress --generate-cli-skeleton
```
```json
{
    "CidrIp": "",
    "FromPort": 0,
    "GroupId": "",
    "GroupName": "",
    "IpPermissions": [
        {
            "FromPort": 0,
            "IpProtocol": "",
            "IpRanges": [
                {
                    "CidrIp": "",
                    "Description": ""
                }
            ],
            "Ipv6Ranges": [
                {
                    "CidrIpv6": "",
                    "Description": ""
                }
            ],
            "PrefixListIds": [
                {
                    "Description": "",
                    "PrefixListId": ""
                }
            ],
            "ToPort": 0,
            "UserIdGroupPairs": [
                {
                    "Description": "",
                    "GroupId": "",
                    "GroupName": "",
                    "PeeringStatus": "",
                    "UserId": "",
                    "VpcId": "",
                    "VpcPeeringConnectionId": ""
                }
            ]
        }
    ],
    "IpProtocol": "",
    "SourceSecurityGroupName": "",
    "SourceSecurityGroupOwnerId": "",
    "ToPort": 0,
    "DryRun": true
}
```


最終的にcygwinで実行するシェルスクリプト全体としてはこんな感じ。

```bash:update_securitygroup_myip.sh
#!/bin/bash
PATH=[いい感じにパス通す]
export PATH

### SCRIPT ENV ###
retval=0

SCRIPT_DIR=[スクリプトを設置してるディレクトリ]
LOG_DIR=${SCRIPT_DIR}/log
JSON_DIR=${SCRIPT_DIR}/json

TODAY=$(date '+%Y%m%d')
TIME=$(date '+%H%M%S')
MYIP="$(getMyIP.sh)/32"
TARGET_INFO_JSON=${TODAY}_${TIME}.json

cd ${SCRIPT_DIR}

### AWS ENV ###
retval=0
GROUP_ID="xx-xxxxxxxx"
IP_PROTOCOL="-1"

TARGET_INFO=$(jo -p GroupId="${GROUP_ID}" IpPermissions[]=$(jo IpProtocol="${IP_PROTOCOL}" IpRanges[]=$(jo CidrIp="${MYIP}" Description="MyHomeIP($TODAY)")))
echo ${TARGET_INFO} | jq '.' > ${TARGET_INFO_JSON}
TMP=$(cat ${TARGET_INFO_JSON} | sed -e "s/-1/\"-1\"/g")
echo -e "${TMP}" >${TARGET_INFO_JSON}
#cat ${TARGET_INFO_JSON}

### main ###
RESULT=$(aws ec2 authorize-security-group-ingress --cli-input-json file://${TARGET_INFO_JSON} 2>&1 )
retval=$?

#retvalが0以外だったら、ログディレクトリに出しておく
if [ ! $retval -eq 0 ] ; then
  echo $0 > ${LOG_DIR}/${TODAY}_${TIME}.log
  echo $RESULT >> ${LOG_DIR}/${TODAY}_${TIME}.log
  echo "error , watch log -> ${LOG_DIR}/${TODAY}_${TIME}.log" 1>&2
fi

#生成したjsonを退避
mv ${TARGET_INFO_JSON} ${JSON_DIR}/

exit $retval
```

## 実行
・成功

```bash
$ ./update_securitygroup_myip.sh
```
※特に出力なし。json/配下に、--input-cli-jsonで渡したjsonが残ってる。

・失敗(わざと2回目やると、重複するルールを登録することになるので失敗する)

```bash
$ ./update_securitygroup_myip.sh
error , watch log -> [ログディレクトリ]/20171022_192416.log
```
ログを確認する

```bash
$ cat [ログディレクトリ]/20171022_192416.log
./update_securitygroup_myip.sh
 An error occurred (InvalidPermission.Duplicate) when calling the AuthorizeSecurityGroupIngress operation: the specified rule "peer: xxx.xxx.xxx.xxx/32, ALL, ALLOW" already exists
```
ちゃんと、「このIPのルール既にあるよ」のエラーを返している。ちなみに終了ステータスは255．


## TO DO
・不要になったIPをセキュリティグループから削除する　※上のスクリプトの中でいっしょにやるべき