AWSセキュリティグループの変更をコマンド一発化した

ある日、というか昨晩、EC2インスタンスにSSH接続して作業中にコネクションをブチ切られるというブチ切れ事案が発生しました。怒った僕は、しょぼーんとする僕に事態の改善を命じました。

何を言ってるんだお前は、という始まり方をしてしまいました。すみません。

私は自宅のthe Internet接続に5Gルータを使用しています。おおむね満足しているのですが、よくグローバルIPアドレスが変わってしまうので、このブログをお届けしているEC2インスタンスのSSH接続やApache/WordPressの管理ページのアクセス制限に自分でよく引っかかります。
今回は作業中にグローバルIPアドレスが変わってしまい、接続を切られてしまいました。もともとEC2インスタンスにアクセスするたびにセキュリティグループの変更をするのも煩わしいと感じていたのですが、今回の作業中切断事案の発生で、ついに重い腰を上げてコマンド一発化に取り組みました。

自分のグローバルIPアドレスを取得して、awscliでセキュリティグループを更新します。簡単に言ってしまえば、それだけです。awscliをマジメにやるのはほぼ初めてですが、結論から言えばそれほど苦労せずできました。CLIに抵抗がないこと、ドキュメントやヘルプ、エラーメッセージをきちんと読む習慣はお得だな、と実感しました。

まずは、こちらのドキュメントを参照して、awscliでEC2セキュリティグループを触ってみます。ふーんって感じです。

ただ、残念ながら、このドキュメントで紹介されている範囲では、セキュリティグループ自体の作成と削除、ルールの追加しかできないようです。「ルールの追加」手順で許可をどんどん追加していけば、場当たり的ですがやりたいことをできそうです。でもいずれ破綻します。火を見るよりも明らかです。やりたくありません。

そこで、aws ec2 help を実行して出てきたサブコマンドをながめます。その中で、下記あたりが使えそうなので、それのヘルプを読んでいきます。

  • describe-security-group-rules
  • modify-security-group-rules

本命は二つ目の modify~ コマンドです。
まずはdescribe~コマンドで現状を把握できるのを確認します。

$ aws ec2 describe-security-group-rules --security-group-rule-ids sgr-01234567890abcdef
{
    "SecurityGroupRules": [
        {
            "SecurityGroupRuleId": "sgr-01234567890abcdef",
            "GroupId": "sg-01234567890abcdef",
            "GroupOwnerId": "987654321012",
            "IsEgress": false,
            "IpProtocol": "tcp",
            "FromPort": 22,
            "ToPort": 22,
            "CidrIpv4": "12.34.56.78/32",
            "Tags": []
        }
    ]
}

ここで、CidrIpv4に指定されているIPアドレスを適宜変更したいわけです。
つづいて本命のmodify~コマンドの出番です。これは下記のようにすることで、特定のセキュリティグループの中の特定のルールの設定内容を変更することが出来ました。ハマりポイントはありましたが、ちょっとした躓きですみました。これについては後述します。

$ aws ec2 modify-security-group-rules --group-id sg-01234567890abcdef --security-group-rules SecurityGroupRuleId=sgr-01234567890abcdef,SecurityGroupRule={"IpProtocol=tcp,FromPort=22,ToPort=22,CidrIpv4=98.76.54.32/32"}
{
    "Return": true
}

前のdescribe~コマンドで許可された接続元IPアドレスが変更されていることを確認します。

$ aws ec2 describe-security-group-rules --security-group-rule-ids sgr-01234567890abcdef
{
    "SecurityGroupRules": [
        {
            "SecurityGroupRuleId": "sgr-01234567890abcdef",
            "GroupId": "sg-01234567890abcdef",
            "GroupOwnerId": "987654321012",
            "IsEgress": false,
            "IpProtocol": "tcp",
            "FromPort": 22,
            "ToPort": 22,
            "CidrIpv4": "98.76.54.32/32",
            "Tags": []
        }
    ]
}

次は、モバイル環境からEC2インスタンスに接続するために、一時的なルールの追加・削除を一発で出来るようにしようと思います。だいたい、次のサブコマンドでいけそうと目星はついていますが、最近引きこもりなのでモチベーションがイマイチです。

  • authorize-security-group-ingress
  • revoke-security-group-ingress

セキュリティグループの一発変更スクリプトはこちらです。

$ cat aws-updatesg.sh
#!/bin/bash

set -eu

readonly MYGIP="$(/usr/bin/curl -s https://checkip.amazonaws.com/)/32"
readonly SGID="sg-01234567890abcdef"
readonly SGRID="sgr-01234567890abcdef"

/usr/bin/aws ec2 modify-security-group-rules --group-id ${SGID} \
        --security-group-rules SecurityGroupRuleId=${SGRID},SecurityGroupRule={"IpProtocol=tcp,FromPort=22,ToPort=22,CidrIpv4=${MYGIP}"}

/usr/bin/aws ec2 describe-security-group-rules --security-group-rule-ids ${SGRID}

変更したいのはIPアドレスだけ、プロトコルやポート番号は変更しないんだから省略したい!と思ったのですが、SecurityGroupRule={""}の中にCidrIpv4しか書かないと、次のように怒られたので、プロトコルとポート番号を指定しています。

Invalid value 'null' for protocol

以下、ハマりポイントと解決までの道筋を書いていこうと思います。

ワナはawscliコマンドのヘルプに潜んでいました・・・!
こちらです。

$ aws ec2 modify-security-group-rules help | sed -n '119p;128,130p'
EXAMPLES
          aws ec2 modify-security-group-rules \
              --group-id sg-1234567890abcdef0 \
              --security-group-rules SecurityGroupRuleId=sgr-abcdef01234567890,SecurityGroupRule={Description=test,IpProtocol=-1,CidrIpv4=0.0.0.0/0}

SecurityGroupRule={}のなかに指定するパラメータたちが""(ダブルクォーテーション)でくくられていません。これがワナでした。
最初、上に引用したヘルプの例をベースに次のように実行しました。

/usr/bin/aws ec2 modify-security-group-rules --group-id ${SGID} \
        --security-group-rules SecurityGroupRuleId=${SGRID},SecurityGroupRule={IpProtocol=tcp,FromPort=22,ToPort=22,CidrIpv4=${MYGIP}}

そうしたところ、次のようなエラーがIpProtocolFromPortToPortCidrIpv4と4つ出力されて怒られました。

Invalid type for parameter SecurityGroupRules[0].SecurityGroupRule, value: IpProtocol=tcp, type: <class 'str'>, valid types: <class 'dict'>

エラーメッセージから、本来は辞書として渡されるべきものが、文字列として渡されちゃってるんだな、ということは想像できます。
そこでPythonの辞書の作り方を見直してみましたが、d = {'k' : 'v'}といった感じで、雰囲気が違います。これは参考にならなさそうです。残念。
そこでGoogle先生で awscli error str dict あたりをキーワードに検索しました。aws公式のページが2つ先頭に来ましたがこれはハズレで、その次のクラスメソッドさんのブログがあたりでした。大体、私程度がハマることなんて、先人が経験しているものです。

クラスメソッドさんの例ではtype: <type 'unicode'>, valid types: <type 'dict'>と、誤認されるタイプは異なるものの、本来あるべきタイプが辞書であることは共通です。そこでズバリ「適切な書き方」を参照して、{}内の値全体をダブルクオーテーションでくくるようにしました。解決しました。ありがたいです。

このブログを書いているうちに、作業場所がどこかに関わらず、作業するときだけセキュリティグループに許可を追加、作業終了時は許可を削除するのが好ましいんじゃないの、と思えてきましたが、これは明日の自分に託そうと思います。

今日はこのくらいで。