Y_Yamashitaのブログ

勉強したことのアウトプット・メモ中心。たまに日記とか。

JAWS-UG CLI専門支部 #185R IAM入門 (ユーザー/グループ)を噛みしめながら復習する①

はじめに

JAWS-UG CLI専門支部 #185R IAM入門 (ユーザー/グループ)のハンズオンに参加した。

jawsug-cli.connpass.com

当日は手順書通りに作業を実施して問題なく完了したものの、「何のために何をやっているのか」きちんと理解しきれていなかった部分があったので、改めて噛みしめながら復習する。
※手順はハンズオンから一部変更している。

今回実施すること

今回は、アクセスキーを使用し、異なるIAMユーザーの権限を利用してAWSリソースにアクセスする。
図示すると以下のようになる。 f:id:YuY_83:20210703015259p:plain

  • IAMFullAccess権限しか持っていないCloud9環境を用意する。このCloud9環境では、S3やEC2へのアクセスは出来ない。
  • S3FullAccess権限を持っているIAMユーザーのアクセスキーを利用し、このCloud9環境上からS3へアクセス出来ることを確認する。
  • アクセスキーを利用しても、EC2へのアクセスは出来ないことを確認する。

(個人的反省)なぜ今回のハンズオンで、途中で理解が追い付かなくなったのか

作業に入る前に、なぜ今回のハンズオンで途中から理解が追い付かなくなったのか改めて考えてみると、以下の2点にハマってしまったように思う。(※個人の感想なので、他の受講者の人はまた違うかもしれない)

アクセス先のユーザーをCloud9で作成したことで混乱してしまった

もともと自分の中には、「アクセスキーを利用してAWSリソースにアクセスするのは、AWS外のリソースからアクセスする場合」という若干偏った認識があった。その場合、外部リソースは、何等かの方法でアクセスキーIDとクレデンシャル情報(シークレットアクセスキー)を他所から手に入れて、それを使ってアクセスすることになる。

ところが今回は、アクセス先のユーザーや、そのユーザーが保持するアクセスキー自体を、Cloud9環境で作成した。
そのため、なんだか自作自演感があり、どのリソースが何を司っているのかよく分からなくなってしまった。(※個人の感想です) f:id:YuY_83:20210703015400p:plain

そこで、改めて今回やることを図示し、自分の頭を整理し直した。

やりたい事を実行するためのコマンドと、変数やファイルを作成するためのコマンドを分類できていなかった。

CLI支部でのハンズオンでは毎回、結構な数の変数を作成する(基本的にコマンドの引数は変数を参照させるため)。中には一つの変数やファイルを作成するためにかなり色々とやるケースがあり、特に今回クレデンシャルファイルを作成する際には前段で色々な準備が発生したため、当日は何のために何をやっているのかついていけなくなった感がある。

この点に関しては、以下のように逆算・分類して考えるべきだった。

  1. やりたい事を実行するために必要なコマンドは何か
  2. そのコマンドで必要な引数やファイルは何か
  3. その引数を格納する変数やファイルの作成に必要なコマンドは何か


そこで今回は、「変数やファイルを作成するためのコマンド」は一旦省略し、「やりたい事を実行するために必要なコマンド」にフォーカスする。
変数を作成するためのコマンドについては、後日、別の記事としてまとめたいと思う。

実作業

IAMユーザの作成

IAMユーザー「handson-cli-novice-user」を作成する。
IAMユーザーの作成には、aws iam create-userコマンドを使用する。

コマンド 内容
aws iam create-user IAMユーザーを作成する
--user-name ユーザー名を指定する
[ec2-user@ip-10-0-0-127%]$ IAM_USER_NAME='handson-cli-novice-user'
[ec2-user@ip-10-0-0-127%]$ aws iam create-user \
>   --user-name ${IAM_USER_NAME}
{
    "User": {
        "Path": "/",
        "UserName": "handson-cli-novice-user",
        "UserId": "AIDATO53NFKUDFSH7BZLN",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:user/handson-cli-novice-user",
        "CreateDate": "2021-07-02T13:08:20Z"
    }
}
[ec2-user@ip-10-0-0-127%]$ 


IAMログインプロファイルの作成

IAMユーザー「handson-cli-novice-user」のログインプロファイル(パスワード等)を作成する。aws iam create-login-profileコマンドを使用する。

コマンド 内容
aws iam create-login-profile ログインプロファイルを作成する
--user-name ユーザー名を指定する
--password パスワードを指定する
[ec2-user@ip-10-0-0-127%]$ IAM_USER_NAME='handson-cli-novice-user'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ IAM_USER_PASSWORD='#userPass123'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws iam create-login-profile \
>   --user-name ${IAM_USER_NAME} \
>   --password "${IAM_USER_PASSWORD}"
{
    "LoginProfile": {
        "UserName": "handson-cli-novice-user",
        "CreateDate": "2021-07-02T13:12:05Z",
        "PasswordResetRequired": false
    }
}
[ec2-user@ip-10-0-0-127%]$ 


アクセスキーの作成

IAMユーザー「handson-cli-novice-user」のアクセスキーを作成する。aws iam create-access-keyコマンドを使用する。

コマンド 内容
aws iam create-access-key アクセスキーを作成する
--user-name ユーザー名を指定する
[ec2-user@ip-10-0-0-127%]$ IAM_USER_NAME='handson-cli-novice-user'
[ec2-user@ip-10-0-0-127%]$ FILE_ACCESS_KEY='/home/ec2-user/environment/conf-handson-cli-iam/handson-cli-novice-user-token-0.json'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws iam create-access-key \
>   --user-name ${IAM_USER_NAME} \
>   > ${FILE_ACCESS_KEY} \
>     && cat ${FILE_ACCESS_KEY}
{
    "AccessKey": {
        "UserName": "handson-cli-novice-user",
        "AccessKeyId": "XXXXXXXXXXXXXXXXXXXX",
        "Status": "Active",
        "SecretAccessKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        "CreateDate": "2021-07-02T13:19:26Z"
    }
}
[ec2-user@ip-10-0-0-127%]$


クレデンシャルファイルの作成

クレデンシャルファイルは、AWS CLIでは、デフォルトでは~/.aws/credentialsに格納されるファイルで、以下の内容になっている。

[default]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

当日のハンズオンでは、このファイルをjp.pyやsedを駆使して作成していたが、そこを掘り下げるとそれだけでブログが1本書けそうなので、今回は省略する。

以下のパスでクレデンシャルファイルを作成した。

[ec2-user@ip-10-0-0-127%]$ ls ${FILE_USER_CREDENTIAL}
/home/ec2-user/environment/conf-handson-cli-iam/handson-cli-novice-user.ini
[ec2-user@ip-10-0-0-127%]$ 


IAMグループの作成

IAMグループ「handson-cli-novice-group」作成する。IAMグループの作成は、aws iam create-groupコマンドを使用する。

コマンド 内容
aws iam create-group IAMグループを作成する
--group-name グループ名を指定する
[ec2-user@ip-10-0-0-127%]$ IAM_GROUP_NAME='handson-cli-novice-group'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws iam create-group \
>   --group-name ${IAM_GROUP_NAME}
{
    "Group": {
        "Path": "/",
        "GroupName": "handson-cli-novice-group",
        "GroupId": "AGPATO53NFKUB2ZZ2ITWG",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:group/handson-cli-novice-group",
        "CreateDate": "2021-07-02T16:35:18Z"
    }
}
[ec2-user@ip-10-0-0-127%]$ 


IAMグループへのポリシーアタッチ

IAMグループ「handson-cli-novice-group」にIAMポリシー「AmazonS3FullAccess」をアタッチする。ポリシーのアタッチにはaws iam attach-group-policyコマンドを使用する。

コマンド 内容
aws iam attach-group-policy IAMポリシーをアタッチする
--group-name グループ名を指定する
--policy-arn ポリシーのARNを指定する

ポリシーのARNはaws iam list-policiesで参照する。詳細はこちらの記事を参照。

[ec2-user@ip-10-0-0-127%]$ IAM_GROUP_NAME='handson-cli-novice-group'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ IAM_POLICY_NAME='AmazonS3FullAccess'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ IAM_POLICY_ARN=$( \
>   aws iam list-policies \
>     --scope AWS \
>     --max-items 1000 \
>     --query "Policies[?PolicyName==\`${IAM_POLICY_NAME}\`].Arn" \
>     --output text \
> ) \
>   && echo "${IAM_POLICY_ARN}"
arn:aws:iam::aws:policy/AmazonS3FullAccess
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws iam attach-group-policy \
>   --group-name ${IAM_GROUP_NAME} \
>   --policy-arn ${IAM_POLICY_ARN}
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws iam list-attached-group-policies \
>   --group-name ${IAM_GROUP_NAME} \
>   --query "AttachedPolicies[?PolicyName == \`${IAM_POLICY_NAME}\`].PolicyName" \
>   --output text
AmazonS3FullAccess
[ec2-user@ip-10-0-0-127%]$ 


IAMユーザーのIAMグループへの追加

IAMユーザー「handson-cli-novice-user」をIAMグループ「handson-cli-novice-group」へ追加する。IAMユーザーをIAMグループに追加するにはaws iam add-user-to-groupコマンドを使用する。

コマンド 内容
aws iam add-user-to-group IAMユーザーをIAMグループに追加する
--group-name グループ名を指定する
--user-name ユーザー名を指定する
[ec2-user@ip-10-0-0-127%]$ IAM_GROUP_NAME='handson-cli-novice-group'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ IAM_USER_NAME='handson-cli-novice-user'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws iam add-user-to-group \
>   --group-name ${IAM_GROUP_NAME} \
>   --user-name ${IAM_USER_NAME}
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws iam list-groups-for-user \
>   --user-name ${IAM_USER_NAME} \
>   --query "Groups[?GroupName==\`${IAM_GROUP_NAME}\`].GroupName" \
>   --output text
handson-cli-novice-group
[ec2-user@ip-10-0-0-127%]$ 


アクセスキーを利用してアクセス確認

準備が整ったので、いよいよアクセスキーを利用してアクセス確認を行う。その前に、まずは現在のCloud9の権限でS3、EC2にアクセスできないことを確認する。

[ec2-user@ip-10-0-0-127%]$ aws iam list-attached-role-policies \
> --role-name handson-cloud9-environment-role
{
    "AttachedPolicies": [
        {
            "PolicyName": "IAMFullAccess",
            "PolicyArn": "arn:aws:iam::aws:policy/IAMFullAccess"
        }
    ]
}
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************3HXR         iam-role    
secret_key     ****************AjZt         iam-role    
    region                <not set>             None    None
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws s3 ls

An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws ec2 describe-instances --instance-ids i-0cdb696axxxxx7373
You must specify a region. You can also configure your region by running "aws configure".
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ export AWS_DEFAULT_REGION=ap-northeast-1
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws ec2 describe-instances --instance-ids i-0cdb696axxxxx7373

An error occurred (UnauthorizedOperation) when calling the DescribeInstances operation: You are not authorized to perform this operation.
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 

想定通り、S3もEC2も権限が無く表示することが出来なかった。
なお、EC2を表示させようとした時に「デフォルトリージョンを指定しろ」と怒られたので、環境変数AWS_DEFAULT_REGIONを東京リージョンに指定した。ちなみに、CLI環境の環境変数の設定は、aws configureコマンドで実施することも可能。

今度はアクセスキーを利用してアクセス確認をする。アクセスキーを利用するには、環境変数AWS_DEFAULT_PROFILEを「handson-cli-novice-user」に設定し、「handson-cli-novice-user」のプロファイルを使用するようにする。

さらに、環境変数AWS_SHARED_CREDENTIALS_FILEで先ほど作成したクレデンシャルファイルパスを指定し、利用するアクセスキーを指定する。

AWS CLI環境変数については、以下のサイトを参照。

docs.aws.amazon.com

[ec2-user@ip-10-0-0-127%]$ export AWS_SHARED_CREDENTIALS_FILE="${HOME}/environment/conf-handson-cli-iam/handson-cli-novice-user.ini"
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ export AWS_DEFAULT_PROFILE='handson-cli-novice-user'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile  handson-cli-novice-user              env    ['AWS_DEFAULT_PROFILE', 'AWS_PROFILE']
access_key     ****************YIMY shared-credentials-file    
secret_key     ****************eTQf shared-credentials-file    
    region           ap-northeast-1              env    AWS_DEFAULT_REGION
[ec2-user@ip-10-0-0-127%]$ 

aws configure listの出力結果が変わっていることが分かる。この状態で、改めてS3とEC2にアクセスを試行してみる。

[ec2-user@ip-10-0-0-127%]$ aws s3 ls
2021-05-16 09:21:41 cf-templates-t1azjfgdsw8s-ap-northeast-1
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws ec2 describe-instances --instance-ids i-0cdb696axxxxx7373

An error occurred (UnauthorizedOperation) when calling the DescribeInstances operation: You are not authorized to perform this operation.
[ec2-user@ip-10-0-0-127%]$ 

想定通り、S3の情報は表示できたが、EC2の情報は表示できなかった。

なお、環境変数を削除すれば、プロファイル設定はすぐに元に戻る。

[ec2-user@ip-10-0-0-127%]$ export -n AWS_DEFAULT_PROFILE
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************HA5W         iam-role    
secret_key     ****************wdqW         iam-role    
    region           ap-northeast-1              env    AWS_DEFAULT_REGION
[ec2-user@ip-10-0-0-127%]$ 


おわりに

じっくりと復習することで、大分理解が深まった。今回深堀しなかったクレデンシャルファイルの作成等は、別途ブログにまとめたいと思う。

また、以下のページで推奨されている、IAMロールを使用した一時認証情報でのアクセスについても実際に試して理解を深めたいと思う。

docs.aws.amazon.com

その他にも、ウェブIDフェデレーションや、AD連携などの認証方式もあるので、これらもなるべく実際に自分で構築して理解を深めたい。

認証の道は果てしなく続いているが、実務で避けて通れないところでもあるので、コツコツ取り組みたいと思う。

ユーザーデータを使用してEC2のSSHのListenポートを変えてみる(JAWS-UG CLI専門支部 #184R EC2入門の復習)

はじめに

6/24(木)開催の「JAWS-UG CLI専門支部 #184R EC2入門」に参加した。

jawsug-cli.connpass.com

今回のハンズオンではユーザーデータを使ってWEBサーバの立ち上げを実施した。自分はユーザーデータを使ったのは初めてだったので、復習がてら、何かオリジナルのユーザーデータを使ってみようと思う。
といっても大層なものは思い浮かばなかったしサクッと試したかったので、タイトル通り、SSHのListenポートを変えてみることにした。

前提

今回は以下の前提で作業を実施する。

  • VPCは作成済みのデフォルトVPCを使用する
  • EC2はAmazon Linux2を使用する
  • EC2インスタンスの作成、起動はCloud9から実施する

手順1. Cloud9への権限付与

Cloud9でEC2インスタンスの起動を実施するため、Cloud9用のIAMロールを作成し、"AmazonEC2FullAccess"権限を付与する。権限の付与はCloudShellで実施する。権限付与の流れはこちらの記事を参照。

手順2. ユーザーデータの作成

今回使うユーザーデータ

今回は、Amazon Linux2でSSHのListenポートを変更するスクリプトをユーザーデータとして用意した。

#!/bin/bash

sed -i.old -e 's/#Port 22/Port 51512/g' /etc/ssh/sshd_config
systemctl restart sshd

スクリプトと呼ぶのが憚られる簡便さだが、とりあえずユーザーデータを使った設定を試してみたいだけなのでよしとしよう。
なお、ユーザーデータはroot権限で実施されるので、sudoコマンドを使わないことに注意する。
また、Amazon Linux2では動作したが、他のディストリビューションで動くかどうかは不明。(SELinuxが有効かどうかでも変わると思うし、sshdコンフィグのパスが違う可能性もあるため)

ユーザーデータの格納

Cloud9上に上記のスクリプトを準備する。

[ec2-user@ip-10-0-0-127%]$ DIR_USER_DATA="${HOME}/environment/conf-handson-cli-ec2"
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ ls -d ${HOME}/environment/conf-handson-cli-ec2
ls: cannot access /home/ec2-user/environment/conf-handson-cli-ec2: No such file or directory
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ ls -d ${DIR_USER_DATA}
ls: cannot access /home/ec2-user/environment/conf-handson-cli-ec2: No such file or directory
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ mkdir -p ${HOME}/environment/conf-handson-cli-ec2
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ USER_DATA_NAME='ec2-ssh-port-change'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ FILE_USER_DATA="${DIR_USER_DATA}/${USER_DATA_NAME}.bash" \
>   && echo ${FILE_USER_DATA}
/home/ec2-user/environment/conf-handson-cli-ec2/ec2-ssh-port-change.bash
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ cat << EOF > ${FILE_USER_DATA}
> #!/bin/bash
> 
> sed -i.old -e 's/#Port 22/Port 51512/g' /etc/ssh/sshd_config
> 
> systemctl restart sshd
> 
> EOF
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ cat ${FILE_USER_DATA}
#!/bin/bash

sed -i.old -e 's/#Port 22/Port 51512/g' /etc/ssh/sshd_config

systemctl restart sshd

[ec2-user@ip-10-0-0-127%]$ ls ${FILE_USER_DATA}
/home/ec2-user/environment/conf-handson-cli-ec2/ec2-ssh-port-change.bash
[ec2-user@ip-10-0-0-127%]$ 

EC2インスタンスの起動

キーペアの作成

EC2インスタンスSSH接続するために、キーペアを作成する。

[ec2-user@ip-10-0-0-127%]$ aws ec2 create-key-pair --key-name SampleKeyPair \
> --query 'KeyMaterial' \
> --output text > SampleKeyPair.pem
[ec2-user@ip-10-0-0-127%]$

--query 'KeyMaterial'は「暗号化されていないPEMエンコードされたRSA秘密鍵」の指定になる模様。
ちゃんと作成されているか確認してみる。

[ec2-user@ip-10-0-0-127%]$ aws ec2 describe-key-pairs --key-name SampleKeyPair
{
    "KeyPairs": [
        {
            "KeyPairId": "key-0d1de70fb6fa94734",
            "KeyFingerprint": "6e:ff:0b:0a:9c:02:81:86:92:fe:0b:6c:18:13:80:bf:85:49:c6:d1",
            "KeyName": "SampleKeyPair",
            "Tags": []
        }
    ]
}
[ec2-user@ip-10-0-0-127%]$

問題なく作成されていた。作成された秘密鍵は、ローカルにダウンロードしておく。

EC2インスタンスの起動

いよいよEC2インスタンスを起動する。今回は以下のコマンド、オプションでEC2を起動する。

コマンド 内容
aws ec2 run-instances EC2インスタンスを起動する
--image-id <イメージID> インスタンスのイメージIDを指定する
--instance-type <インスタンスタイプ> インスタンスタイプを指定する
--tag-specifications <タグキー、値> タグキーと値を指定する
--user-data <ファイルパス> ユーザーデータのファイルパスを指定する
--associate-public-ip-address パブリックIPアドレスの割当を行う
--key-name <キーペア名> キーペアを指定する

上記の実行のために、変数を準備する。

[ec2-user@ip-10-0-0-127%]$ EC2_INSTANCE_IMAGE_NAME="amzn2-ami-hvm-2.0.20210525.0-x86_64-gp2"
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ EC2_INSTANCE_TYPE="t2.micro"
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ EC2_INSTANCE_IMAGE_ID=$( \
>   aws ec2 describe-images \
>     --filters Name=name,Values="${EC2_INSTANCE_IMAGE_NAME}" \
>     --query 'Images[].ImageId' \
>     --output text \
> ) \
>   && echo ${EC2_INSTANCE_IMAGE_ID}
ami-001f026eaf69770b4
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ EC2_INSTANCE_TAG_NAME='handson-cli-ec2-userdata_public_ip_address_default_vpc-instance'
[ec2-user@ip-10-0-0-127%]$ EC2_TAG_KEY='Name'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ STRING_TAG_CONF="ResourceType=instance,Tags=[{Key=${EC2_TAG_KEY},Value=${EC2_INSTANCE_TAG_NAME}}]" \
>   && echo ${STRING_TAG_CONF}
ResourceType=instance,Tags=[{Key=Name,Value=handson-cli-ec2-userdata_public_ip_address_default_vpc-instance}]
[ec2-user@ip-10-0-0-127%]$ 

変数の設定が完了したので、実際にEC2インスタンスを起動する。

[ec2-user@ip-10-0-0-127%]$ aws ec2 run-instances \
>   --image-id ${EC2_INSTANCE_IMAGE_ID} \
>   --instance-type ${EC2_INSTANCE_TYPE} \
>   --tag-specifications ${STRING_TAG_CONF} \
>   --user-data file://${FILE_USER_DATA} \
>   --associate-public-ip-address \
>   --key-name SampleKeyPair
{
    "Groups": [],
    "Instances": [
        {
            "AmiLaunchIndex": 0,
            "ImageId": "ami-001f026eaf69770b4",
            "InstanceId": "i-04e7cc3f33fe110c4",
            "InstanceType": "t2.micro",
            "KeyName": "SampleKeyPair",

-------------- 中略 --------------
    ],
    "OwnerId": "XXXXXXXXXXXX",
    "ReservationId": "r-048e604b224cb5961"
}
[ec2-user@ip-10-0-0-127%]$

コマンドが成功すると、EC2の情報が記述されたJSONが出力される。

セキュリティグループの更新

次に、デフォルトのセキュリティグループを更新し、ユーザーデータで指定したListenポートを許可するようにする。インバウンドルールを更新する際はaws ec2 authorize-security-group-ingressコマンドを実施する。

コマンド 内容
aws ec2 authorize-security-group-ingress インバウンドのセキュリティグループを更新する
--group-name <グループ名> セキュリティグループ名を指定する
--protocol <プロトコル> プロトコルを指定する
--port <ポート番号> ポート番号を指定する
--cidr <CIDRブロック> CIDRブロックを指定する

実際にコマンドを実行する。

[ec2-user@ip-10-0-0-127%]$ EC2_SECURITY_GROUP_NAME='default'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ EC2_SECURITY_GROUP_PROTOCOL='tcp'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ EC2_SECURITY_GROUP_PORT='51512'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ EC2_SECURITY_GROUP_CIDR='0.0.0.0/0'
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws ec2 authorize-security-group-ingress \
>   --group-name ${EC2_SECURITY_GROUP_NAME} \
>   --protocol ${EC2_SECURITY_GROUP_PROTOCOL} \
>   --port ${EC2_SECURITY_GROUP_PORT} \
>   --cidr ${EC2_SECURITY_GROUP_CIDR}
[ec2-user@ip-10-0-0-127%]$

アクセス確認

EC2が立ち上がり、セキュリティグループも更新されたので、変更後のポート指定でSSH出来るか試してみる。
今回は、TeraTermを使用してSSHログインを試行する。

EC2インスタンスグローバルIPアドレスの確認

まずはアクセスするために、EC2インスタンスグローバルIPアドレスを確認する。

[ec2-user@ip-10-0-0-127%]$ EC2_IP_PUBLIC=$( \
>   aws ec2 describe-instances \
>     --filters Name=tag-key,Values=Name \
>               Name=tag-value,Values=${EC2_INSTANCE_TAG_NAME} \
>     --instance-ids ${ARRAY_EC2_INSTANCE_IDS} \
>     --query "Reservations[].Instances[].PublicIpAddress" \
>     --output text \
> ) \
>   && echo ${EC2_IP_PUBLIC}
54.248.6.20
[ec2-user@ip-10-0-0-127%]$

TeraTermによるアクセス試行

続いてTeraTermでアクセスを試す。 f:id:YuY_83:20210628030036p:plain f:id:YuY_83:20210628030230p:plain

サーバがTeraTermのknownhostsリストに未登録のため警告が出るが、問題ないので続行する。この時点で接続自体は出来ている。
あとは、事前にダウンロードした秘密鍵を使ってログインする。 f:id:YuY_83:20210628030638p:plain f:id:YuY_83:20210628031017p:plain

問題なくログインできたので、sshdコンフィグの内容が書き換わっているか確認してみる。ついでにポートのListen状況も確認する。

[ec2-user@ip-172-31-45-52 ~]$ sudo cat /etc/ssh/sshd_config | grep Port
Port 51512
#GatewayPorts no
[ec2-user@ip-172-31-45-52 ~]$ 
[ec2-user@ip-172-31-45-52 ~]$ 
[ec2-user@ip-172-31-45-52 ~]$ netstat -alt
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 localhost:smtp          0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:sunrpc          0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:51512           0.0.0.0:*               LISTEN     
tcp        0    252 ip-172-31-45-52.a:51512 dynamic-xxx-xxx-xxx:51280 ESTABLISHED
tcp6       0      0 [::]:sunrpc             [::]:*                  LISTEN     
tcp6       0      0 [::]:51512              [::]:*                  LISTEN     
[ec2-user@ip-172-31-45-52 ~]$ 
[ec2-user@ip-172-31-45-52 ~]$ 

問題なさそう。あとは、このEC2インスタンスのプライベートIPアドレスが合っているかどうかも、念のためCloud9上で再確認する。

[ec2-user@ip-10-0-0-127%]$ aws ec2 describe-instances \
>     --filters Name=tag-key,Values=Name \
>               Name=tag-value,Values=${EC2_INSTANCE_TAG_NAME} \
>     --instance-ids i-04e7cc3f33fe110c4 \
>     --query "Reservations[].Instances[].PrivateIpAddress" \
>     --output text
172.31.45.52
[ec2-user@ip-10-0-0-127%]$ 

こちらも想定通りだった。満足したので、後片付けをする。今回作成したEC2インスタンスやキーペアは全て削除し、デフォルトVPCからセキュリティグループをデタッチし、Cloud9のIAMロールから"AmazonEC2FullAccess"をデタッチする。(手順は割愛)

おわりに

今回は最低限のスクリプトを使って、ユーザーデータによる設定変更の確認が出来た。機会があれば、もう少し複雑な処理も試してみたいと思う。

「AKIBA.AWS ONLINE #04 - こんなときどうする⁉︎ トラブル・インシデント対応 編-」参加メモ

はじめに

本日、以下のオンライン勉強会に参加した。

dev.classmethod.jp

各セッションで個人的に印象に残った内容・勉強になった内容等を抜粋して、メモとして記載する。

当日のセッション内容

AWSセキュリティインシデント通知

なぜAWSから通知がくるのか?

  • 責任共有モデルでユーザーの責任範囲となっている箇所のインシデントについて、なぜAWSから通知がくるのか?
    • AWSリソースが外部に攻撃をしかけている、アクセスキーが流出しているなど、AWSが看過出来ない状況であるため。

Your AWS Access Key is Exposed

  • アクセスキーが外部に公開されていることを検出したという通知
  • EC2がマイニングに使われ、数千万円レベルの利用費が発生する場合も
  • 事業継続に影響を与えるレベルの損害が発生することもある

  • やること
    • アクセスキーの削除、無効を最優先(無効で終わらせずに、最終的には必ず削除する)
    • AWSへ対応着手したことを連絡する(連絡しないとアカウント停止される恐れも)
    • アクセスキーに付与されていた権限を確認
    • 全IAMユーザーとrootユーザーのパスワード・アクセスキーをローテーション
    • CloudTrailで対象のキーのアクティビティを確認(全リージョンで確認する)
    • 不正なリソースの削除(IAMユーザーが作られているケースも)
    • 請求のチェック(反映には1日かかる場合も)
    • AWSへの対応完了連絡(英語)

  • 知っておくこと
    • AWSに漏洩した理由を聞いてもわからない。AWSの責任範囲ではない。
    • たまにAWSが権限をはく奪するポリシーを適用し被害を止めることがあるが、必ずではない。過信しない。
    • rootユーザーのアクセスキーは使用しない。
    • S3のオブジェクトレベルのログは有効にしておく。何が漏れたかログでチェックできる。
    • 攻撃者は本番環境か検証環境かは気にしない。検証環境だからセキュリティ対策は適当でいいや、は通用しない。

AWS Abuse Report

  • 自分のAWSアカウントのリソースが外部に攻撃をしている。(加害者になっている)
  • リスク
    • EC2が乗っ取られている場合、EC2内のデータも抜き取られる。
    • ロール権限によっては他のリソース(S3やRDSなど)にも被害が波及する。
    • 攻撃の被害者から訴訟される可能性も。
    • 対処やAWSへの返信をしない場合、24時間以内にアカウントを停止される恐れも。

  • やること
    • AWSへ着手したことの報告(英語)
    • 攻撃を止める。発生はほぼEC2
    • EC2のIAMロール権限の確認
    • CloudTrailで不審なアクティビティがないか確認
    • AWSへ対応完了の連絡(英語)※攻撃が停止されていること+適切な再発防止策が実施されていることを求められる。

  • 知っておくこと
    • Abuseについてはサポートケースに質問しても回答不可。対応チームが異なる。
    • 対応チームは持っている情報くれるが、原因調査は行わない(責任共有モデル)
    • 削除したEC2をバックアップから復元して再発した場合はもう一度通知がやってくる。
    • スパムがフィッシングメールだと判明すると、AWSの温度感が上がる。AWSからの通知を見逃さないこと。

Amazon SES Review

  • 存在しないアドレスにメールを送ってバウンスしている場合や、苦情率が増加した時に通知を受ける。
  • SESの認証情報が奪取されてスパム送信している場合がある。
  • リスク
    • 著名なドメインを運用中の場合、そのドメインでスパムが送信されることにより被害が拡大したり、ブランドの損失につながったりする。
    • フィッシングメールの場合、非常に高い緊急度での対応がAWSから求められる。
    • 24時間以内にSESが停止されることも。

  • SESが不正利用された場合の対処については、以下のブログに詳細にまとめられている

dev.classmethod.jp

通知が来た時に焦らないために

  • インシデントは起きるものとして準備しておく。
  • AWSの通知に頼らない検知・調査方法を確立する。
  • 監査ログが取れているかチェックする。
  • ビジネスリスクとして認識し、組織的に対応できるようにしておく。
    • エンジニアリングレイヤーだけで取り組まず、ビジネスレイヤーを巻き込む。
  • 自力で対応が難しい場合は調査会社を選定しておく。
  • 対応訓練を行う。
    • 公式ドキュメントに対応手順の記載がある。
    • 定期的に訓練することには高い効果がある。

AWSアカウントのセキュリティインシデント調査どうする?Amazon Detectiveを利用した調査の勘所

登壇者ブログ

このセッションの内容については、すでに登壇者の方がご自身でブログにまとめているので、詳細についてはそちらを参照。

dev.classmethod.jp

AWSのセキュリティとは

  • Well-Architectedフレームワークの中にセキュリティの柱がある。
  • セキュリティの柱は7つの設計原則と6つのベストプラクティスがあり、その中に10個の質問がある。
  • Well-Architectedフレームワークの質問を使い、「自分の組織はどうか?」を議論すると、幅広い内容について検討できる。
  • AWSに閉じた話ではなく、ワークロード・ビジネス・会社全体に活用できる考え方。

Amazon GuardDuty

  • AWS上のインシデント検知するサービス。以下は一例。
  • GuardDutyによるインシデント検知
    • CloudTrail/VPC FlowLog/Athena等を利用してログ調査する。
    • S3に保存されたログはAthenaを使って分析するのがセオリー。

Amazon Detective

  • ログ調査を簡単にしてくれるツール。
  • 有効にすればログを自動で取り込んでくれる。
  • エンティティ間を自動で関連づけしてくれる。
  • GuardDutyのFindingsから「Detectiveで調査する」というボタンを押して使用する。
  • リンクを辿ることができる(例えばEC2インスタンスのログから、作成者・インスタンスID・ロールなどへ飛ぶことができる)
  • API実行履歴や通信記録を確認できる。
    • 実行したIPアドレス毎にAPI呼び出しが分類されている。
    • 正規利用者と不正利用者が混在している場合などに整理しやすい。
    • どのあたりからAPIが利用されているか地図上で表示してくれる。

コインマイニングへの対応

  • GuardDuty Findingsで「CryptoCurrencyCurrency:EC2/BitcoinTool.B」が出たら、ほぼ100%マイニングされていると思って間違いない。
  • IAMが乗っ取られたかEC2が乗っ取られている。どちらかによって対応範囲が異なる。
  • EC2をいつ誰が作ったのか確認する。ユーザーに見覚えがあるか。見覚えがあれば本人に確認する。
  • 攻撃者が作った場合は更に元を辿る。どこからクレデンシャルが漏れたのか。
  • IAMユーザーのアクセスキーか、EC2等のIAMロールの一時クレデンシャルか。
  • ユーザーの他の作業を確認。「RunInstances」「CreateSecurityGroup」「CreateKeyPair」があれば注意。
  • コインマイニングのために新規に作られたEC2は削除する。
  • 既存のEC2の場合、AMIをバックアップして、隔離された環境で後で解析する。
  • コインマイニングの場合、すべてのインスタンスタイプで作成できる限界までEC2を作成される。※インスタンスタイプ毎に作成数に上限がある。

S3データアクセス

  • GuardDuty Findings「PenTest:S3/KaliLinux」など。
  • もともと公開しているものじゃないバケット、データにアクセスが来ていたらヤバい。
  • 公開しているバケット、データなのかについて、Detectiveで調査することはできない。
  • Detectiveは全てのFindingTypeに対応しているわけではないため。対応しているタイプは以下を参照。

サポートされている検索結果タイプ - Amazon Detective

  • S3データイベントはCloudTrail上ではデフォルトでは取れない。管理イベントのみ。
  • 重要なデータを保存する場合はデータイベントを収集する設定を追加する。
  • S3にログを保管するなら、不正アクセスされてもログを消されない工夫が必要。
    • 別のAWSアカウントにログを集約する。
    • S3オブジェクトロックを使用する。
    • SCPを使用して、組織全体でアクセス制限をかける。

その他

  • EC2についてロケーション単位でのアクセス制限をしたい場合は、ALBをかませてWAFを利用する。
  • ロケーションでのアクセス制限はあくまで補助的な対策と考える。
  • 組織のセキュリティ意識を高めるには、マイニングを実際にやられた人のブログを読ませるのが良いかも。

アラート対応で疲弊しているチームが今できること

登壇者ブログ

こちらのセッションについても、すでに登壇者の方がブログにまとめていたので、詳細はそちらを参照。

dev.classmethod.jp

AWS Systems Manager(SSM)

  • EC2インスタンスなどの操作を自動化(RunBook)
  • 2021年5月にアップデートあり
  • インシデントマネージャー
    • インシデント管理機能と進化した自動化フロー
    • CloudWatchアラームをトリガーにしてSSM AutomationのRunBookを実行
    • さらにインシデントの管理ページを作成できる(事象サマリ、時系列、振り返り)
    • 一定時間解決しない場合に電話を発信したり、状況をSlackに上げたりできる。
    • CloudWatchアラームのアクション設定画面から連携可能。
    • 「あるアラームが出たらプロセスを再起動する」といったオペレーションを自動化できる。
    • アラートが発生した時にtopコマンドを打つように自動化して、後でどのプロセスが暴れていたのか検知したりすることも可能。

CloudWatch Anomaly Detection(異常検出)

  • 学習結果を閾値に反映する。
  • 従来型のアラートと並行して運用。
  • 最初は予測のバンド狭く(敏感に)設定→徐々に鈍感にしていく
  • 傾向がある程度読めるメトリクス向き(月末に多い・土日に少ない・バッチ処理する夜間に多い、など)
  • 突発的なイベントには対応しづらい(セールや新商品の発表など)

CloudWatchダッシュボード

  • 傾向が知りたいならダッシュボードを利用する。
  • 「なんか不安だから」と低い閾値でアラームを出すのをやめる。
  • 朝に5分眺める習慣から。
  • 他のアカウントのリソースを持ってくるのはちょっと大変。
  • ダッシュボードはいきなり完璧なものを作るのは難しい。まずはたたき台を作り、そこから練り上げていく。ダッシュボードは育てるもの。

CloudWatch Syncetics

  • 合成監視サービス。
  • URLを監視して、ユーザーの体験のメトリクスをとる。
    • レスポンスタイム、レスポンスコード、レスポンスなしの数。
    • 4XX、5XXエラーが返ってくるか。
    • WEBサービスの死活監視にも使える。

おわりに

今回の勉強会は非常にためになった。個人環境だと本格的な運用の経験を積むのがなかなか難しいので、実際の現場でのリアルな運用話が聞けて良かった。

また、個人アカウントでも不正アクセスから大きな損害を発生させてしまうのは、リソースを簡単にデプロイできるクラウド環境ならではの怖さだな、と感じた。 Well-Architectedフレームワークを見て自分の利用環境も見直してみようと思う。

「AWSの基礎を学ぼう 特別編 最新サービスをみんなで触ってみる DevOps Guru」参加メモ

はじめに

6/19(土)に実施された「AWSの基礎を学ぼう 特別編 最新サービスをみんなで触ってみる DevOps Guru」に参加した。

awsbasics.connpass.com

学習したことを忘れないようにメモを残す。

DevOps Guruとは?

機械学習によって通常の運用パターンから逸脱した動作を検出し、考えられる原因と解決案の提示などをしてくれるサービス。

特徴

将来の予測もしてくれる

例えば、ストレージへのデータ書き込みが日に日に増えている状態を検知した場合、「このままのペースで増え続ければ1週間後にはリソースが枯渇する」といった予測をして通知を上げてくれる。

考えられる原因を提示してくれる

異常に関連がありそうなイベントの表示が可能。例えば、監視対象のCloudFormationがいつ作られたのか等。CloudTrailのログへのリンクの表示等が可能。

解決案を提案してくれる(レコメンデーション)

異常の内容を分析した結果、解決に繋がりそうなAWSドキュメントのリンクを表示したり、設定のカスタマイズの提案をしてくれる。

レコメンデーションはアップデートされる

レコメンデーションはAWSのドキュメント等を参考にして提示されている。そのため、AWSドキュメントが更新されれば、それに合わせてレコメンデーションもアップデートされる。(ただし、リアルタイムに反映されているわけではない)

注意点

複数リージョンにまたがった監視はできない

現時点では、DevOps Guruを稼働させているリージョンのサービスのみが監視対象となる。

特定のAWSリソースのみ監視対象とすることはできない

現時点では、「現在のAWS アカウントのすべてのAWSリソース」か、もしくは「指定されたCloudFormationスタックのすべてのAWSリソース」が対象となる。

f:id:YuY_83:20210620214322p:plain
DevOps Guru監視対象

すべてのAWSリソースが監視対象になっているわけではない

上記で「すべてのAWSリソース」と記載があるが、実際にはGuruに対応しているAWSリソースはまだ一部のみなので、「Guruに対応しているすべてのAWSリソースが監視対象」というのが正しい。対象は以下を参照。

aws.amazon.com

監視対象がアップデートされると料金が上がるかも

上述のように、監視するリソースを選べず、現時点で対応しているすべてのリソースが対象となるので、サービスがアップデートされて監視対象が増えると、気づかないうちに請求額が上がっている、なんて事態になるかも。サービスのアップデート情報は常にキャッチアップが必要。

監視を開始する前に2時間の「学習期間」が必要

「正常な状態」(ベースラインというらしい)を学習するために2時間必要なので、すぐに監視を開始できるわけではない。

ちなみに、ベースラインを再度学習しなおしたい場合、設定→DevOps Guru 分析カバレッジで「このリージョンのリソースを分析しない」を選択し、一度分析をやめてから、再度分析するリソースを選びなおす必要がある。(選びなおした後に、再度2時間の学習期間が必要)

f:id:YuY_83:20210620232551p:plain
ベースラインを再学習するには、一度分析を解除する。

学習期間に起きていたことを「正常状態」として学習する

Guruはエラーを検知するサービスではなく、あくまで「学習した正常状態との乖離を通知する」サービスなので、例えば、学習期間に特定のエラーが頻発していた場合、それを「正常」として学習してしまい、その後に同様のエラーが出てもスルーする可能性がある。

検知の厳しさのチューニングはできない

「ベースラインからどの程度かけ離れたら異常と判断するか」の厳しさについては、現時点ではチューニングすることができない。

実際に異常通知を出してみる(ハンズオン概要)

今回のハンズオンでは、読み込みキャパシティが「1」のDynamoDBを作成し、そこにスクリプトで連続リクエストを出して500系エラーを起こすことで、異常状態を発生させた。(スクリプトの内容や手順は、ハンズオン当日のみパスが公開されていた事を考慮して、ここでは割愛する)

まずはスクリプト実行前のGuruの状態。何のエラーも出ていない。

f:id:YuY_83:20210620232945p:plain
まだ何も起きていない平和な状態

ここから、Cloud9上でスクリプトを実行し、リクエストを大量投下する。複数のターミナルでスクリプトを同時実行する。ちなみに、CloudShellだとこのようなスクリプトの複数同時実行は出来ない模様。(シングルスレッドのため)

f:id:YuY_83:20210620234004p:plain
Cloud9上で、スクリプトを複数ターミナルで同時実行

5~10分くらい待つと、Guruに異常通知が表示された。

f:id:YuY_83:20210620234756p:plain
異常発生
なお、「事後的インサイト」とは既に発生している異常で、「予測的インサイト」とは今後発生し得る異常のこと。

「継続中の事後的インサイト」の数字をクリックし、事後的インサイトの中身を見てみる。
f:id:YuY_83:20210621000610p:plain
事後的インサイト
影響を受けるリソースや、重要度、異常が継続中かどうか等が表示されている。名前をクリックすると更に詳細が表示される。
f:id:YuY_83:20210621000810p:plain
集約されたメトリクス
集約されたメトリクスでは、異常と関連があると判断されたメトリクスが、発生時刻とともに表示されている。

また、「集約されたメトリクス」の隣にある「グラフ化された異常」タブでは、異常と関連のありそうな値(レイテンシなど)がグラフで表示されている。
f:id:YuY_83:20210621001010p:plain
グラフ化された異常
さらに、レコメンデーションでは解決に役立ちそうなAWSドキュメントのリンクや、解決につながりそうな設定のカスタマイズが提案されている。
f:id:YuY_83:20210621001516p:plain
レコメンデーション
このように、異常に対して複合的な観点で分析を行い、最適と思われる解決策を提示してくれる。

おわりに

単に通知が出るだけでなく、色々な分析結果と解決案の提示があるのが面白かった。実際には、複雑な本番環境の運用で、AIの判定を全面的に信頼して対応できるようになるにはもう少し時間がかかるのかもしれないが、今後の展開に期待したいサービスだった。

Amazon SQS標準キューが順序を保証しないことをスクリプトを使って確認してみた(JAWS-UG CLI専門支部 #181R SQS入門の復習その②)

はじめに

この記事は前回のJAWS-UG CLI専門支部 #181R SQS入門を噛みしめながら復習する(①事前作業編)の続編にあたる。

続編ならタイトルを「噛みしめながら復習する(②〇〇編)」にすべきだが、今回は、前回ほど一つ一つのコマンドを掘り下げておらず、別のことがやりたくなったので、結局全然違うタイトルにしてしまった。

やりたいこと

タイトル通り、Amazon SQS標準キューがキューの順序を保証しないことを、実際に確かめてみたくなった。当日のハンズオンでは送受信したメッセージが一つだったので、今回は複数メッセージを送受信してみる。

それとシェルスクリプトが書きたくなったので、メッセージの送受信や削除をスクリプトで回してみる。(スクリプト中のSQS絡みのコマンドは、ハンズオンで学習したものを主に使用している。)

<補足>Amazon SQSキューには、キューの順序を保証しない標準キューと、格納された順に取り出しが可能なFIFOキューがある。両者の細かい違いについては以下を参照。5年前の記事だが、凄く分かりやすくまとまっていた。

dev.classmethod.jp

今回の確認方法

まず30回ほどメッセージを送信してキューに格納する。次に、それを30回受信し、送信時と順番がどのように変わるか確認してみる。メッセージの送受信はシェルスクリプトで実施する。

なお、前提として、SQS標準キューはすでに作成済み。また、今回は可視性タイムアウトをデフォルトの30秒から、少し長めの2分に変更してみた。それと、メッセージを受信する際の最大メッセージ数を30に設定した。

メッセージの送信

早速、メッセージを送信してみる。

送信用スクリプト

#!/bin/bash

SQS_QUEUE_NAME="handson-cli-sqs-novice-queue"
SQS_QUEUE_URL=$(aws sqs get-queue-url \
    --queue-name ${SQS_QUEUE_NAME} \
    --output text)

for ((i=1; i<=30; i++))
do
    SQS_MESSAGE_BODY="${i}回目のメッセージ送信です。今は$(date +%H:%M:%S)です。" \
    && echo ${SQS_MESSAGE_BODY}

    aws sqs send-message --queue-url "${SQS_QUEUE_URL}" --message-body "${SQS_MESSAGE_BODY}"

    SQS_MESSAGE_NUMBER="キューのメッセージ数は \
    $(aws sqs get-queue-attributes \
    --queue-url ${SQS_QUEUE_URL} \
    --attribute-names ApproximateNumberOfMessages \
    --query 'Attributes.ApproximateNumberOfMessages' \
    --output text) \
    です。" \
    && echo ${SQS_MESSAGE_NUMBER}
done

SQS_QUEUE_NAMEには事前に作成したキューの名前を指定。SQS_QUEUE_URLには作成したキューのURLを指定する。(キューにメッセージを送信する際や、キューに格納されているメッセージ数を表示する際に引数として指定する必要がある。)

送信メッセージは、「〇回目のメッセージ送信です。今は<何時何分何秒>です。」という内容にした。(後でメッセージを受信した際に、何回目に送信したメッセージを受信したのか分かりやすくするため。)

その後、aws sqs send-messageコマンドで実際にメッセージを送信し、最後にaws sqs get-queue-attributesコマンドを使用し、ApproximateNumberOfMessages(キューから取得可能なおおよそのメッセージ数)を表示させる。

実際に送信してみた

[ec2-user@ip-10-0-0-127%]$ sh Message_send.sh
1回目のメッセージ送信です。今は18:00:17です。
{
    "MD5OfMessageBody": "8cfebdc11d8f69f86f5b9e6ebb3b498c",
    "MessageId": "87674bd6-168c-4e2f-bae1-04a38d354979"
}
キューのメッセージ数は 1 です。
2回目のメッセージ送信です。今は18:00:18です。
{
    "MD5OfMessageBody": "69654bfffefed671752009807c78a201",
    "MessageId": "3aca5431-db4b-4f92-8b94-4bd4bbe13bad"
}
キューのメッセージ数は 0 です。
3回目のメッセージ送信です。今は18:00:19です。
{
    "MD5OfMessageBody": "63926ed43acc5a4e5090acb674b51586",
    "MessageId": "6bf66fde-b885-4156-8d71-dd821d8f00f6"
}
キューのメッセージ数は 2 です。
4回目のメッセージ送信です。今は18:00:20です。
{
    "MD5OfMessageBody": "9fab63a356221d8c157018b0f1602ec9",
    "MessageId": "e967d444-70e4-414f-9876-4d4f986f2a0d"
}
キューのメッセージ数は 4 です。
5回目のメッセージ送信です。今は18:00:21です。
{
    "MD5OfMessageBody": "a554d49254c583f472cfbaf684d63cd5",
    "MessageId": "93685150-7a86-43ea-aa4c-b79d3b0d1c0a"
}
キューのメッセージ数は 5 です。

-------中略-------

26回目のメッセージ送信です。今は18:00:42です。
{
    "MD5OfMessageBody": "f507afe33a1fdebc3c33d34bd8bd88d2",
    "MessageId": "4cb3cf6e-4944-4126-8d44-11a4e2035005"
}
キューのメッセージ数は 19 です。
27回目のメッセージ送信です。今は18:00:43です。
{
    "MD5OfMessageBody": "9dbe09b131ece845f9d9e81d1efb9023",
    "MessageId": "96ea8765-7fd3-450f-9b62-28975f914a2c"
}
キューのメッセージ数は 15 です。
28回目のメッセージ送信です。今は18:00:44です。
{
    "MD5OfMessageBody": "65b5d8d23b5431e40ffcbaf47cd45b58",
    "MessageId": "4af0258b-ce2f-47c6-923a-2420506318fe"
}
キューのメッセージ数は 17 です。
29回目のメッセージ送信です。今は18:00:45です。
{
    "MD5OfMessageBody": "1e33c69b50bd937a4d250bfbb0dd6754",
    "MessageId": "be599a97-d21a-4f15-ba63-59bece7d0773"
}
キューのメッセージ数は 28 です。
30回目のメッセージ送信です。今は18:00:46です。
{
    "MD5OfMessageBody": "57d21dc8959426918e66607515c4fb0d",
    "MessageId": "e2bcc30a-36cf-4796-a3b4-12e4c60191a7"
}
キューのメッセージ数は 26 です。
[ec2-user@ip-10-0-0-127%]$
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws sqs get-queue-attributes \
>   --queue-url ${SQS_QUEUE_URL} \
>   --attribute-names ApproximateNumberOfMessages \
>   --query 'Attributes.ApproximateNumberOfMessages' \
>   --output text
30
[ec2-user@ip-10-0-0-127%]$

無事に30回メッセージが送信された。実行結果を見ると、ちょうど1秒に1メッセージ送信していた模様。また、最後のメッセージが送信されて少ししてから改めてApproximateNumberOfMessagesを確認すると、キューに格納されているメッセージ数はちゃんと30個になっていた。

ただし、スクリプトを回している間のApproximateNumberOfMessagesの数はキレイに増える訳ではなく、結構増減していた。結果を表にまとめると以下のようになった。

メッセージ送信回数 送信時刻 ApproximateNumberOfMessages
1回目 18:00:17 1
2回目 18:00:18 0
3回目 18:00:19 2
4回目 18:00:20 4
5回目 18:00:21 5
6回目 18:00:22 6
7回目 18:00:23 2
8回目 18:00:24 7
9回目 18:00:25 7
10回目 18:00:26 10
11回目 18:00:27 5
12回目 18:00:28 6
13回目 18:00:29 11
14回目 18:00:30 11
15回目 18:00:31 1
16回目 18:00:32 4
17回目 18:00:33 12
18回目 18:00:34 16
19回目 18:00:35 7
20回目 18:00:36 11
21回目 18:00:37 10
22回目 18:00:38 20
23回目 18:00:39 21
24回目 18:00:40 20
25回目 18:00:41 20
26回目 18:00:42 19
27回目 18:00:43 15
28回目 18:00:44 17
29回目 18:00:45 28
30回目 18:00:46 26

大体、5秒に1回くらいキューの数がガクッと下がっているけど、理由がイマイチ分からん。。これはもう少し調べる必要がありそうだけど、とりあえず次は受信してみる。

メッセージの受信

キューに溜まっている30個のメッセージを、スクリプトを使って全て受信する。

受信用スクリプト

#!/bin/bash

SQS_QUEUE_NAME="handson-cli-sqs-novice-queue"
SQS_QUEUE_URL=$(aws sqs get-queue-url \
    --queue-name ${SQS_QUEUE_NAME} \
    --output text)

DIR_SQS_MESSAGE="${HOME}/environment/tmp-handson-cli-sqs"


for ((i=1; i<=30; i++))
do
    FILE_SQS_MESSAGE="${DIR_SQS_MESSAGE}/${SQS_QUEUE_NAME}-${i}.json"
    
    echo "${i}回目のメッセージ受信です。今は$(date +%H:%M:%S)です。"
    
    aws sqs receive-message --queue-url "${SQS_QUEUE_URL}" > ${FILE_SQS_MESSAGE}
    
    SQS_MESSAGE_BODY="受信したメッセージは \
    $(cat ${FILE_SQS_MESSAGE} | jp.py 'Messages[].Body' | sed 's/[]\"\[]//g') \
    です。" \
    && echo ${SQS_MESSAGE_BODY}

    SQS_MESSAGE_NUMBER="キューのメッセージ数は \
    $(aws sqs get-queue-attributes \
    --queue-url ${SQS_QUEUE_URL} \
    --attribute-names ApproximateNumberOfMessages \
    --query 'Attributes.ApproximateNumberOfMessages' \
    --output text) \
    です。" \
    && echo ${SQS_MESSAGE_NUMBER}
done

今回はハンズオンの手順に倣い、受信したメッセージを一時ファイルに保存する。その際、メッセージ毎に個別の一時ファイルを作成することにした。(後でメッセージを削除する時に、その方が楽そうなので。詳細は後述。)

スクリプトの内容について簡単に触れておくと、まずは何回目の受信であるか最初に表示する。その後、aws sqs receive-messageコマンドでメッセージを受信し、受信したメッセージの内容を表示する。

もしキューが順序を保証するなら、1回目に受信したメッセージの中身は「1回目のメッセージ送信です。」だし、10回目に受信したものは「10回目のメッセージ送信です。」になるはずだが、どれくらいずれるのだろうか。

また、今回もApproximateNumberOfMessagesを表示させるようにした。メッセージを受信してもキューから削除されるわけではないが、可視性タイムアウトが効いて数字がどんどん減るのかな?

実際に受信してみた

[ec2-user@ip-10-0-0-127%]$ sh Message_receive.sh 
1回目のメッセージ受信です。今は18:04:12です。
受信したメッセージは 3回目のメッセージ送信です。今は18:00:19です。 です。
キューのメッセージ数は 29 です。
2回目のメッセージ受信です。今は18:04:13です。
受信したメッセージは 4回目のメッセージ送信です。今は18:00:20です。 です。
キューのメッセージ数は 30 です。
3回目のメッセージ受信です。今は18:04:14です。
受信したメッセージは 13回目のメッセージ送信です。今は18:00:29です。 です。
キューのメッセージ数は 28 です。
4回目のメッセージ受信です。今は18:04:15です。
受信したメッセージは 1回目のメッセージ送信です。今は18:00:17です。 です。
キューのメッセージ数は 26 です。
5回目のメッセージ受信です。今は18:04:16です。
受信したメッセージは 15回目のメッセージ送信です。今は18:00:31です。 です。
キューのメッセージ数は 29 です。
6回目のメッセージ受信です。今は18:04:17です。
受信したメッセージは 6回目のメッセージ送信です。今は18:00:22です。 です。
キューのメッセージ数は 29 です。
7回目のメッセージ受信です。今は18:04:18です。
受信したメッセージは 20回目のメッセージ送信です。今は18:00:36です。 です。
キューのメッセージ数は 30 です。
8回目のメッセージ受信です。今は18:04:20です。
受信したメッセージは 21回目のメッセージ送信です。今は18:00:37です。 です。
キューのメッセージ数は 24 です。
9回目のメッセージ受信です。今は18:04:21です。
受信したメッセージは 7回目のメッセージ送信です。今は18:00:23です。 です。
キューのメッセージ数は 21 です。
10回目のメッセージ受信です。今は18:04:22です。
受信したメッセージは 10回目のメッセージ送信です。今は18:00:26です。 です。
キューのメッセージ数は 21 です。

-------中略-------

21回目のメッセージ受信です。今は18:04:34です。
受信したメッセージは 23回目のメッセージ送信です。今は18:00:39です。 です。
キューのメッセージ数は 18 です。
22回目のメッセージ受信です。今は18:04:35です。
受信したメッセージは 29回目のメッセージ送信です。今は18:00:45です。 です。
キューのメッセージ数は 15 です。
23回目のメッセージ受信です。今は18:04:36です。
受信したメッセージは 16回目のメッセージ送信です。今は18:00:32です。 です。
キューのメッセージ数は 11 です。
24回目のメッセージ受信です。今は18:04:37です。
受信したメッセージは 18回目のメッセージ送信です。今は18:00:34です。 です。
キューのメッセージ数は 11 です。
25回目のメッセージ受信です。今は18:04:38です。
受信したメッセージは 26回目のメッセージ送信です。今は18:00:42です。 です。
キューのメッセージ数は 5 です。
26回目のメッセージ受信です。今は18:04:39です。
受信したメッセージは 24回目のメッセージ送信です。今は18:00:40です。 です。
キューのメッセージ数は 5 です。
27回目のメッセージ受信です。今は18:04:40です。
受信したメッセージは 25回目のメッセージ送信です。今は18:00:41です。 です。
キューのメッセージ数は 14 です。
28回目のメッセージ受信です。今は18:04:41です。
受信したメッセージは 17回目のメッセージ送信です。今は18:00:33です。 です。
キューのメッセージ数は 6 です。
29回目のメッセージ受信です。今は18:04:42です。
受信したメッセージは 19回目のメッセージ送信です。今は18:00:35です。 です。
キューのメッセージ数は 9 です。
30回目のメッセージ受信です。今は18:04:43です。
受信したメッセージは 27回目のメッセージ送信です。今は18:00:43です。 です。
キューのメッセージ数は 13 です
[ec2-user@ip-10-0-0-127%]$
[ec2-user@ip-10-0-0-127%]$ 

ちょっと見づらい表示になってしまったが、表にまとめると以下のような結果になった。

受信回数 受信時刻 何回目の送信メッセージを受信したか ApproximateNumberOfMessages
1回目 18:04:12 3回目 29
2回目 18:04:13 4回目 30
3回目 18:04:14 13回目 28
4回目 18:04:15 1回目 26
5回目 18:04:16 15回目 29
6回目 18:04:17 6回目 29
7回目 18:04:18 20回目 30
8回目 18:04:20 21回目 24
9回目 18:04:21 7回目 21
10回目 18:04:22 10回目 21
11回目 18:04:23 22回目 24
12回目 18:04:24 30回目 26
13回目 18:04:25 14回目 18
14回目 18:04:26 8回目 19
15回目 18:04:27 9回目 21
16回目 18:04:28 2回目 23
17回目 18:04:29 5回目 28
18回目 18:04:30 28回目 15
19回目 18:04:31 11回目 19
20回目 18:04:32 12回目 13
21回目 18:04:34 23回目 18
22回目 18:04:35 29回目 15
23回目 18:04:36 16回目 11
24回目 18:04:37 18回目 11
25回目 18:04:38 26回目 5
26回目 18:04:39 24回目 5
27回目 18:04:40 25回目 14
28回目 18:04:41 17回目 6
29回目 18:04:42 19回目 9
30回目 18:04:43 27回目 13

こうして確認してみると、見事に順番がバラバラだった。受信と送信の順序が一致したのは30回中2回だけだった。「順序を保証しない」というから「多少ずれることもあるのかな?」くらいに思っていたが、「ほぼランダム」と言った方がしっくりくるような結果で意外だった。今回たまたま極端にずれたのだろうか?

また、ApproximateNumberOfMessagesは減ったが、送信時同様、キレイな減り方はしなかった。

なお、先述した通り、受信してもキューからメッセージが削除されるわけではないので、少し時間を空けてから再度キューの数を確認すると、想定通り30と表示された。

[ec2-user@ip-10-0-0-127%]$ date
Sat Jun 12 18:09:32 UTC 2021
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws sqs get-queue-attributes \
>   --queue-url ${SQS_QUEUE_URL} \
>   --attribute-names ApproximateNumberOfMessages \
>   --query 'Attributes.ApproximateNumberOfMessages' \
>   --output text
30
[ec2-user@ip-10-0-0-127%]$ 

一時ファイルの確認

受信自体は問題なく出来たので、受信メッセージを格納した一時ファイルがちゃんと存在するか確認する。

[ec2-user@ip-10-0-0-127%]$ ls ./tmp-handson-cli-sqs/
2021-06-12-handson-cli-sqs-novice-queue.json  handson-cli-sqs-novice-queue-16.json  handson-cli-sqs-novice-queue-22.json  handson-cli-sqs-novice-queue-29.json  handson-cli-sqs-novice-queue-7.json
handson-cli-sqs-novice-queue-10.json          handson-cli-sqs-novice-queue-17.json  handson-cli-sqs-novice-queue-23.json  handson-cli-sqs-novice-queue-2.json   handson-cli-sqs-novice-queue-8.json
handson-cli-sqs-novice-queue-11.json          handson-cli-sqs-novice-queue-18.json  handson-cli-sqs-novice-queue-24.json  handson-cli-sqs-novice-queue-30.json  handson-cli-sqs-novice-queue-9.json
handson-cli-sqs-novice-queue-12.json          handson-cli-sqs-novice-queue-19.json  handson-cli-sqs-novice-queue-25.json  handson-cli-sqs-novice-queue-3.json
handson-cli-sqs-novice-queue-13.json          handson-cli-sqs-novice-queue-1.json   handson-cli-sqs-novice-queue-26.json  handson-cli-sqs-novice-queue-4.json
handson-cli-sqs-novice-queue-14.json          handson-cli-sqs-novice-queue-20.json  handson-cli-sqs-novice-queue-27.json  handson-cli-sqs-novice-queue-5.json
handson-cli-sqs-novice-queue-15.json          handson-cli-sqs-novice-queue-21.json  handson-cli-sqs-novice-queue-28.json  handson-cli-sqs-novice-queue-6.json
[ec2-user@ip-10-0-0-127%]$
[ec2-user@ip-10-0-0-127%]$ cat ./tmp-handson-cli-sqs/handson-cli-sqs-novice-queue-15.json
{
    "Messages": [
        {
            "MessageId": "14dd0d6e-9610-421a-9701-2e02e35d594c",
            "ReceiptHandle": "AQEBvvqm5ra499xjlsBW0ljchmxtHbBs7485HxIjhXbQ2JJw3ev/rFAT/GQqboUaIvLl+y6an6hyiKA5ww8WEOIqCdDJBKUYIb4r9Q07pY6jpYNbTwb3vrJL4dFFAzAYSqN2WRtIyj5zVg88V/b3w9OTqNjclZal6YzsXonLAiOUqfgiIWhe4+1xkfEOma04bUEJUKp6IOxan9L8UocXd4JO9GtUQ1ZXqN+uTQ+dzC9KP7TAAQGfuYM9V1TIRlwYGQnLrXllbho1OPMFYThD7nQMSKC1wl0491try1Btd9sSrfdNwHHk/A4SN4Y8xZBq5NM1a4ya32+9V1TmkDqr89SvFtLoutlTwVgQDSA6ApyYVVVEINEJGBg3APWNkrNk7HVS4uVKvbtkP1voINCbMAnJmIvXWqfkNz4LPeL8P9ug01g=",
            "MD5OfBody": "14ce526cc7ccda226024934d5f854684",
            "Body": "9回目のメッセージ送信です。今は18:00:25です。"
        }
    ]
}
[ec2-user@ip-10-0-0-127%]$

ちゃんとファイルは存在したし、メッセージもちゃんと入っているようで一安心。

メッセージを見ると、ReceiptHandleというやたら長い文字列があるが、今回はメッセージを削除する際にこれを指定する。

メッセージの削除

送受信が問題なく出来たので、30個分のメッセージを削除する。削除もスクリプトで実施する。

削除用スクリプト

#!/bin/bash

SQS_QUEUE_NAME="handson-cli-sqs-novice-queue"
SQS_QUEUE_URL=$(aws sqs get-queue-url \
    --queue-name ${SQS_QUEUE_NAME} \
    --output text)
    
DIR_SQS_MESSAGE="${HOME}/environment/tmp-handson-cli-sqs"


for ((i=1; i<=30; i++))
do
    FILE_SQS_MESSAGE="${DIR_SQS_MESSAGE}/${SQS_QUEUE_NAME}-${i}.json"

    echo "${i}回目のメッセージ削除です。今は$(date +%H:%M:%S)です。"

    SQS_MESSAGE_RECEIPT_HANDLE=$(cat ${FILE_SQS_MESSAGE} | jp.py 'Messages[].ReceiptHandle' | sed 's/[]\"\[]//g') \
    && echo ${SQS_MESSAGE_RECEIPT_HANDLE}
    
    aws sqs delete-message --queue-url "${SQS_QUEUE_URL}" --receipt-handle ${SQS_MESSAGE_RECEIPT_HANDLE}

    SQS_MESSAGE_NUMBER="キューのメッセージ数は \
    $(aws sqs get-queue-attributes \
    --queue-url ${SQS_QUEUE_URL} \
    --attribute-names ApproximateNumberOfMessages \
    --query 'Attributes.ApproximateNumberOfMessages' \
    --output text) \
    です。" \
    && echo ${SQS_MESSAGE_NUMBER}
done

受信時に作成した一時ファイルからReceiptHandleの値を抜き出し、aws sqs delete-messageコマンドでキューのURLとReceiptHandleを指定してメッセージを削除する。(メッセージ毎に一時ファイルを分けていたのは、その方がReceiptHandleを抜き出しやすそうだったため。)

また、今回もしつこくApproximateNumberOfMessagesの値を表示させる。今回はキューからメッセージを削除するので、値はドンドン減っていくのではないだろうか。

実際に削除してみた

[ec2-user@ip-10-0-0-127%]$ sh Message_delete.sh 
1回目のメッセージ削除です。今は18:12:53です。
AQEBPJze/hVJusVhMW1qezwrPBj+uP9ZS1yymJEDMepsYTNMrLxOR7uqN0ihCfSSgOE7c1k7wg06YzJ6XvyVyWSWdZFbu3sZ+ZW5maqMR8Af5v47ts7CK9FWItVFB3BcjcRXW1hdYFSQjhPU30b27Ux0T+/AgmTrWgUhzwley1/IOm4fkaXgJMS4bVPKezU0q79KGelXr3x8avoOGhb4Z15hjQh6d2l4YSW1rE5diDl557iiQBWMDMT1P3p0x3ppY7z65Vt1oTGmORV7XO/u/W0CYIfpDxUVxk+wo7AuyeZcrzKmfyw8NSBGIEzfLMsKocoLUwzcNgxq6Q+oRYrNrFiFq/lyf3tCUUwhRqdAqDrQ6sd278nXQemxt63f1hzpuxcLnt4Ri2TNRS13z8IlRIpiQPY32Ol1tHlsmJW094JxmEs=
キューのメッセージ数は 29 です。
2回目のメッセージ削除です。今は18:12:54です。
AQEBoB+hkt853aeqST1xsAERiO0Alu6g7jwUkE5JeRneSkgwWpA699LngmxqEBKf6FV1DWn8nvA38aaoyfaViV9yxj4C5/Snr6bi/KKxpL06tXFdrNZONh83pQIcDG5UFWNXEF1FAAKH2Avxh00KiHtLH6mnpJ/jPiAwo6HMMXVtgWWzpKY36rMjCJa0hAl/Qk176FX+Qlsk34+2qlra6wihdyakwfhpqtQ7E22JfBjlcYWWcvsUjmX6hNbYQACi+CIZHMA7WrDBTFsrjzLWj0wToT6c/beF/uMNNCe4jWtx5MHda95IQqtXhSrKefieMkrDVimX3qwpdmorl5ekA6mk/tfHBpfbsD0dsP1xWU0QwyPIpo/b++5ptGlbRviu/IAsQd4pz1goYqL4XykeR12X+8qJUdGTZ7SQiK2vIVT2Zl0=
キューのメッセージ数は 30 です。
3回目のメッセージ削除です。今は18:12:55です。
AQEBJ+FMrXQewt3Duf9B1HzvKBtH4GIe7iCxrBKSG3SOGWHfvy6ciwvfzEtN8OWrDpK3iZsEOZSK3HCOB+PLjS70dMoWxVnThf8Xg5OXBMRlumf/s0Z3U7x8ZlfCR0jItMpRCvURkjxFKROVgJMwZMdeNQ1r3Y/0r/9B32ckMbrkxMg880N+g7xxoQ6sWe0jVjrfMi15UfP6nJrAzPmcTqPi8LdETnY7w0IE5CrZscC3lPIn4TPXqns2QWnM55Gq1NXTzf2UEZP7YmORx213WavwAIwYhZumseJaUaiEYhQkv2T9eevp4+UqfpaEqjal+7e5xna7hyzB3L1nn0PM5v5x6yOzBBQvMROAddV+sJ2TyvDa/9ABMQAxnhaum0Vrd4jya0K3jlcbzmrpF1v3dkYNdPQqybcUTYmHQEW40QJgb6M=
キューのメッセージ数は 29 です。

-------中略-------

28回目のメッセージ削除です。今は18:13:22です。
AQEBVubFV8H/oQ3TYKeKhdk9pCO+f2KFdCeasVoGcj6vKhIIXb8cCm8FXiHBb+x9Ef3GEMXscH8NR20DDa2iQdglMzcVvuWkgzQ4DwfaA0qVdirpXj0OIX5tvuYLx6375eBGLObat3FOr9Emyc1bTvRu5QuXvyUK83ECoxnlqJh2hhqJEtMPuyg+n34deDfqNXUL9xNR3StDXRPz5ZaE9oJ2ic2XC5ELlqR5A7r7I2OGzD3UOzLF+CSpxbO9/KqrkOXWDZ0zcWLxYQEqg+WvrOFJpPh8mOjvWT91QAoaBO0uWGrwbxCx/UMNIMus6sTGp+rc/AZad6hHUuuXdcKigKsA22m0WBvsBnlMcplm2Rllo7Jcteuij7hSS2kYXjEJAe/5xbAhKC9O+GfEihUAM25QLXX5D/vLagZ028YZeYFAAns=
キューのメッセージ数は 4 です。
29回目のメッセージ削除です。今は18:13:23です。
AQEBFHWzCcY2bUBwkDKSrDvg7XSsIHJ1OAbiacEPa2jfSV6lUFhkDBfnHTdrpspmzn7TEJ57voH2L2E1AHI9WPENn0YUZc7TMZx7kkM42muIy9nNxvZNP8zAfnj1sNNCpdvUsku6RyIR/Rv9KDz27m3nBtgaiTjM+JV1GmG7KWCnJ5XAyXWJsxHihGs/kw3aBMLhBojUSjEb0/96ZzvN2A+yDQAfsvie1XmydB6tN1M0Iygy5r1Jx6HRli9EU9bx+4oBWTjJ11Bj0YmadfSBUR3g+blnJNsUdMuq3bOEKrk64DiZSNOHy2E3xjo6C3PJ8EZpnDH6aGL1rFtQSslAFdl5qNdj5JibPJT9dP0UutEhbeUBG0DI9vu573IJF+B1LpeY1sMWk7C9vqfsmHsV8Uo+TWjG4/D8Zir0MZjjiBG4FAQ=
キューのメッセージ数は 12 です。
30回目のメッセージ削除です。今は18:13:24です。
AQEB6vCTH+YGzL8eLisOjiHSJm/NoHCEmpFYcsBEypeFtW/xPjnuV53rReiuvaZlbq7SFcNs0YI5Sd5stVGSGI47aWRYx+osVCNHjXCe+K1fY+S+g47zrW0eZM1A+AmSTZPobfpl0C2YsQXyGonuEoKwBKS7hCHezdkX+bN5FiAXLbnd1wgJ+LG56aFXlDSWXhtOJR7ai7czbZT3hr6kbi3lucpml4ekHBUL6YooLVYDyh52DGFftVhGooxZJsbAo58bIj7L+6dTLMJfDqD6Ynwa3rqd7ERrewGqgTseZ6bpi4ubiLIXbeC1LVQtYWTBeRSl1TqodCMNBG6BukJKGmAslsJwlKnu8yWoZaackfZw0jn738na8rW3bxUg8rZtrkzwi3GrOxQ6iNOdrPgl4vgSOvDBYl9IiBnyx9Kd1NBdAfY=
キューのメッセージ数は 1 です。
[ec2-user@ip-10-0-0-127%]$
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ aws sqs get-queue-attributes \
>   --queue-url ${SQS_QUEUE_URL} \
>   --attribute-names ApproximateNumberOfMessages \
>   --query 'Attributes.ApproximateNumberOfMessages' \
>   --output text
0
[ec2-user@ip-10-0-0-127%]$

問題なく削除できた。ReceiptHandleがちゃんと取得できているか分かるようにechoで表示させていたが、文字列が多いので出さない方が良かったかな。あと、一時ファイルの削除もスクリプトに組み込めば良かった。

ApproximateNumberOfMessagesの値については、結局今回もキレイに減ることはなかった。

おわりに

ApproximateNumberOfMessagesについては改めて確認が必要なものの、標準キューの順序が想像以上にバラバラなのが確認できたので良かった。あと、スクリプトを書くのは楽しい。

JAWS-UG CLI専門支部 #181R SQS入門を噛みしめながら復習する(①事前作業編)

はじめに

先日、JAWS-UG CLI専門支部 #181R SQS入門に参加した。

jawsug-cli.connpass.com

CloudShell、Cloud9を使ってSQSを作成、メッセージの送付/受信/削除を実施した。忘れないうちに、再度復習を実施する。
前回の復習同様、出来るだけ一つ一つのコマンドの中身を噛みしめながら復習していく。

※とか何とか言っていたら、噛みしめ過ぎてしまい、事前作業だけで1記事分のボリュームになってしまった。。仕方ないので、2回に分けて記載することにする。

IAMロールへのポリシーアタッチ

今回はCloud9でSQSを扱うので、Cloud9用のIAMロールに対し、IAMポリシー"AmazonSQSFullAccess"をアタッチする。IAMポリシーのアタッチはCloudShellで実施する。

変数の準備

前回のハンズオン同様、まずは変数を用意する。

[cloudshell-user@ip-10-0-167-94 ~]$ IAM_ROLE_NAME='handson-cloud9-environment-role'
[cloudshell-user@ip-10-0-167-94 ~]$ IAM_POLICY_NAME='AmazonSQSFullAccess'
[cloudshell-user@ip-10-0-167-94 ~]$ 
[cloudshell-user@ip-10-0-167-94 ~]$ echo ${IAM_ROLE_NAME} ; echo ${IAM_POLICY_NAME}
handson-cloud9-environment-role
AmazonSQSFullAccess
[cloudshell-user@ip-10-0-167-94 ~]$ 
[cloudshell-user@ip-10-0-167-94 ~]$ set | grep IAM_*
IAM_POLICY_NAME=AmazonSQSFullAccess
IAM_ROLE_NAME=handson-cloud9-environment-role
[cloudshell-user@ip-10-0-167-94 ~]$ 

続いて、"AmazonSQSFullAccess"のARNを取得する。IAMロールにポリシーをアタッチする際に、ポリシー名ではなくARNで指定する必要があるため。

[cloudshell-user@ip-10-0-167-94 ~]$ IAM_POLICY_ARN=$( \
>   aws iam list-policies \
>     --scope AWS \
>     --max-items 1000 \
>     --query "Policies[?PolicyName==\`${IAM_POLICY_NAME}\`].Arn" \
>     --output text \
> ) \
>   && echo "${IAM_POLICY_ARN}"
arn:aws:iam::aws:policy/AmazonSQSFullAccess
[cloudshell-user@ip-10-0-167-94 ~]$

ARNの取得部分のコマンドの詳細は以下。

コマンド 内容
aws iam list-policies IAMポリシーの一覧を表示する
--scope AWS AWS管理ポリシーに絞る
--max-items 1000 表示の最大数を1000にする
--query "Policies[?PolicyName==${IAM_POLICY_NAME}].Arn" PolicyNameが"AmazonSQSFullAccess"(変数IAM_POLICY_NAMEの中身)のポリシーの、ARN部分を指定する
--output text 結果をJSON形式ではなくテキスト形式で出力する

ちなみに、aws iam list-policiesコマンドをそのまま出力した結果はこんな感じだった。(※全部出力するととんでもない量になるので、headで冒頭20行だけ出してみた)

[cloudshell-user@ip-10-0-35-205 ~]$ aws iam list-policies --scope AWS | head -n 20
{
    "Policies": [
        {
            "PolicyName": "AWSDirectConnectReadOnlyAccess",
            "PolicyId": "ANPAI23HZ27SI6FQMGNQ2",
            "Arn": "arn:aws:iam::aws:policy/AWSDirectConnectReadOnlyAccess",
            "Path": "/",
            "DefaultVersionId": "v4",
            "AttachmentCount": 0,
            "PermissionsBoundaryUsageCount": 0,
            "IsAttachable": true,
            "CreateDate": "2015-02-06T18:40:08+00:00",
            "UpdateDate": "2020-05-18T18:48:22+00:00"
        },
        {
            "PolicyName": "AmazonGlacierReadOnlyAccess",
            "PolicyId": "ANPAI2D5NJKMU274MET4E",
            "Arn": "arn:aws:iam::aws:policy/AmazonGlacierReadOnlyAccess",
            "Path": "/",
            "DefaultVersionId": "v2",
[cloudshell-user@ip-10-0-35-205 ~]$ 
[cloudshell-user@ip-10-0-35-205 ~]$ 
[cloudshell-user@ip-10-0-35-205 ~]$ aws iam list-policies --scope Local | head -n 20
{
    "Policies": [
        {
            "PolicyName": "AWSQuickSightIAMPolicy",
            "PolicyId": "ANPATO53NFKUB4O64JFIN",
            "Arn": "arn:aws:iam::xxxxxxxxxxxx:policy/service-role/AWSQuickSightIAMPolicy",
            "Path": "/service-role/",
            "DefaultVersionId": "v1",
            "AttachmentCount": 1,
            "PermissionsBoundaryUsageCount": 0,
            "IsAttachable": true,
            "CreateDate": "2021-05-06T14:26:15+00:00",
            "UpdateDate": "2021-05-06T14:26:15+00:00"
        },
        {
            "PolicyName": "handson-Cloud9EnvironmentOwner-policy",
            "PolicyId": "ANPATO53NFKUB6T4HF7QC",
            "Arn": "arn:aws:iam::xxxxxxxxxxxx:policy/handson-cloud9/handson-Cloud9EnvironmentOwner-policy",
            "Path": "/handson-cloud9/",
            "DefaultVersionId": "v1",
[cloudshell-user@ip-10-0-35-205 ~]$

頭にPoliciesがあって、その中にPolicyName, PolicyId, Arnなどが格納されているのが分かる。そのため、先ほどの--queryでは、Arnを指定するためにPolicies[].Arnという記載を行っていた事が分かる。
また、--scopでAWSを指定した場合とLocalを指定した場合で出力が変わっていることも分かる。AWSを指定した場合は、AWS管理ポリシー(AWSがあらかじめ用意しているポリシー)が出力されていて、Localを指定した場合は、カスタマー管理ポリシー(ユーザーが自分で作成したポリシー)が出力されている。そのため、Localの方のポリシーは、ArnにアカウントID(xxxでマスキングしている所)が含まれている。

IAMロールへのIAMポリシーのアタッチ

だいぶ脱線してしまったが、これで変数の取得が終わったので、IAMロールにIAMポリシーをアタッチする。IAMロールへのポリシーのアタッチはaws iam attach-role-policyコマンドを使用する。(管理ポリシーの場合。インラインポリシーの場合はaws iam put-role-policyコマンドになる。管理ポリシーとインラインポリシーの違いはこちらを参照。)

[cloudshell-user@ip-10-0-35-205 ~]$ aws iam attach-role-policy \
>   --role-name ${IAM_ROLE_NAME} \
>   --policy-arn ${IAM_POLICY_ARN}
[cloudshell-user@ip-10-0-35-205 ~]$

問題なくアタッチされたようなので、確認してみる。ロールにアタッチされた管理ポリシーを確認するには、aws iam list-attached-role-policiesコマンドを使用する。(インラインポリシーの場合はaws iam list-role-policies

[cloudshell-user@ip-10-0-35-205 ~]$ aws iam list-attached-role-policies \
>   --role-name ${IAM_ROLE_NAME} \
>   --query "AttachedPolicies[?PolicyName == \`${IAM_POLICY_NAME}\`].PolicyName" \
>   --output text
AmazonSQSFullAccess
[cloudshell-user@ip-10-0-35-205 ~]$

問題なくアタッチされていることを確認した。念のためマネコンでも確認してみる。

f:id:YuY_83:20210606004214p:plain
IAMポリシーアタッチ確認
問題なくアタッチされていた。加えて、ReadOnlyAccessもアタッチされている。先ほどのコマンドで、queryでPolicyNameでフィルタしなければ、両方出力されるだろうか。

[cloudshell-user@ip-10-0-35-205 ~]$ aws iam list-attached-role-policies \
>   --role-name ${IAM_ROLE_NAME} \
>   --query "AttachedPolicies[].PolicyName" \
>   --output text
AmazonSQSFullAccess     ReadOnlyAccess
[cloudshell-user@ip-10-0-35-205 ~]$ 
[cloudshell-user@ip-10-0-35-205 ~]$

想定通り、二つとも出力された。

おわりに

当日のハンズオンでは5分くらい?であっという間に終わらせた事前準備だが、あれこれ脱線して調べてみると、色々気づきがあって面白い。とはいえ、SQS入門というタイトルなのに、今回はSQSのことを何もしていない。。さすがにこのままではアレなので、次回はちゃんとSQSに関する作業を復習してブログにする。

CloudShellでIAMポリシー/IAMロールを削除しようとして詰まった話

はじめに

先日、以下のハンズオンを実施した。

docs.aws.amazon.com

Lambdaの使い方がよく分かっていないこともあり、関数の作成や呼び出しは手順通りマネジメントコンソールで実行。問題なく関数呼び出しが出来た。

手順の最後にチュートリアル環境の削除手順があり、これもマネコンでの作業手順が記載されているが、せっかく最近CloudShellを触ったので、せめて最後の削除だけはCloudShellで実行してみることにした。そうしたら、IAMポリシー/IAMロールの削除で思うようにいかなかった場面があったので、備忘として記載することにした。

IAMポリシー削除

まずは以下のWEBページでCLIの削除手順を軽く確認。

docs.aws.amazon.com

最初はポリシーのリストを確認するようなので、とりあえずやってみる。

[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam list-policies | grep AWSLambdaS3E*
            "PolicyName": "AWSLambdaS3ExecutionRole-50e074db-f735-45b9-867f-2f1ca04f5ce5",
            "Arn": "arn:aws:iam::xxxxxxxxxxxx:policy/service-role/AWSLambdaS3ExecutionRole-50e074db-f735-45b9-867f-2f1ca04f5ce5",
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ 

表示されたので、消そうとしてみる。

[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam delete-policy \
> --policy-arn arn:aws:iam::xxxxxxxxxxxx:policy/service-role/AWSLambdaS3ExecutionRole-50e074db-f735-45b9-867f-2f1ca04f5ce5

An error occurred (DeleteConflict) when calling the DeletePolicy operation: Cannot delete a policy attached to entities.
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$

「エンティティにアタッチされているポリシーは削除できない」というエラーが出てしまった。

ただ、ハンズオンの手順だと、いきなりポリシーを削除することになっている。マネコンとCLIでは勝手が違うのだろうか。

試しにマネコン上での削除を試みる。

f:id:YuY_83:20210603230939p:plain
マネコンのIAMポリシー削除確認画面

マネコンだとエンティティのデタッチを自動で実施してくれるのか。うーむ、マネコン、侮れん。

再度CloudShellに戻って、ポリシーに割り当たっているエンティティについて確認する。aws iam list-entities-for-policy --policy-arnコマンドで、指定したIAMポリシーにアタッチされているエンティティのリストが表示される模様。想定では、ハンズオンで作成したIAMロールが表示されるはず。

[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam list-entities-for-policy \
> --policy-arn arn:aws:iam::xxxxxxxxxxxx:policy/service-role/AWSLambdaS3ExecutionRole-50e074db-f735-45b9-867f-2f1ca04f5ce5
{
    "PolicyGroups": [],
    "PolicyUsers": [],
    "PolicyRoles": [
        {
            "RoleName": "my-s3-function-role",
            "RoleId": "AROATO53NFKUGNFFORV4I"
        }
    ]
}
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$

想定通り表示された。今度はCLI上でデタッチをやってみる。デタッチについてはaws iam detach-role-policyコマンドで、デタッチしたいロールと、現在アタッチされているポリシーを指定する模様。

[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam detach-role-policy \
> --role-name my-s3-function-role \
> --policy-arn arn:aws:iam::xxxxxxxxxxxx:policy/service-role/AWSLambdaS3ExecutionRole-50e074db-f735-45b9-867f-2f1ca04f5ce5
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ 
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ 

デタッチコマンドが通ったので、改めてエンティティのアタッチ状況を確認する。

[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam list-entities-for-policy --policy-arn arn:aws:iam::xxxxxxxxxxxx:policy/service-role/AWSLambdaS3ExecutionRole-50e074db-f735-45b9-867f-2f1ca04f5ce5
{
    "PolicyGroups": [],
    "PolicyUsers": [],
    "PolicyRoles": []
}
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ 
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ 

デタッチされた模様。では、改めてポリシーを削除する。

[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam delete-policy --policy-arn arn:aws:iam::xxxxxxxxxxxx:policy/service-role/AWSLambdaS3ExecutionRole-50e074db-f735-45b9-867f-2f1ca04f5ce5
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ 

削除成功。

IAMロール削除

続いて、IAMロールを削除する。まずはコマンドを以下のWEBページで確認する。

docs.aws.amazon.com

とりあえず、ページに記載の通りにやってみる。aws iam list-rolesコマンドを実行したら、現在作成されているロールがJSON形式で全て表示された。よくページ見たら、「対象ロール名が分からない場合」と書いてあった。今回は必要なかったな。

気を取り直して、次のコマンドから実行する。

[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam list-instance-profiles-for-role --role-name my-s3-function-role
{
    "InstanceProfiles": []
}
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$  
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam list-role-policies --role-name my-s3-function-role
{
    "PolicyNames": []
}
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ 

特に何も割り当たっていないように見えるので、削除コマンドを実行する。

[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam delete-role --role-name my-s3-function-role

An error occurred (DeleteConflict) when calling the DeleteRole operation: Cannot delete entity, must detach all policies first.
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ 

消せない。「ロールを消したきゃ、まずポリシーからデタッチしろ」とエラーが出た。なんでだ?

他のコマンドで改めてポリシーのアタッチ状況を確認してみる。

[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam list-attached-role-policies --role-name my-s3-function-role
{
    "AttachedPolicies": [
        {
            "PolicyName": "AWSLambdaBasicExecutionRole-b6e2f444-ac64-4852-8685-d03f9e4b131c",
            "PolicyArn": "arn:aws:iam::xxxxxxxxxxxx:policy/service-role/AWSLambdaBasicExecutionRole-b6e2f444-ac64-4852-8685-d03f9e4b131c"
        }
    ]
}
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$

なんか割り当たってる!?状況がよく飲み込めていないが、とりあえずこれをデタッチして、再度削除を試みる。

[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam detach-role-policy \
> --role-name my-s3-function-role \
> --policy-arn arn:aws:iam::xxxxxxxxxxxx:policy/service-role/AWSLambdaBasicExecutionRole-b6e2f444-ac64-4852-8685-d03f9e4b131c
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ 
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam list-attached-role-policies --role-name my-s3-function-role
{
    "AttachedPolicies": []
}
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$ aws iam delete-role --role-name my-s3-function-role
[cloudshell-user@ip-10-xxx-xxx-xxx ~]$

消せた。。いったい全体どういうことなのか。

改めて調べてみた

IAMポリシーには「管理ポリシー」と「インラインポリシー」があり、最初に実行したaws iam list-role-policiesインスタンスポリシーの一覧を、後で実行したaws iam list-attached-role-policiesは管理ポリシーの一覧を表示するものらしい。

管理ポリシーとインラインポリシーの違いについては、ザックリいうと、複数のIAMアイデンティティ(ユーザー、グループ、ロール)に割り当て可能なものが管理ポリシーで、特定のIAMアイデンティティと完全に紐づけされるものがインラインポリシーらしい。IAMにおいては通常は管理ポリシーを利用するのがベストプラクティスな模様。インラインポリシーは、特定のアイデンティティにのみ適用したいポリシーがあり、他とポリシーを共有することによって意図せぬ変更が発生することを避けたいケースで使う。

なお、バケットポリシーのようなリソースベースのポリシーは、必ずインラインポリシーになる模様。

管理ポリシーとインラインポリシーの違いについては、以下のサイトを参考にした。

dev.classmethod.jp

docs.aws.amazon.com

qiita.com

dev.classmethod.jp

おわりに

最初はCloudShellで設定を削除するだけのつもりだったが、思いがけずポリシーの種類の勉強になった。実際に手を動かすと、「なんか上手くいかない → 色々調べる → 知らないまま使っていたものの仕組みが分かる」という学習効果が得られるので、今後も積極的に手を動かしていきたい。