Bash
JSON
Cygwin
aws-cli

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

More than 1 year has passed since last update.

何言ってるかよくわからない?俺もなんでこんなことしてるのかようわからん。。: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 で実行するだけでよかった

.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

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

$ 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フォーマットは以下の内容。


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

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

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を一個かまして無理やりダブルクォートを足す。

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の出力は以下

$ aws ec2 authorize-security-group-ingress --generate-cli-skeleton
{
    "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で実行するシェルスクリプト全体としてはこんな感じ。

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

実行

・成功

$ ./update_securitygroup_myip.sh

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

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

$ ./update_securitygroup_myip.sh
error , watch log -> [ログディレクトリ]/20171022_192416.log

ログを確認する

$ 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をセキュリティグループから削除する ※上のスクリプトの中でいっしょにやるべき