Y_Yamashitaのブログ

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

CloudFormationでVPCピアリングを設定し通信確認してみた

はじめに

これまで、本ブログでの「やってみた」系の記事は全て、JAWS-UGハンズオンの復習だけでしたが、そろそろ自分でテーマを決めて構築や検証をしてみようと思います。

その際、今後の業務利用も見据え、構築は出来るだけCloudFormation等のIaCツールを使用して実施してみます。それと、折角JAWS-UG CLI専門支部CLIを扱っているので、リソースの確認はCLIで確認したいと思います。

というわけで、今回はタイトル通り、CloudFormationでVPCピアリングを設定し、通信確認を行ってみます。非常にシンプルな構成なので、入り口としては丁度良さそうです。

VPCピアリングを試そうと思った理由は、ちょうど今「AWS 認定 高度なネットワーキング – 専門知識」の学習中で、「インプットした記憶をアウトプットによって定着させたい」と思ったためです。そのため、今後も資格取得まではネットワーク系の構築と検証が中心になる予定です。(DirectConnectは個人環境で試すのがちょっと難しいですが。。)

今回の構成と試すこと

今回は、3つのVPCを作成し、下図のようにVPCピアリング接続を行い、ルーティング設定を行います。クライアントPCから各EC2にSSH接続するため、インターネット接続設定もしておきます。
また、各EC2のセキュリティグループでは、他のVPCのCIDRからのインバウンド通信は全て許可する設定を入れておきます。 f:id:YuY_83:20210724022934p:plain

VPCピアリングではトランジット接続は出来ないため、たとえルーティング設定と通信許可設定を行っていても、VPC1とVPC3は通信が出来ないことを確認します。 f:id:YuY_83:20210724023118p:plain



今回構築に使用するCloudFormationテンプレ

今回構築に使用するCloudFormationテンプレートですが、このブログに直接貼り付けると結構な行数になってしまうので、こちらに格納しました。

CloudFormationをちゃんと使うのは初めてなので、CIDRブロックやAMI IDがベタ書きだったりして色々と粗がありますが、徐々にマシなものが書けるようになりたいと思います。

あと、実はGitHubを使うのも初めてだったりします。一応IT業界には7~8年くらいいるのですが、今まで全く触れる機会がありませんでした。使わない所は使わないものですね。
とはいえ今後いつ必要になるか分からないし、折角なのでこの機会に使い始めて、慣れておきたいと思います。

CLIによるリソースの確認

上記テンプレートによる環境構築が完了したので、CLIでリソースの確認をしてみます。今回はCloudShellを使用します。

CLIコマンドについては、こちらのブログをかなり参考にさせていただきました。

blog.serverworks.co.jp



# VPCの確認(VPCID、CIDRブロック、状態、テナンシー)
[cloudshell-user@ip-10-0-131-98 ~]$ aws ec2 describe-vpcs \
> --filters "Name=tag-value,Values=handson-vpc*" \
> --output text \
> --query "Vpcs[].[Tags[?Key=='Name'] 
> | [0].Value,VpcId,CidrBlock,State,InstanceTenancy]" \
> | sort
handson-vpc-peering1    vpc-0932bf14cf664577f   10.100.0.0/16   available       default
handson-vpc-peering2    vpc-0f5af64c8262401f0   10.200.0.0/16   available       default
handson-vpc-peering3    vpc-0150bd49c56f4f085   10.50.0.0/16    available       default
[cloudshell-user@ip-10-0-131-98 ~]$ 
[cloudshell-user@ip-10-0-131-98 ~]$ 


#サブネットの確認(VPCID、CIDRブロック、利用可能アドレス数、サブネットID、AZ、状態)
[cloudshell-user@ip-10-0-131-98 ~]$ aws ec2 describe-subnets \
> --filters "Name=tag-value,Values=handson-vpc*" \
> --query "Subnets[].[Tags[?Key=='Name'] \
> | [0].Value,VpcId,CidrBlock,AvailableIpAddressCount,SubnetId,AvailabilityZone,MapPublicIpOnLaunch,State]" \
> --output text \
> | sort
handson-vpc-peering1    vpc-0932bf14cf664577f   10.100.0.0/24   250     subnet-05283462e7cd5044d        ap-northeast-1a True    available
handson-vpc-peering2    vpc-0f5af64c8262401f0   10.200.0.0/24   250     subnet-082fe1abac42bd13c        ap-northeast-1c True    available
handson-vpc-peering3    vpc-0150bd49c56f4f085   10.50.0.0/24    250     subnet-02bf2ccee82e23c16        ap-northeast-1c True    available
[cloudshell-user@ip-10-0-131-98 ~]$ 
[cloudshell-user@ip-10-0-131-98 ~]$ 


#ルートテーブルの確認1(サブネットID、ルートテーブルID、メインルートかどうか、関連付けられたサブネットがあるか)
[cloudshell-user@ip-10-0-131-98 ~]$ aws ec2 describe-route-tables \
> --filters "Name=tag-value,Values=handson-vpc*" \
> --query "RouteTables[].Associations[].[SubnetId,RouteTableId,Main,AssociationState.State]" \
> --output text \
> | sort | column -t
subnet-02bf2ccee82e23c16  rtb-09af531f07f2cd06f  False  associated
subnet-05283462e7cd5044d  rtb-09abdfe97a306a035  False  associated
subnet-082fe1abac42bd13c  rtb-0e3a628ee0d845da7  False  associated
[cloudshell-user@ip-10-0-131-98 ~]$ 
[cloudshell-user@ip-10-0-131-98 ~]$ 


#ルートテーブルの確認2(ルートテーブルID、経路)
[cloudshell-user@ip-10-0-131-98 ~]$ aws ec2 describe-route-tables \
> --filters "Name=tag-value,Values=handson-vpc*" \
> --query "RouteTables[].[Associations[].[RouteTableId],Routes]" \
> --output text
rtb-09af531f07f2cd06f
10.100.0.0/24           CreateRoute     active  pcx-0dbfc21a4ec1e1609
10.200.0.0/24           CreateRoute     active  pcx-0dbfc21a4ec1e1609
10.50.0.0/16    local   CreateRouteTable        active  
0.0.0.0/0       igw-0a6cbcd0152530b46   CreateRoute     active  
rtb-0e3a628ee0d845da7
10.50.0.0/24            CreateRoute     active  pcx-0dbfc21a4ec1e1609
10.100.0.0/24           CreateRoute     active  pcx-0b9cd821777935106
10.200.0.0/16   local   CreateRouteTable        active  
0.0.0.0/0       igw-0628af6c9db7c71d6   CreateRoute     active  
rtb-09abdfe97a306a035
10.50.0.0/24            CreateRoute     active  pcx-0b9cd821777935106
10.200.0.0/24           CreateRoute     active  pcx-0b9cd821777935106
10.100.0.0/16   local   CreateRouteTable        active  
0.0.0.0/0       igw-0bada0d7405535216   CreateRoute     active  
[cloudshell-user@ip-10-0-131-98 ~]$ 
[cloudshell-user@ip-10-0-131-98 ~]$ 


#インターネットゲートウェイの確認
[cloudshell-user@ip-10-0-131-98 ~]$ aws ec2 describe-internet-gateways \
> --filters "Name=tag-value,Values=handson-vpc*" \
> --query "InternetGateways[].{InternetGatewayId:InternetGatewayId,VpcId:Attachments[0].VpcId}" \
> --output table
----------------------------------------------------
|             DescribeInternetGateways             |
+------------------------+-------------------------+
|    InternetGatewayId   |          VpcId          |
+------------------------+-------------------------+
|  igw-0628af6c9db7c71d6 |  vpc-0f5af64c8262401f0  |
|  igw-0a6cbcd0152530b46 |  vpc-0150bd49c56f4f085  |
|  igw-0bada0d7405535216 |  vpc-0932bf14cf664577f  |
+------------------------+-------------------------+
[cloudshell-user@ip-10-0-131-98 ~]$ 
[cloudshell-user@ip-10-0-131-98 ~]$ 


#VPCピアリングの確認(VPCピアリングコネクションID、CIDRブロック)
[cloudshell-user@ip-10-0-131-98 ~]$ aws ec2 describe-vpc-peering-connections \
> --filters "Name=status-code,Values=active" \
> --query "VpcPeeringConnections[].[VpcPeeringConnectionId,AccepterVpcInfo.CidrBlock,RequesterVpcInfo.CidrBlock]" \
> --output text
pcx-0dbfc21a4ec1e1609   10.50.0.0/16    10.200.0.0/16
pcx-0b9cd821777935106   10.200.0.0/16   10.100.0.0/16
[cloudshell-user@ip-10-0-131-98 ~]$ 
[cloudshell-user@ip-10-0-131-98 ~]$ 


#EC2インスタンス(プライベートIP、グローバルIP、タグ名、CloudFormationロジカルID)
[cloudshell-user@ip-10-0-131-98 ~]$ aws ec2 describe-instances \
> --filters "Name=tag-value,Values=handson-vpc*" \
> --query "Reservations[].Instances[].[PrivateIpAddress,PublicIpAddress,Tags[?Key=='Name'].[Value],Tags[?Key=='aws:cloudformation:logical-id'].[Value]]" \
> --output text
10.50.0.215     13.112.80.211
handson-vpc-peering3
EC2C
10.200.0.230    3.112.25.175
handson-vpc-peering2
EC2B
10.100.0.199    18.176.59.141
handson-vpc-peering1
EC2A
[cloudshell-user@ip-10-0-131-98 ~]$ 


#セキュリティグループの確認(Description、プロトコル、CIDRブロック)
[cloudshell-user@ip-10-0-131-98 ~]$ aws ec2 describe-security-groups \
> --filters "Name=tag-value,Values=handson-vpc*" \
> --query "SecurityGroups[].[Description,IpPermissions]" \
> --output text
Security Group for VPC2
        -1                        #すべてのプロトコル
IPRANGES        10.100.0.0/16
IPRANGES        10.50.0.0/16
22      tcp     22
IPRANGES        XXX.XXX.XXX.XXX/32  #クライアントPCのグローバルIP
Security Group for VPC1
        -1                        #すべてのプロトコル      
IPRANGES        10.50.0.0/16
IPRANGES        10.200.0.0/16
22      tcp     22
IPRANGES        XXX.XXX.XXX.XXX/32 #クライアントPCのグローバルIP
Security Group for VPC3
        -1                        #すべてのプロトコル
IPRANGES        10.200.0.0/16
IPRANGES        10.100.0.0/16
22      tcp     22
IPRANGES        XXX.XXX.XXX.XXX/32 #クライアントPCのグローバルIP
[cloudshell-user@ip-10-0-131-98 ~]$ 


どうやらリソースは問題なく作成されていそうですので、クライアントPCから各EC2へSSH接続して、他のEC2への通信確認をしてみたいと思います。

通信確認

それでは、各EC2インスタンスSSHログインして、他のVPCのEC2インスタンスPingを打ってみます。

#EC2AからEC2BへのPing 
[ec2-user@ip-10-100-0-199 ~]$ 
[ec2-user@ip-10-100-0-199 ~]$ ping 10.200.0.230
PING 10.200.0.230 (10.200.0.230) 56(84) bytes of data.
64 bytes from 10.200.0.230: icmp_seq=1 ttl=255 time=1.63 ms
64 bytes from 10.200.0.230: icmp_seq=2 ttl=255 time=1.60 ms
64 bytes from 10.200.0.230: icmp_seq=3 ttl=255 time=1.59 ms
64 bytes from 10.200.0.230: icmp_seq=4 ttl=255 time=1.61 ms
64 bytes from 10.200.0.230: icmp_seq=5 ttl=255 time=1.58 ms
^C
--- 10.200.0.230 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 1.587/1.608/1.631/0.015 ms
[ec2-user@ip-10-100-0-199 ~]$ 


#EC2AからEC2CへのPing
[ec2-user@ip-10-100-0-199 ~]$ ping 10.50.0.215
PING 10.50.0.215 (10.50.0.215) 56(84) bytes of data.
^C
--- 10.50.0.215 ping statistics ---
10 packets transmitted, 0 received, 100% packet loss, time 9198ms

[ec2-user@ip-10-100-0-199 ~]$ 
[ec2-user@ip-10-100-0-199 ~]$ 


#EC2BからEC2AへPing
[ec2-user@ip-10-200-0-230 ~]$ 
[ec2-user@ip-10-200-0-230 ~]$ ping 10.100.0.199
PING 10.100.0.199 (10.100.0.199) 56(84) bytes of data.
64 bytes from 10.100.0.199: icmp_seq=1 ttl=255 time=1.88 ms
64 bytes from 10.100.0.199: icmp_seq=2 ttl=255 time=1.66 ms
64 bytes from 10.100.0.199: icmp_seq=3 ttl=255 time=1.67 ms
64 bytes from 10.100.0.199: icmp_seq=4 ttl=255 time=1.59 ms
64 bytes from 10.100.0.199: icmp_seq=5 ttl=255 time=1.59 ms
^C
--- 10.100.0.199 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 1.593/1.682/1.880/0.108 ms
[ec2-user@ip-10-200-0-230 ~]$ 
[ec2-user@ip-10-200-0-230 ~]$ 


#EC2BからEC2CへPing
[ec2-user@ip-10-200-0-230 ~]$ 
[ec2-user@ip-10-200-0-230 ~]$ ping 10.50.0.215
PING 10.50.0.215 (10.50.0.215) 56(84) bytes of data.
64 bytes from 10.50.0.215: icmp_seq=1 ttl=255 time=0.572 ms
64 bytes from 10.50.0.215: icmp_seq=2 ttl=255 time=0.390 ms
64 bytes from 10.50.0.215: icmp_seq=3 ttl=255 time=0.178 ms
64 bytes from 10.50.0.215: icmp_seq=4 ttl=255 time=0.181 ms
64 bytes from 10.50.0.215: icmp_seq=5 ttl=255 time=0.218 ms
^C
--- 10.50.0.215 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4093ms
rtt min/avg/max/mdev = 0.178/0.307/0.572/0.154 ms
[ec2-user@ip-10-200-0-230 ~]$ 
[ec2-user@ip-10-200-0-230 ~]$ 


#EC2CからEC2AへPing
[ec2-user@ip-10-50-0-215 ~]$ 
[ec2-user@ip-10-50-0-215 ~]$ ping 10.100.0.199
PING 10.100.0.199 (10.100.0.199) 56(84) bytes of data.
^C
--- 10.100.0.199 ping statistics ---
9 packets transmitted, 0 received, 100% packet loss, time 8176ms

[ec2-user@ip-10-50-0-215 ~]$ 
[ec2-user@ip-10-50-0-215 ~]$ 


#EC2CからEC2BへPing
[ec2-user@ip-10-50-0-215 ~]$ 
[ec2-user@ip-10-50-0-215 ~]$ ping 10.200.0.230
PING 10.200.0.230 (10.200.0.230) 56(84) bytes of data.
64 bytes from 10.200.0.230: icmp_seq=1 ttl=255 time=0.214 ms
64 bytes from 10.200.0.230: icmp_seq=2 ttl=255 time=0.178 ms
64 bytes from 10.200.0.230: icmp_seq=3 ttl=255 time=0.230 ms
64 bytes from 10.200.0.230: icmp_seq=4 ttl=255 time=0.202 ms
64 bytes from 10.200.0.230: icmp_seq=5 ttl=255 time=0.225 ms
^C
--- 10.200.0.230 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4094ms
rtt min/avg/max/mdev = 0.178/0.209/0.230/0.026 ms
[ec2-user@ip-10-50-0-215 ~]$ 



想定通り、VPCピアリングをトランジットした通信は(ルーティング設定を行ってセキュリティグループで許可設定しても)出来ませんでした。

おわりに

今回はVPCピアリングの基本的な確認でした。非常に簡便な構成でしたが、CloudFormationでの環境構築やCLIでのリソース確認、GitHubの使用など、本題以外の不慣れな部分で結構時間がかかってしまいました。。もっと数をこなして、検証やアウトプットをスピーディに出来るようになりたいですね。

あと、今回はCloudFormationのスタック作成・削除をマネジメントコンソールで実施しましたが、次回以降はこれもCLIで実施してみようと思います。

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

はじめに

今回のブログも前回同様、JAWS-UG CLI専門支部 #185R IAM入門を噛みしめながら復習します。
今回は、AWS CLIで利用するクレデンシャル情報に焦点を当てます。

JAWS-UG CLI専門支部 #185R IAM入門の内容はこちらを参照してください。

今回のやり方とよくあるやり方の相違について

まずは本題に入る前に、JAWS-UG CLI専門支部でのクレデンシャルファイルの作り方と、ネット上でよく見る作り方の相違について確認しておきます。

よくあるやり方(aws configureコマンド)

AWS CLIでプロファイルやクレデンシャルを作成する際には、aws configureコマンドを使用するケースが多いかと思います。この場合、クレデンシャル情報は~/.aws/credentialsに格納されます。

実際にやってみます。(今回はサンプルなので、アクセスキー/シークレットアクセスキーの情報はAWS公式ドキュメントのサンプルから拝借しました。)

[ec2-user@ip-10-0-0-127%]$ aws configure --profile sample-user1
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: ap-northeast-1
Default output format [None]: json
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ cat ~/.aws/credentials
[sample-user1]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
[ec2-user@ip-10-0-0-127%]$ 

~/.aws/credentialsにプロファイル情報が追加されています。更に、プロファイルを追加してみます。

[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$  aws configure --profile sample-user2
AWS Access Key ID [None]: AKIAI44QH8DHBEXAMPLE
AWS Secret Access Key [None]: je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY
Default region name [None]: ap-northeast-1
Default output format [None]: json
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ cat ~/.aws/credentials
[sample-user1]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
[sample-user2]
aws_access_key_id = AKIAI44QH8DHBEXAMPLE
aws_secret_access_key = je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY
[ec2-user@ip-10-0-0-127%]$ 

~/.aws/credentialsにクレデンシャル情報が追加されました。

このように、aws configureコマンドを使用した場合、クレデンシャル情報は一つのファイルに追記されます。1ファイルへの情報の集約は、プロファイルが非常に多くなっても一元管理が可能なメリットがある半面、特定のプロファイルの情報を抜き出したい、編集したい場合には苦労するかもしれません。(そのようなシーンがどれほどあるかはさておいて。。)

<2021/7/18追記> 今回のようなハンズオンが、まさに「特定のプロファイルの情報を抜き出したい、編集したい場合」に当てはまりましたね。だからこそ、後述するやり方を採用しているようです。

JAWS-UG CLI専門支部のやり方(プロファイル毎にクレデンシャル情報を分ける)

次に、JAWS-UG CLI専門支部のやり方ですが、こちらはタイトル通り、プロファイル毎にクレデンシャル情報を分けます。つまり別ファイルにします。メリデメは1ファイルに情報を集約する場合の逆と考えればよいかと思います。

<2021/7/18追記> 上記について、「credentialファイルを分離するメリットが出るのは、共有環境(ツール群の実行環境など: credentialsの編集リスクがありゴミが溜まりやすい)や、ハンズオン環境(参加者のアカウントで通常利用しているcredentialファイルをいじらせたくない)」とコメントを頂戴しましたので、追記させていただきます。

ただし、aws configureのようなawsコマンドでクレデンシャルファイルを分けることは出来ないので、少々遠回りをします。

クレデンシャル情報格納ファイルの作成方法

ようやく本題ですが、ここから、JAWS-UG CLI専門支部でのクレデンシャル情報格納ファイル(つまり~/.aws/credentialsの代替ファイル)の作成方法を具体的に見ていきます。

作成の流れ

作成の大まかな流れは以下です。

  1. アクセスキーを格納するためのアクセスキーファイル用の変数を定義する
  2. アクセスキーを新規作成し、アクセスキーファイルに格納する
  3. クレデンシャル情報を格納するためのファイル用の変数を定義する
  4. アクセスキーファイルから必要な情報を抜き出し、クレデンシャル情報格納ファイルを作成する



前提

作成の前提として、IAMユーザーとプロファイルは作成済みとします。IAMユーザー名は"handson-cli-novice-user"とします。
IAMユーザーやプロファイルの作成方法は前回のブログを参照ください。

アクセスキーを格納するためのアクセスキーファイル用の変数を定義する

まず最初に、アクセスキーを格納するためのアクセスキーファイル用の変数を定義します。この時点ではファイルそのものは作成せず、ファイルのパスを変数に格納します。

今回作成するアクセスキーファイルのパスの構成は以下です。

{ホームディレクトリ}/environment/conf-handson-cli-iam/{IAMユーザー名}-token-{キーのカウント数}.json

「キーのカウント数」とは、今回作成するアクセスキーが、何個目のキーであるか、という数です。初めて作る場合は「1」になります。(※ハンズオンでは0からカウントする手順になっていましたが、今回は1からカウントする手順にしてみます)

例として、ホームディレクトリが/home/ec2-user、IAMユーザー名がhandson-cli-novice-user、キーのカウント数が1の場合、ファイルパスは以下となります。

/home/ec2-user/environment/conf-handson-cli-iam/handson-cli-novice-user-token-1.json



それでは実際に作成してみます。アクセスキーファイルの作成にあたり、以下のシェル変数を定義・使用します。

変数名 用途
IAM_USER_NAME IAMユーザー名 handson-cli-novice-user
DIR_ACCESS_KEY アクセスキーファイルのディレクトリパス /home/ec2-user/environment/conf-handson-cli-iam
COUNT_IAM_ACCESS_KEYS キーのカウント数 1
FILE_ACCESS_KEY アクセスキーファイルのパス /home/ec2-user/environment/conf-handson-cli-iam/handson-cli-novice-user-token-1.json
[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%]$ DIR_ACCESS_KEY="${HOME}/environment/conf-handson-cli-iam"
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ ls -d ${DIR_ACCESS_KEY}
ls: cannot access /home/ec2-user/environment/conf-handson-cli-iam: No such file or directory
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ mkdir -p ${DIR_ACCESS_KEY}
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ ls -d ${DIR_ACCESS_KEY}
/home/ec2-user/environment/conf-handson-cli-iam
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ COUNT_IAM_ACCESS_KEYS=$( \
>   aws iam list-access-keys \
>     --user-name ${IAM_USER_NAME} \
>     --query 'length(AccessKeyMetadata[])' \
> ) \
>   && COUNT_IAM_ACCESS_KEYS=$((COUNT_IAM_ACCESS_KEYS + 1)) \
>   && echo ${COUNT_IAM_ACCESS_KEYS}
1
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ FILE_ACCESS_KEY="${DIR_ACCESS_KEY}/${IAM_USER_NAME}-token-${COUNT_IAM_ACCESS_KEYS}.json" \
>   && echo ${FILE_ACCESS_KEY}
/home/ec2-user/environment/conf-handson-cli-iam/handson-cli-novice-user-token-1.json
[ec2-user@ip-10-0-0-127%]$

アクセスキーファイルのパスが格納されたシェル変数が作成できました。

なお、キーのカウント方法については、最後に補足としてもう少し掘り下げて確認してみたいと思います。

アクセスキーを新規作成し、アクセスキーファイルに格納する

次に、アクセスキーを作成し、アクセスキーファイルに格納します。アクセスキーの作成は、aws iam create-access-keyコマンドを使用します。詳しくは前回のブログを参照ください。

[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": "AKIATOXXXXXXXXXXXXX",
        "Status": "Active",
        "SecretAccessKey": "Gwhdvj1XXXXXXXXXXXXXXXXXXXXXX",
        "CreateDate": "2021-07-17T05:37:31Z"
    }
}
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ ls -al ${DIR_ACCESS_KEY}                                                                                                                                                                 
total 4
drwxrwxr-x 2 ec2-user ec2-user  50 Jul 17 05:37 .
drwxr-xr-x 4 ec2-user ec2-user  62 Jul 17 04:49 ..
-rw-rw-r-- 1 ec2-user ec2-user 267 Jul 17 05:37 handson-cli-novice-user-token-1.json
[ec2-user@ip-10-0-0-127%]$ 

作成されたアクセスキーが、JSON形式でアクセスキーファイルに格納されました。

クレデンシャル情報を格納するためのファイル用の変数を定義する

続いて、クレデンシャル情報を格納するためのファイル用の変数を定義します。ここも前回同様、ファイルそのものは作成せず、ファイルパスが格納された変数を用意します。

今回作成したいファイルパス構成は以下です。

{ホームディレクトリ}/environment/conf-handson-cli-iam/{IAMユーザー名}.ini

今回の環境では以下のようになります。

/home/ec2-user/environment/conf-handson-cli-iam/handson-cli-novice-user.ini

それでは早速作っていきます。今回は、すでに作成済みのシェル変数を使いまわしていきます。

[ec2-user@ip-10-0-0-127%]$ FILE_USER_CREDENTIAL="${DIR_ACCESS_KEY}/${IAM_USER_NAME}.ini" \
>   && echo ${FILE_USER_CREDENTIAL}
/home/ec2-user/environment/conf-handson-cli-iam/handson-cli-novice-user.ini
[ec2-user@ip-10-0-0-127%]$ 

作成できました。

アクセスキーファイルから必要な情報を抜き出し、クレデンシャル情報格納ファイルを作成する

最後に、アクセスキーファイルから必要な情報を抜き出して書式を整理し、クレデンシャル情報格納ファイルを作成します。 つまり、アクセスキーファイルのJSON形式から、~/.aws/credentialsと同じ形式のファイルを作成します。

f:id:YuY_83:20210717151527p:plain

それでは実際にやってみます。

[ec2-user@ip-10-0-0-127%]$ echo "[${IAM_USER_NAME}]" > ${FILE_USER_CREDENTIAL}
[ec2-user@ip-10-0-0-127%]$ 

これは非常にシンプルですね。1行目の[handson-cli-novice-user]を作成しています。

問題はここからです。アクセスキーIDとシークレットアクセスキーを引っこ抜いて、形式を整える必要があります。jp.pyとsedgrepを駆使して、なんか凄い頑張ってます。

[ec2-user@ip-10-0-0-127%]$ cat "${FILE_ACCESS_KEY}" \
>   | jp.py 'AccessKey' \
>   | sed '/[{}]/d' | sed 's/[\" ,]//g' | sed 's/:/=/' \
>   | sed 's/AccessKeyId/aws_access_key_id/' \
>   | sed 's/SecretAccessKey/aws_secret_access_key/' \
>   | grep '^aws_' \
>   >> ${FILE_USER_CREDENTIAL} \
>     && cat ${FILE_USER_CREDENTIAL}
[handson-cli-novice-user]
aws_access_key_id=AKIATOXXXXXXXXXXXXX
aws_secret_access_key=Gwhdvj1XXXXXXXXXXXXXXXXXXXXXX
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ ls -l ${FILE_USER_CREDENTIAL}
-rw-rw-r-- 1 ec2-user ec2-user 128 Jul 17 06:20 /home/ec2-user/environment/conf-handson-cli-iam/handson-cli-novice-user.ini
[ec2-user@ip-10-0-0-127%]$ 

めでたくクレデンシャルファイルの形式になりました。とはいえ、これだけだと何をしているのかよく分かりませんね。こちらも最後に補足としてもう少し掘り下げてみたいと思います。

全体の流れとしては以上です。

補足

上記の作業において、少々わかりにくい箇所について掘り下げてみたいと思います。

キーのカウント数について

キーのカウント数については、aws iam list-access-keysコマンドでキーのリストを表示し、その中の"AccessKeyMetadata[]"の配列長を--query 'length(AccessKeyMetadata[])'でカウントしています。

そこで、ここでは--queryを実行せずに、aws iam list-access-keysコマンドのみを実行し、どのような出力結果になるか見てみます。まずはアクセスキーが一つも無い場合から。

[ec2-user@ip-10-0-0-127%]$ aws iam list-access-keys --user-name ${IAM_USER_NAME}
{
    "AccessKeyMetadata": []
}
[ec2-user@ip-10-0-0-127%]$ 

アクセスキーのメタデータが空であることが分かります。

このようにアクセスキーがない状態だと、"AccessKeyMetadata"が空なので、'length(AccessKeyMetadata)'の値は0になります。

[ec2-user@ip-10-0-0-127%]$ aws iam list-access-keys \
>    --user-name ${IAM_USER_NAME} \
>    --query 'length(AccessKeyMetadata[])'
0
[ec2-user@ip-10-0-0-127%]$ 



続いて、aws iam create-access-keyコマンドでアクセスキーを作った後の、aws iam list-access-keysの出力結果を見てみます。

[ec2-user@ip-10-0-0-127%]$ aws iam list-access-keys \
>     --user-name ${IAM_USER_NAME}
{
    "AccessKeyMetadata": [
        {
            "UserName": "handson-cli-novice-user",
            "AccessKeyId": "AKIATOXXXXXXXXXXXXX",
            "Status": "Active",
            "CreateDate": "2021-07-17T05:37:31Z"
        }
    ]
}
[ec2-user@ip-10-0-0-127%]$ 

今度はアクセスキーのメタデータとして、ユーザー名・アクセスキーID・ステータス・作成日時が追加されました。ただし、配列の中のオブジェクト({}で囲まれた部分)の数は一つなので、'length(AccessKeyMetadata[])'の値は1になります。

[ec2-user@ip-10-0-0-127%]$ aws iam list-access-keys \
>     --user-name ${IAM_USER_NAME} \
>     --query 'length(AccessKeyMetadata[])'
1
[ec2-user@ip-10-0-0-127%]$ 



アクセスキーファイルからクレデンシャルファイルを作成する部分の掘り下げ

続いて、アクセスキーファイルからクレデンシャルファイルを作成する部分を掘り下げてみます。 今回やりたいことを再掲します。

f:id:YuY_83:20210717151527p:plain

[handson-cli-novice-user]はecho "[${IAM_USER_NAME}]" > ${FILE_USER_CREDENTIAL}で作ったので、クレデンシャルファイルの2行目、3行目を作りたいわけです。

先ほどは文字の抽出や整形を一気にやってしまったので、今回は一つずつ出力結果を見ていきたいと思います。
まずは改めてcatでファイルの中身を表示します。

[ec2-user@ip-10-0-0-127%]$ cat "${FILE_ACCESS_KEY}"
{
    "AccessKey": {
        "UserName": "handson-cli-novice-user",
        "AccessKeyId": "AKIAXXXXXXXXXXXX",
        "Status": "Active",
        "SecretAccessKey": "GwhXXXXXXXXXXXXXXXXXXXXX",
        "CreateDate": "2021-07-17T05:37:31Z"
    }
}
[ec2-user@ip-10-0-0-127%]$ 


次に、jp.py 'AccessKey'で、AccessKeyの中身の部分だけ抽出します。

[ec2-user@ip-10-0-0-127%]$ cat "${FILE_ACCESS_KEY}" \
>   | jp.py 'AccessKey'
{
    "UserName": "handson-cli-novice-user",
    "AccessKeyId": "AKIAXXXXXXXXXXXX",
    "Status": "Active",
    "SecretAccessKey": "GwhXXXXXXXXXXXXXXXXXXXXX",
    "CreateDate": "2021-07-17T05:37:31Z"
}
[ec2-user@ip-10-0-0-127%]$ 

jp.pyはaws-cliをインストールすると一緒にインストールされるJSONのクエリコマンドで、用途はjqコマンドと一緒です。

続いて、sed '/[{}]/d'で、"{" もしくは "}" を含む行を削除します。

[ec2-user@ip-10-0-0-127%]$ cat "${FILE_ACCESS_KEY}" \
>   | jp.py 'AccessKey' \
>   | sed '/[{}]/d'
    "UserName": "handson-cli-novice-user",
    "AccessKeyId": "AKIAXXXXXXXXXXXX",
    "Status": "Active",
    "SecretAccessKey": "GwhXXXXXXXXXXXXXXXXXXXXX",
    "CreateDate": "2021-07-17T05:37:31Z"
[ec2-user@ip-10-0-0-127%]$ 



さらに、sed 's/[\" ,]//g'で、ダブルクォーテーションと空白スペースとカンマを削除します。行中の全てのダブルクォーテーションと空白スペースとカンマを対象としたいので、末尾にgをつけます。

[ec2-user@ip-10-0-0-127%]$ cat "${FILE_ACCESS_KEY}" \
>   | jp.py 'AccessKey' \
>   | sed '/[{}]/d' | sed 's/[\" ,]//g'
UserName:handson-cli-novice-user
AccessKeyId:AKIAXXXXXXXXXXXX
Status:Active
SecretAccessKey:GwhXXXXXXXXXXXXXXXXXXXXX
CreateDate:2021-07-17T05:37:31Z
[ec2-user@ip-10-0-0-127%]$ 



さらにさらに、sed 's/:/=/'で、コロンをイコールに置換します。これは各行で一回実行されれば良いので、末尾にgは不要です。

[ec2-user@ip-10-0-0-127%]$ cat "${FILE_ACCESS_KEY}" \
>   | jp.py 'AccessKey' \
>   | sed '/[{}]/d' | sed 's/[\" ,]//g' | sed 's/:/=/'
UserName=handson-cli-novice-user
AccessKeyId=AKIAXXXXXXXXXXXX
Status=Active
SecretAccessKey=GwhXXXXXXXXXXXXXXXXXXXXX
CreateDate=2021-07-17T05:37:31Z
[ec2-user@ip-10-0-0-127%]$

末尾にgをつけていないので、"CreateDate"の"05:37:31"のコロンが置換されていないのが分かります。

まだ続きますw
"AccessKeyId"を"aws_access_key_id"に、"SecretAccessKey"を"aws_secret_access_key"に置換します。クレデンシャルファイルの命名規則に合わせるためですね。

[ec2-user@ip-10-0-0-127%]$ cat "${FILE_ACCESS_KEY}" \
>   | jp.py 'AccessKey' \
>   | sed '/[{}]/d' | sed 's/[\" ,]//g' | sed 's/:/=/' \
>   | sed 's/AccessKeyId/aws_access_key_id/' \
>   | sed 's/SecretAccessKey/aws_secret_access_key/'
UserName=handson-cli-novice-user
aws_access_key_id=AKIAXXXXXXXXXXXX
Status=Active
aws_secret_access_key=GwhXXXXXXXXXXXXXXXXXXXXX
CreateDate=2021-07-17T05:37:31Z
[ec2-user@ip-10-0-0-127%]$



ようやくラストです。"aws_access_key_id"と"aws_secret_access_key"以外は不要なので、grepで"aws"から始まる行だけ出力します。

[ec2-user@ip-10-0-0-127%]$ 
[ec2-user@ip-10-0-0-127%]$ cat "${FILE_ACCESS_KEY}" \
>   | jp.py 'AccessKey' \
>   | sed '/[{}]/d' | sed 's/[\" ,]//g' | sed 's/:/=/' \
>   | sed 's/AccessKeyId/aws_access_key_id/' \
>   | sed 's/SecretAccessKey/aws_secret_access_key/' \
>   | grep '^aws_'
aws_access_key_id=AKIAXXXXXXXXXXXX
aws_secret_access_key=GwhXXXXXXXXXXXXXXXXXXXXX
[ec2-user@ip-10-0-0-127%]$

うまくいきました。jp.pyを1回、sedを5回、grepを1回やってて、なかなかの力技ですねw

おわりに

ハンズオン当日から大分時間が空いてしまいましたが、クレデンシャルについて掘り下げて復習ができました。

こうやって掘り下げるとなんだか大変そうに見えますが、実際にハンズオンやってみるとかなり短時間で終わります。また、この手順自体をスクリプト化すればもっと速く終わるかと思います。

前回のブログから予定外に期間が空いてしまい、ちょっと今更感が出てしまったかもしれませんが、自分的にはちょうどよいタイミングでの復習になりました。

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