Y_Yamashitaのブログ

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

CloudFormation初心者が色々ハマった話

はじめに

前回のブログで記載した通り、慣れないCloudFormationで環境構築をしようとしたところハマってしまい、解決までに結構手間取ってしまいました。今後同じようなミスをしないように、備忘としてブログに残したいと思います。

今回ハマったこと

今回、主に以下の3つのポイントでハマってしまいました。

ファイルパスの先頭にfile://をつけていなかった

CLIでCloudFormationのスタックを作成する場合、aws cloudformation create-stackコマンドを使用し、--template-body file://<ファイルパス>という形式でテンプレートファイルのパスを指定します。ポイントは、ファイルパスの前に「file://」をつけることなのですが、これをつけていませんでした。なぜかスッポリ意識から抜け落ちていました。「CLIで引数にファイルを指定する時はファイルパス」という思い込みがあったのかもしれません。

そうして「file://」をつけずに実行しようとすると、以下のようなエラーが表示されます。

[cloudshell-user@ip-10-0-87-215 ~]$ aws cloudformation create-stack \
> --stack-name EC2-WEB-deploy \
> --template-body /home/cloudshell-user/ec2_web.yml \
> --parameters \
> ParameterKey=KeyName,ParameterValue="XXX" \
> ParameterKey=AmiId,ParameterValue="ami-0a1c2ec61571737db" \
> ParameterKey=ClientIP,ParameterValue="114.XXX.XXX.XXX/32" \
> ParameterKey=ServerName,ParameterValue="www.XXXXXXXXXX.com:80"

An error occurred (ValidationError) when calling the CreateStack operation: Template format error: unsupported structure.
[cloudshell-user@ip-10-0-87-215 ~]$


aws cloudformation validate-templateコマンドでも同様のエラーが表示されます。

[cloudshell-user@ip-10-0-87-215 ~]$ aws cloudformation validate-template --template-body /home/cloudshell-user/ec2_web.yml

An error occurred (ValidationError) when calling the ValidateTemplate operation: Template format error: unsupported structure.
[cloudshell-user@ip-10-0-87-215 ~]$ 


「テンプレートフォーマットエラー:サポートされていない構造」という表記だったので、まさかコマンドの書式が間違っているとは思いもしませんでした。特に今回は初めてヘルパースクリプトやユーザーデータを使っていて、テンプレートの記載が正しいか自信がなかったので、真っ先にテンプレートの中身を疑ってしまいました。

そうして暫くテンプレートと格闘していたのですが、エラーメッセージでググったら、そのものズバリな回答が記載されているサイトがすぐ出てきました。。

sqlazure.jp

大文字小文字の記述ミス

こうしてテンプレートフォーマットエラーを解消して、aws cloudformation create-stackコマンドは通るようになったのですが、スタック作成が途中で失敗してロールバックしてしまいました。そこで、aws cloudformation describe-stack-eventsコマンドで、スタックのイベントをチェックしてみました。

[cloudshell-user@ip-10-0-87-215 ~]$ aws cloudformation create-stack \
> --stack-name EC2-WEB-deploy \
> --template-body file:///home/cloudshell-user/ec2_web.yml \
> --parameters \
> ParameterKey=KeyName,ParameterValue="XXX" \
> ParameterKey=AmiId,ParameterValue="ami-0a1c2ec61571737db" \
> ParameterKey=ClientIP,ParameterValue="114.XXX.XXX.XXX/32" \
> ParameterKey=ServerName,ParameterValue="www.XXXXXXXXXX.com:80"
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/EC2-WEB-deploy/8af162a0-0fe7-11ec-a7df-0a0e301349e7"
}
[cloudshell-user@ip-10-0-87-215 ~]$ 
[cloudshell-user@ip-10-0-87-215 ~]$ 
[cloudshell-user@ip-10-0-87-215 ~]$ 
[cloudshell-user@ip-10-0-87-215 ~]$ aws cloudformation describe-stack-events --stack-name EC2-WEB-deploy
{
    "StackEvents": [
        {
          ----- 中略 -----
        },
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/EC2-WEB-deploy/8af162a0-0fe7-11ec-a7df-0a0e301349e7",
            "EventId": "EC2A-CREATE_FAILED-2021-09-07T14:51:54.021Z",
            "StackName": "EC2-WEB-deploy",
            "LogicalResourceId": "EC2A",
            "PhysicalResourceId": "",
            "ResourceType": "AWS::EC2::Instance",
            "Timestamp": "2021-09-07T14:51:54.021000+00:00",
            "ResourceStatus": "CREATE_FAILED",
            "ResourceStatusReason": "Encountered unsupported property Userdata"
        },
        {

最後の行に、何やら「Userdataというサポートしていないpropertyに遭遇した」という表記が出てきました。「EC2はユーザーデータをサポートしていない?いや、そんなはずない。。」と悩みつつ、ユーザーデータの中身をチェックし直してみますが、おかしそうな所は見当たりません。

困惑しつつ、見本にしたテンプレートを自分のテンプレートを何度も見比べて、ようやく気付きました。「Userdata」ではなく「UserData」が正しいことに。。

日本語の文字化け

propertyの表記も修正して、ようやく環境構築が完了したので、早速WEBページにブラウザでアクセスしてみたところ、今度はWEBページが文字化けしていました。。 f:id:YuY_83:20210908005044p:plain

文字コードUTF-8にしてテンプレートにしたのに何故。。」と思ってググったところ、以下のページに詳しい解説がありました。

dev.classmethod.jp


CloudFormationの組み込み関数Fn::Base64で日本語を一度ASCII文字に変換してから、ユーザーデータ上のスクリプトでデコードする必要があるようです。

ただ、環境構築を開始したのが深夜で、これに気付いた時点ですでに夜が明けかかっており、文字変換まで実装する気力がなかったので、今回はWEBページを英語表記にして逃げました。。

今回の一番の反省点

以上、つらつらとハマったポイントをまとめてみました。最初の二つは「不注意」と言われればそれまでなんですが、今回四苦八苦したおかげで、金輪際「file://」と「UserData」を忘れることはないでしょう。

日本語WEBページの表示は、また今度、機会を作って挑戦したいと思います。

そして何より、今回の一番の反省ポイントは「寝不足状態で作業しない」ですw
寝不足だと本当にしょうもないミスが多くて、以前にも、しっかり寝た後で見直して「なんでこんなミスを」と呆れた事がありました。
手を動かしての検証は楽しいのでつい時間を忘れて取り組んでしまいますが、要反省ですね。

(オマケ)今回のCloudFormationの環境構築について

最後に、あまり本題とは関係ないのですが、今回CloudFormationで構築した構成や、テンプレートの中身などについてまとめました。

今回構築した構成

今回は、下図の赤点線部分をCloudFormationで構築しました。

f:id:YuY_83:20210906223623p:plain


Route53のパブリックホストゾーンは既に存在していたものを使用しました。

cfn-init、ユーザーデータによる設定

cfn-init、ユーザーデータを使い、EC2に対して以下の設定も実施してみました。

  • httpdのインストール
  • /var/www/html/index.htmlへのコンテンツ格納
  • /etc/httpd/conf/httpd.confのServerNameの値を変更
  • SSHのListenポートを22から51512に変更


/etc/httpd/conf/httpd.confの変更は、CloudFormationのParameterで入力した値を、ユーザーデータでシェル変数として使うために参照させました。
Parameterの値をユーザーデータで変数として利用できるのは便利ですね。

最後のSSHのポート変更は今回の趣旨と関係ありませんが、以前のブログでユーザーデータを扱った際にやった事があったので、今回CloudFormationでもやってみました。

今回使用したCloudFormationテンプレート

上記の構築・設定のために使用したCloudFormationテンプレートはこちらです。

CloudFormationテンプレートの作成にあたっては、以下のページを大いに参考にさせていただきました。

dev.classmethod.jp


dev.classmethod.jp


blog.serverworks.co.jp


CloudFormationの実行方法

冒頭でも記載した通り、今回、CloudFormationのスタック作成はマネコンではなくCLIで実施しました。

Route53のルーティング方式毎のクエリ応答切り替わりを確認する - シンプルルーティング編

はじめに

前回のブログで記載した通り、AWS Certified Advanced Networking - Specialtyに合格することが出来ました。

試験対策中は座学が中心だったのですが、一区切りついたので、勉強した内容について実際に挙動を確認してみたいと思います。

とはいえDirectConnectなどは構築のハードルが高いので、Route53の各ルーティング方式毎の挙動(クエリ応答の切り替わり)を見ていくことにしました。その中で今回は、最も基本的なシンプルルーティングの挙動を見ていきたいと思います。

Route53シンプルルーティングについて

シンプルルーティングについては以下の公式ページに説明があります。

docs.aws.amazon.com

「複数の値を 1 つのレコードに指定すると、Route 53 はすべての値をランダムな順序で再帰的リゾルバーに返し」と記載があるので、ラウンドロビンではないようです。

今回の構成

今回は図のような構成を作りました。 f:id:YuY_83:20210906220351p:plain

通常であれば、WEBサーバはプライベートサブネットに置いて、ELBをパブリックサブネットに置くのがベタな構成ですが、今回は検証目的なので直接パブリックサブネットに置いてしまいます。

2台のWEBサーバに同じホスト名を割り当て、そのホスト名に対するAレコードを、Route53パブリックホストゾーンに登録します。シンプルルーティングによって、2つのWEBページがランダムに表示されることを確認します。

WEBページについて

2つのWEBページがランダムに表示されていることを確認するために、ページの内容は若干変えました。「Hello」とだけ表示されるページとかでも良かったのですが、それだと少し寂しいので、無駄にリンクなどをつけて、以下のような感じにしました。

f:id:YuY_83:20210906001010p:plain

この時点ではIPアドレスを直接叩いてアクセスしています。

Route53作業

それでは、Route53のパブリックホストゾーンにAレコードを登録します。

f:id:YuY_83:20210906213347p:plain まずは目的のゾーンに移動して、「レコードを作成」を選択

f:id:YuY_83:20210906213441p:plain ルーティングポリシーで「シンプルルーティング」を選択

f:id:YuY_83:20210906213528p:plain ホスト名を入力し、レコードタイプでAレコードを選択。値/トラフィックのルーティング先には「レコードタイプに応じたIPアドレスまたは別の値」を選択し、二つのWEBサーバのIPアドレスを登録。TTLを適当に短い時間(今回は10秒)に変更し、「シンプルなレコードの定義」を選択。

f:id:YuY_83:20210906213810p:plain 内容に問題がないことを確認して、「レコードを作成」を選択。

f:id:YuY_83:20210906213859p:plain シンプルルーティングのAレコードが登録されたことを確認。

ページ閲覧・クエリ応答確認

ホスト名でのページ閲覧

それでは、ホスト名でWEBページにアクセスし、適当にブラウザを更新してみます。

f:id:YuY_83:20210906215533p:plain 同じホスト名で、ランダムで二つのWEBページが表示されました。

クエリ応答確認

続いて、クライアントPCからnslookupでクエリを飛ばし、応答を確認してみます。

C:\Users>echo %date% %time%
2021/09/05 16:18:12.85

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
DNS request timed out.
    timeout was 2 seconds.
名前:    www.XXXXX.com
Addresses:  18.XXX.XXX.XXX
          54.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:19:02.34

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  18.XXX.XXX.XXX
          54.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:19:11.60

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  54.XXX.XXX.XXX
          18.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:19:19.88

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  54.XXX.XXX.XXX
          18.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:19:25.38

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  54.XXX.XXX.XXX
          18.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:19:32.73

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  18.XXX.XXX.XXX
          54.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:19:39.71

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  54.XXX.XXX.XXX
          18.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:19:47.60

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  18.XXX.XXX.XXX
          54.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:19:53.42

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  18.XXX.XXX.XXX
          54.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:20:04.39

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  18.XXX.XXX.XXX
          54.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:20:10.62

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  18.XXX.XXX.XXX
          54.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:20:17.89

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  18.XXX.XXX.XXX
          54.XXX.XXX.XXX


C:\Users>echo %date% %time%
2021/09/05 16:20:23.82

C:\Users>nslookup www.XXXXX.com
サーバー:  UnKnown
Address:  2404:1a8:7f01:b::3

権限のない回答:
名前:    www.XXXXX.com
Addresses:  54.XXX.XXX.XXX
          18.XXX.XXX.XXX


C:\Users>

ラウンドロビンではなく、ランダムで応答が返ってきていることが確認できました。

おわりに

というわけで、Route53のシンプルルーティングの挙動確認でした。こんな感じで、次はフェイルオーバールーティング、加重ルーティングあたりの確認をしようかと思います。位置情報ルーティングや地理的近接性ルーティングも気になりますが、その辺はうまい確認方法があれば試してみたいと思います。

実は今回、Route53の設定と挙動確認は一瞬で終わったのですが、Route53以外の部分を慣れないCloudFormationで構築してみたところ、色々失敗してかなり手間取り、ほとんどの時間をそこに費やしました。ただ、おかげでCloudFormationの扱いには多少慣れました。

せっかくなので、CloudFormationで躓いた箇所についても、備忘として別途ブログに残したいと思います。

AWS Certified Advanced Networking - Specialty試験の振り返り

はじめに

先日、AWS Certified Advanced Networking - Specialty(以下、ANS)に合格しました。(スコアはギリギリでしたが。。)

f:id:YuY_83:20210905052439p:plain

試験対策として何をやったか、受けてみてどうだったか等を振り返り、備忘録として残したいと思います。「これさえやれば合格間違いない!」というものではありませんが、誰かにとって何らか参考になる事があれば幸いです。

なお、試験内容について記載することは規約違反のため、「具体的にこういう問題が出た」みたいな事はここでは記載しません。

(前提)私のスキル・知識

AWSを業務で使ったことはありません。2021年3月くらいからAWSの勉強を開始し、以下の資格を取得しました。

  • Solutions Architect - Associate(以下、SAA)
  • SysOps Administrator - Associate(以下、SOA

ネットワークに関しては、6,7年くらい業務で関わっていました。(最近は自分で手を動かす機会がめっきり減っていましたが。。)資格はネットワークスペシャリストCCNPを取得していました(CCNPはすでに失効)。そのため、ネットワークに関する基本的な知識はある状態でした。

試験対策期間

SOA合格後の2021年7月中旬から勉強を開始し、2021/9/2に受けたので、期間としては大体1.5ヵ月くらいです。

平日は仕事終わりに勉強していたため、仕事の繁忙によって1日の勉強時間は異なりますが、平均1~1.5時間くらいかなーと思います。

休日も予定の有無や体調、やる気で大分差がありますが、対策期間の前半は1日2~3時間、終盤は1日5~6時間くらいだったかと思います。

試験対策としてやったこと

試験対策としては主に以下を実施しました。

  1. AWS WEB問題集で学習しよう」で問題を解く。
  2. ネットワーク関連のBlackBelt資料を読む。


UdemyやAWS公式模擬試験は利用しませんでした。

AWS WEB問題集で学習しよう」

aws.koiwaclub.com

AWS WEB問題集で学習しよう」は、SAA、SOAの試験勉強でも利用しました。問題を解いて解説を読み、解説を読んでもよく分からない所はググったりBlackBert資料を読んだりして理解を深めました。

間違えた問題や、ちゃんと理解できていなかった問題は番号をメモしておき、2周目はメモした問題だけ解きました。さらに復習した方が良さそうな問題をメモして、合計3周しました。

これまでいくつかの資格試験を受けてきましたが、問題を繰り返し解くと記憶に定着しやすいと感じています。有料ですが、個人的には使って損はありませんでした。

BlacBelt資料

aws.amazon.com

AWS WEB問題集で学習しよう」はANSの問題がそこまで多くはないので、(全部で150問程度で、SAAやSOAと比べると大分少ない)BlackBelt資料も読むようにしました。
BlackBelt資料では、Networking & Content Deliveryカテゴリの内、以下の資料を読みました。


上記のサービスを選んだ理由は、カテゴリの中でも特にメジャーなサービスなので、出題頻度も多いかな、と思ったためです。

BlackBeltの内容はYouTubeで解説が聴けるものもありますが、私は自分のペースで読み進めたかったのでPDFを読んでいました。

また、読むだけだとなかなか頭に入らないので、自分でBlackBeltの内容から選択問題を作成し、それを解いたりしました。

こういったアウトプットは多少時間がかかりますが、記憶の定着が良いので、出来るだけインプットだけではなくアウトプットしながら学習したいと思ってはいます。

試験当日

試験は自宅で受験しました。SAA、SOAも自宅受験で今回3回目だったので、さすがに慣れてきていて、セッティングで焦ることはありませんでした。

ただ、試験時間をちゃんと確認しておらず、120分の試験だと思い込んでいたら実際には180分の試験で、これにはちょっと焦りました。。

というのも、その日は平日で仕事を抜けられる時間に限りがあり、試験開始の120分後には上司との1on1ミーティングが設定されていました。しかも試験を開始してしまっているので上司に連絡も出来ないので、もうとにかく120分以内に終わらせることにしました。

結果的には見直しも含めてギリギリ120分で終わりましたが、前半で結構時間を使ったため、途中はかなり焦りました。時間はちゃんと確認しとけって話ですね。。

終わった時は正直あまり自信はありませんでした。択一式なら大体2択には絞れるのですが、最後どっちだっけ?と悩む問題が結構あったような気がします。「合格」の2文字を見た時は思わず安堵のため息が出ました。

今回の反省点と良かった点

反省点

反省点は主に2点です。

  1. 試験時間を事前に把握しておくべきだった
  2. 試験ガイドの出題内容をもっとちゃんと確認しておきべきだった

1は当たり前の話ですね。。
2は、WEB問題集とBlackBeltを見れば何とかなるだろうと高を括ってあまりちゃんと確認しなかったのですが、自分があまり知らないサービスが出題内容になっていないか、くらいはもっとちゃんと見るべきだったなと思います。

良かった点

これは受かったから言えることかもしれませんが、「まだ準備が万全ではないかも」と思いつつ、思い切って受験してしまったのは良かったと思っています。

1.5ヵ月ANSの勉強をして、正直ちょっと飽きてきていたので、思い切って3日後くらいに試験をセッティングしてしまいました。

そこから当日までは気合を入れて勉強できたので、ダレないように締切を自分でセッティングするのも大事かな、と思いました。

今後の勉強について

今回の試験でスコアが悪かった分野

スコアレポートでは出題分野毎のスコアパフォーマンスが出ますが、今回は以下の2分野について「改善が必要」の判定でした。

  • 分野4:アプリケーションサービスとネットワークの連携を構成する
  • 分野5:ネットワークの管理、最適化、トラブルシューティングを行う

各分野の詳細な内容は以下ページの「試験ガイドのダウンロード」から確認できますので、そちらを参照してください。

aws.amazon.com

私は上記分野の中では、以下の内容についてあまり勉強してこなかったので、実際に使ったり調べたりして知見を増やしたいと思います。

  • VPCフローログ
  • CloudTrail
  • CloudWatch
  • 各種ヘルスチェック

比較的新しいネットワークサービス

ネットワークに関しては、比較的最近に以下のようなサービスが登場していますが、試験範囲には含まれていなそうだったので勉強していませんでした。

とはいえ、今後業務でAWSに携わるようになれば使う必要も出てくるかもしれないので、別途勉強したいと思います。

次に狙う資格

次はDVAを取って、アソシエイト3冠を達成したいと思います。

(オマケ)自宅受験について

自宅受験の際は、事前のシステムテストをちゃんとやった方が良い、と今回改めて思いました。

私は今回3回目の自宅受験で、過去2回も大きなトラブルはありませんでしたが、念のため、今回も事前にシステムテストを実施しました。
すると、「mDNSResponderというバックグラウンドプロセスが動いている」とのことでシステムエラーが出ました。(知らない人のために説明しておくと、自宅受験の際には余計なバックグラウンドプロセスを停止させる必要があるのです。)
しかし、タスクマネージャーをいくら確認しても、mDNSResponderというプロセスが見つからないのです。

そこでググってみると、mDNSResponderはWindows10のタスクマネージャーではBonjourという名前で表示されていることが分かりました。

もし事前にシステムテストをやっていなかったら、当日にこのエラーが出てきてもっとバタバタしていたに違いないし、最悪、試験が受けられなかったかもしれ ないと思うとゾッとしました。。

おわりに

やや取り留めのない振り返りになってしまいましたが、以上です。またDVAに受かったら振り返りを行いたいと思います。

iptablesでインスタンスメタデータへのアクセスをブロックしてIAMロールの権限を無効化してみた

はじめに

今回は、EC2インスタンスiptablesインスタンスメタデータへのアクセスをブロックして、IAMロールの権限を無効化してみました。

試してみようと思ったきっかけは、AWSでのプロキシ利用について勉強していた時に、以下の記事を見つけたことです。

docs.aws.amazon.com

dev.classmethod.jp

169.254.169.254はインスタンスメタデータへのアクセスに使われること、IAMロールの権限の利用にメタデータへのアクセスが必要であることが記載されています。

また、以下の記事にも同様の記載がありました。

docs.aws.amazon.com

これらを見て、「iptablesで169.254.169.254へのアクセスを制限すれば、IAMロールの権限が無効化されるのか?」を試してみたくなったので、やってみました。

今回の環境について

今回は、AmazonS3ReadOnlyAccessの権限をIAMロールで付与したEC2インスタンスを用意しました。

[cloudshell-user@ip-10-0-104-65 ~]$ aws ec2 describe-instances --region ap-northeast-1 --instance-ids i-xxxxxxxxxxxxxxxxx \
> --query Reservations[].Instances[].IamInstanceProfile.Arn \
> --output text
arn:aws:iam::xxxxxxxxxxxxx:instance-profile/ec2-sample-s3readonly
[cloudshell-user@ip-10-0-104-65 ~]$ 
[cloudshell-user@ip-10-0-104-65 ~]$ 
[cloudshell-user@ip-10-0-104-65 ~]$ 
[cloudshell-user@ip-10-0-104-65 ~]$ aws iam list-attached-role-policies --role-name ec2-sample-s3readonly
{
    "AttachedPolicies": [
        {
            "PolicyName": "AmazonS3ReadOnlyAccess",
            "PolicyArn": "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
        }
    ]
}
[cloudshell-user@ip-10-0-104-65 ~]$ 



また、対象のEC2インスタンスのバージョンは以下です。

[ec2-user@ip-10-0-11-11 ~]$ asw --version
aws-cli/1.18.147 Python/2.7.18 Linux/4.14.225-168.357.amzn2.x86_64 botocore/1.18.6
[ec2-user@ip-10-0-11-11 ~]$ 



事前アクセス確認

まずはiptablesを設定する前に、インスタンスメタデータへのアクセスが可能であること、S3のリストが表示可能であることを確認してみます。

インスタンスメタデータへのアクセス

インスタンスメタデータへのアクセスは、curl http://169.254.169.254/latest/meta-data/で実施します。また、IAMロールから提供されたセキュリティ認証情報は、メタデータの中のiam/security-credentials/<ロール名>でアクセス可能です。

[ec2-user@ip-10-0-11-11 ~]$ curl http://169.254.169.254/latest/meta-data/
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hibernation/
hostname
iam/
identity-credentials/
instance-action
instance-id
instance-life-cycle
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
public-keys/
reservation-id
security-groups
services/[ec2-user@ip-10-0-11-11 ~]
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$
[ec2-user@ip-10-0-11-11 ~]$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-sample-s3readonly
{
  "Code" : "Success",
  "LastUpdated" : "2021-08-22T06:40:36Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIATxxxxxxxxxx73ZJD",
  "SecretAccessKey" : "/7ri3Cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxo8Rn",
  "Token" : "~省略~",
  "Expiration" : "2021-08-22T13:15:46Z"

メタデータへのアクセスは問題ありませんでした。

S3へのアクセス

続いて、S3へのアクセスが問題ないか確認します。

[ec2-user@ip-10-0-11-11 ~]$ aws s3 ls
2021-05-16 09:21:40 cf-templates-xxxxxxxxxxxx-ap-northeast-1
[ec2-user@ip-10-0-11-11 ~]$ 

こちらも問題なく表示されました。

iptablesの設定

それではiptablesの設定をしていきます。
とはいったものの、そもそもiptablesはインストールされているのでしょうか?分からなかったので確認してみます。

iptablesの確認

[ec2-user@ip-10-0-11-11 ~]$ sudo which iptables
/sbin/iptables
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ sudo iptables -nL
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
[ec2-user@ip-10-0-11-11 ~]$ 

コマンドはありますし、設定も一見すると存在するように見えますね。設定ファイルやサービスの有無はどうでしょうか。

[ec2-user@ip-10-0-11-11 ~]$ sudo systemctl list-unit-files --type=service | grep  iptables
[ec2-user@ip-10-0-11-11 ~]$
[ec2-user@ip-10-0-11-11 ~]$ sudo cat /etc/sysconfig/iptables
cat: /etc/sysconfig/iptables: No such file or directory
[ec2-user@ip-10-0-11-11 ~]$ 

見つかりませんでした。どうも、「iptablesコマンドはあるけどiptablesサービスはない」状態みたいですね。。

iptablesサービスのインストール

このままだとコマンドが通ってもiptablesが効かないので、サービスをインストールします。

[ec2-user@ip-10-0-11-11 ~]$ sudo yum -y install iptables-services 
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                               | 3.7 kB     00:00     
Resolving Dependencies
--> Running transaction check
---> Package iptables-services.x86_64 0:1.8.4-10.amzn2.1.2 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package               Arch       Version                  Repository      Size
================================================================================
Installing:
 iptables-services     x86_64     1.8.4-10.amzn2.1.2       amzn2-core      58 k

Transaction Summary
================================================================================
Install  1 Package

Total download size: 58 k
Installed size: 24 k
Downloading packages:
iptables-services-1.8.4-10.amzn2.1.2.x86_64.rpm            |  58 kB   00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : iptables-services-1.8.4-10.a [                             ] 1/1  Installing : iptables-services-1.8.4-10.a [###                          ] 1/1  Installing : iptables-services-1.8.4-10.a [######                       ] 1/1  Installing : iptables-services-1.8.4-10.a [#######                      ] 1/1  Installing : iptables-services-1.8.4-10.a [########                     ] 1/1  Installing : iptables-services-1.8.4-10.a [##################           ] 1/1  Installing : iptables-services-1.8.4-10.a [############################ ] 1/1  Installing : iptables-services-1.8.4-10.amzn2.1.2.x86_64                  1/1 
  Verifying  : iptables-services-1.8.4-10.amzn2.1.2.x86_64                  1/1 

Installed:
  iptables-services.x86_64 0:1.8.4-10.amzn2.1.2                                 

Complete!
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ 

インストールが出来たので、改めて設定ファイルやサービスの有無を確認してみます。

[ec2-user@ip-10-0-11-11 ~]$ sudo cat /etc/sysconfig/iptables
# sample configuration for iptables service
# you can edit this manually or use system-config-firewall
# please do not ask us to add additional ports/services to this default configuration
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ sudo systemctl list-unit-files --type=service | grep iptables
iptables.service                              disabled

問題ないようです。

iptablesの設定投入

それではiptablesの設定を入れていきます。メタデータへのアクセスはHTTPで行うので80番ポートだけ閉じれば大丈夫そうですが、今回は面倒なので169.254.169.254へのアクセスを全てDROPしてしまいます。

[ec2-user@ip-10-0-11-11 ~]$ sudo iptables -I OUTPUT 1 -p tcp -d 169.254.169.254 -j DROP
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ sudo iptables -L

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
DROP       tcp  --  anywhere             instance-data.ap-northeast-1.compute.internal 
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ sudo service iptables save
iptables: Saving firewall rules to /etc/sysconfig/iptables: [  OK  ]
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ sudo service iptables restart
Redirecting to /bin/systemctl restart iptables.service
[ec2-user@ip-10-0-11-11 ~]$ 

" instance-data.ap-northeast-1.compute.internal "へのDROP設定が入りました。

事後アクセス確認

それでは再度メタデータとS3へのアクセスを確認してみます。

[ec2-user@ip-10-0-11-11 ~]$ curl http://169.254.169.254/latest/meta-data/
^C
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-sample-s3readonly
^C
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ 
[ec2-user@ip-10-0-11-11 ~]$ aws s3 ls
Unable to locate credentials. You can configure credentials by running "aws configure".
[ec2-user@ip-10-0-11-11 ~]$ 

想定通り、メタデータとS3へのアクセスが出来なくなりました。

おわりに

というわけで、想定通りにiptablesメタデータへのアクセスをブロックすることで、IAMロールの権限を無効化することができました。

実際にEC2インスタンスiptablesを使う機会はあまり無いとは思いますが、メタデータが認証に使われていることが確認できて良かったです。

「AWSの基礎を学ぼう 特別編 最新サービスをみんなで触ってみる - Amazon Route 53 Resolver DNS Firewall」をJAWS-UG CLI支部チックにやってみた

はじめに

先日の7/31(土)に以下のハンズオンに参加しました。

awsbasics.connpass.com

今回は、ハンズオン当日にGUIで実施したDNS Firewallの設定を、CLIで実施してみたいと思います。
また、最近JAWS-UG CLI支部のハンズオンにちょくちょく参加するようになったので、折角なのでそちらで学習したお作法やコマンドを意識しながらやってみたいと思います。

JAWS-UG CLI支部についてはこちらをご参照ください。CLI手順だけでなく、サービスの特徴や運用の勘所なども学べて、とても勉強になります。

jawsug-cli.connpass.com



今回のハンズオンの内容と手順

今回のハンズオンの内容や手順については、以下にまとめられているのでこちらを参照ください。

dev.classmethod.jp

今回は、上記手順の「3. DNS Firewall 設定」と「4. 動作確認」をCLIで実施していきます。

CLIでの手順

それでは、CLIでの手順を実際に見ていきます。CLIで実施する場合、マネジメントコンソールでの手順と一部順番が変わるので、その辺も確認しながらやっていきます。

0.動作環境と使用コマンド

今回は、以下の動作環境で作業を実施しました。

YuY:~/environment $ aws --version
aws-cli/1.19.112 Python/2.7.18 Linux/4.14.238-182.422.amzn2.x86_64 botocore/1.20.112
YuY:~/environment $ 



DNS Firewallの設定は、route53resolverコマンドで実施します。コマンドの詳細は以下を参照してください。

route53resolver — AWS CLI 1.20.11 Command Reference

なお、CLIバージョン2では、バージョン1で使用可能だったサブコマンドがいくつか無くなっているようで、以下の手順は実行できませんでした。詳細は後述します。

1.事前のDNSクエリ確認

まずは事前のDNSクエリ確認を実施します。ハンズオン手順の「Step 2-3」に該当する箇所です。

YuY:~/environment $ dig example.com

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35625
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            300     IN      A       93.184.216.34

;; Query time: 2 msec
;; SERVER: 10.1.0.2#53(10.1.0.2)
;; WHEN: Sun Aug 01 08:13:09 UTC 2021
;; MSG SIZE  rcvd: 56

YuY:~/environment $ 

example.comのAレコードが返ってきていることが確認できます。

2.DNS Firewall ルールグループ作成

さて、いよいよここから本題のDNS Firewall設定です。まずはDNS Firewallのルールグループを作成します。

# ルールグループ名を定義
YuY:~/environment $ FW_RULE_GR_NAME='handson0731-gr'
YuY:~/environment $ 

# ルールグループを作成
YuY:~/environment $ aws route53resolver create-firewall-rule-group \
>     --name ${FW_RULE_GR_NAME}
{
    "FirewallRuleGroup": {
        "Status": "COMPLETE", 
        "Name": "handson0731-gr", 
        "CreationTime": "2021-08-01T08:44:58.245974Z", 
        "CreatorRequestId": "988bb4be-db45-419a-869e-e8624732a249", 
        "ModificationTime": "2021-08-01T08:44:58.245974Z", 
        "ShareStatus": "NOT_SHARED", 
        "Arn": "arn:aws:route53resolver:ap-northeast-1:XXXXXXXXXXXX:firewall-rule-group/rslvr-frg-3f7127xxxxxxxxxx", 
        "RuleCount": 0, 
        "OwnerId": "XXXXXXXXXXXX", 
        "Id": "rslvr-frg-3f7127xxxxxxxxxx", 
        "StatusMessage": "Created Firewall Rule Group"
    }
}
YuY:~/environment $ 

# ルールグループIDの取得
YuY:~/environment $ FW_RULE_GR_ID=$( \
> aws route53resolver list-firewall-rule-groups \
> --query FirewallRuleGroups[?Name==\`${FW_RULE_GR_NAME}\`].Id \
> --output text \
> ) \
> && echo ${FW_RULE_GR_ID}
rslvr-frg-3f7127xxxxxxxxxx
YuY:~/environment $ 

ルールグループIDを取得する理由は、この後のコマンドで引数として必要になるためです。

3.ドメインリスト作成

続いて、フィルタリングルールを作る前に、先にドメインリストの作成を実施します。CLIの場合、フィルタリングルールの作成時にドメインリストIDを指定する必要があるためです。

# ドメインリスト名を定義
YuY:~/environment $ DOMAIN_LIST_NAME='example-list'
YuY:~/environment $ 

# ドメインリストを作成
YuY:~/environment $ aws route53resolver create-firewall-domain-list \
> --name ${DOMAIN_LIST_NAME}
{
    "FirewallDomainList": {
        "Status": "COMPLETE", 
        "Name": "example-list", 
        "CreationTime": "2021-08-01T09:21:15.074053Z", 
        "CreatorRequestId": "4698e450-6c16-4115-8e38-eed0efb99e78", 
        "ModificationTime": "2021-08-01T09:21:15.074053Z", 
        "Arn": "arn:aws:route53resolver:ap-northeast-1:XXXXXXXXXXXX:firewall-domain-list/rslvr-fdl-7a6d3xxxxxxxxxx", 
        "DomainCount": 0, 
        "Id": "rslvr-fdl-7a6d3xxxxxxxxxx", 
        "StatusMessage": "Created Firewall Domain List"
    }
}
YuY:~/environment $

# ドメインリストIDの取得
YuY:~/environment $ FW_DOMAIN_LIST_ID=$( \
> aws route53resolver list-firewall-domain-lists \
> --query "FirewallDomainLists[?Name==\`${DOMAIN_LIST_NAME}\`].Id" \
> --output text \
> ) \
> && echo ${FW_DOMAIN_LIST_ID}
rslvr-fdl-7a6d3xxxxxxxxxx
YuY:~/environment $ 

ドメインリストIDも、ルールグループID同様、この後のコマンドで引数として必要となります。

4.ドメインリストへのドメインの追加

続いて、ドメインリストにドメインexample.com」を追加します。

# ドメインの定義
YuY:~/environment $ FW_DOMAIN='example.com'
YuY:~/environment $

# ドメインリストへのドメインの追加 
YuY:~/environment $ aws route53resolver update-firewall-domains \
> --firewall-domain-list-id ${FW_DOMAIN_LIST_ID} \
> --operation ADD \
> --domains ${FW_DOMAIN}
{
    "Status": "COMPLETE", 
    "Id": "rslvr-fdl-7a6d3d4778xxxxx", 
    "StatusMessage": "Created Firewall Domain List", 
    "Name": "example-list"
}
YuY:~/environment $ 

# ドメインの追加の確認
YuY:~/environment $ aws route53resolver list-firewall-domains \
> --firewall-domain-list-id ${FW_DOMAIN_LIST_ID} \
> --query "Domains[]" \
> --output text
example.com.
YuY:~/environment $ 



5.フィルタリングルール作成

続いて、フィルタリングルールを作成します。

# フィルタリングルール名を定義
YuY:~/environment $ FW_RULE_NAME='handson0731-rule'
YuY:~/environment $ 

# フィルタリングルールを設定
YuY:~/environment $ aws route53resolver create-firewall-rule \
> --name ${FW_RULE_NAME} \
> --firewall-rule-group-id ${FW_RULE_GR_ID} \
> --firewall-domain-list-id ${FW_DOMAIN_LIST_ID} \
> --action BLOCK \
> --block-response NODATA \
> --priority 1
{
    "FirewallRule": {
        "Name": "handson0731-rule", 
        "CreationTime": "2021-08-01T11:52:21.983902Z", 
        "CreatorRequestId": "7dfbada3-455a-420b-975a-5a4c489761a1", 
        "ModificationTime": "2021-08-01T11:52:21.983902Z", 
        "Priority": 1, 
        "BlockResponse": "NODATA", 
        "FirewallDomainListId": "rslvr-fdl-7a6d3xxxxxxxxxx", 
        "Action": "BLOCK", 
        "FirewallRuleGroupId": "rslvr-frg-3f7127xxxxxxxxxx"
    }
}
YuY:~/environment $ 



6.ルールグループにVPCを関連付け

続いて、ルールグループにVPCを関連付けます。関連付けにはVPC IDが必要なので、まずはVPC IDを取得します。

# VPC名の定義
YuY:~/environment $ FW_VPC_NAME='handson-dns-firewall-20210731-VPC'
YuY:~/environment $ 

# VPC IDの取得
YuY:~/environment $ FW_VPC_ID=$( \
> aws ec2 describe-tags --filter "Name=tag-value,Values=${FW_VPC_NAME}" \
> --query "Tags[].ResourceId" \
> --output text \
> ) \
> && echo ${FW_VPC_ID}
vpc-0808ed7xxxxxxxxxx
YuY:~/environment $ 

VPC名は、「{ハンズオン手順のStep1-2で入力したスタック名}-VPC」になっているかと思います。(ハンズオン手順では「handson0731」になっていますが、私は「handson-dns-firewall-20210731」と入力していたため、そちらを指定しています。)

VPC IDが取得できたら、ルールグループにVPCを関連付けます。

# 関連付け名の定義
YuY:~/environment $ FW_ASSOCIATE_NAME='handson-dns-firewall-20210731'
YuY:~/environment $ 

# 関連付け 
YuY:~/environment $ aws route53resolver associate-firewall-rule-group \
>     --firewall-rule-group-id ${FW_RULE_GR_ID} \
>     --vpc-id ${FW_VPC_ID} \
>     --priority 101 \
>     --name ${FW_ASSOCIATE_NAME}
{
    "FirewallRuleGroupAssociation": {
        "Status": "UPDATING", 
        "VpcId": "vpc-0808ed7xxxxxxxxxx", 
        "Name": "handson-dns-firewall-20210731", 
        "CreationTime": "2021-08-01T12:10:42.351432Z", 
        "MutationProtection": "DISABLED", 
        "CreatorRequestId": "3c68e290-da71-476e-bb26-7f108683b938", 
        "ModificationTime": "2021-08-01T12:10:42.351432Z", 
        "Priority": 101, 
        "StatusMessage": "Creating Firewall Rule Group Association", 
        "Id": "rslvr-frgassoc-7a0ba2xxxxxxxxxx", 
        "Arn": "arn:aws:route53resolver:ap-northeast-1:XXXXXXXXXXXX:firewall-rule-group-association/rslvr-frgassoc-7a0ba2xxxxxxxxxx", 
        "FirewallRuleGroupId": "rslvr-frg-3f7127xxxxxxxxxx"
    }
}
YuY:~/environment $ 

上記を見ると、Statusが"UPDATING"、StatusMessageが"Creating Firewall Rule Group Association"となっており、まだ関連付けが完了していないことが分かります。そのため、少し時間を置いてから、次の手順で再度StatusとStatusMessageを取得します。

7.ルールグループへのVPC関連付けの完了確認

再度StatusとStatusMessageを取得、関連付けが完了したことを確認します。

# 関連付けIDの取得
YuY:~/environment $ FW_ASSOCIATION_ID=$( \
> aws route53resolver list-firewall-rule-group-associations \
> --firewall-rule-group-id ${FW_RULE_GR_ID} \
> --query "FirewallRuleGroupAssociations[].Id" \
> --output text \
> ) \
> && echo ${FW_ASSOCIATION_ID}
rslvr-frgassoc-7a0ba2xxxxxxxxxx
YuY:~/environment $ 

# Status、StatusMessageの取得
YuY:~/environment $ aws route53resolver get-firewall-rule-group-association \
> --firewall-rule-group-association-id ${FW_ASSOCIATION_ID} \
> --query "FirewallRuleGroupAssociation.[Status, StatusMessage]" \
> --output text
COMPLETE        Finished rule group association update
YuY:~/environment $ 

Statusが「COMPLETE」、StatusMessageが「Finished rule group association update」に変わりました。

8.動作確認

DNS Firewallの設定が一通り終わったので、改めて動作確認します。ハンズオン手順の「4.動作確認」の部分です。

YuY:~/environment $ dig example.com

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1611
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;example.com.                   IN      A

;; Query time: 3 msec
;; SERVER: 10.1.0.2#53(10.1.0.2)
;; WHEN: Sun Aug 01 12:26:30 UTC 2021
;; MSG SIZE  rcvd: 40

YuY:~/environment $ 
YuY:~/environment $ 
YuY:~/environment $ 
YuY:~/environment $ dig example.com @8.8.8.8

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> example.com @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14379
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            20694   IN      A       93.184.216.34

;; Query time: 3 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Sun Aug 01 12:27:02 UTC 2021
;; MSG SIZE  rcvd: 56

YuY:~/environment $ 

ハンズオン手順と同じ結果になりました。どうやら問題なくDNS Firewallの設定が出来たようです。

補足:CLIバージョン1とバージョン2の差異について

冒頭にも記載した通り、今回のCLI手順はCLIバージョン1での手順となります。CLIバージョン2では「create-firewall-rule-group」等のサブコマンドが使えないらしく、同じ手順を実行することが出来ませんでした。

以下に、バージョン1とバージョン2で使用できるサブコマンドの差分を記載します。※マイナーバージョンの違いでも差分が出るかもしれないので、一例として見てください。

# aws cliバージョン1の場合
YuY:~/environment $ aws --version
aws-cli/1.19.112 Python/2.7.18 Linux/4.14.238-182.422.amzn2.x86_64 botocore/1.20.112
YuY:~/environment $ 
YuY:~/environment $ 
YuY:~/environment $ aws route53resolver 
associate-firewall-rule-group                 get-resolver-rule 
associate-resolver-endpoint-ip-address        get-resolver-rule-association 
associate-resolver-query-log-config           get-resolver-rule-policy 
associate-resolver-rule                       import-firewall-domains 
create-firewall-domain-list                   list-firewall-configs 
create-firewall-rule                          list-firewall-domain-lists 
create-firewall-rule-group                    list-firewall-domains 
create-resolver-endpoint                      list-firewall-rule-group-associations 
create-resolver-query-log-config              list-firewall-rule-groups 
create-resolver-rule                          list-firewall-rules 
delete-firewall-domain-list                   list-resolver-dnssec-configs 
delete-firewall-rule                          list-resolver-endpoint-ip-addresses 
delete-firewall-rule-group                    list-resolver-endpoints 
delete-resolver-endpoint                      list-resolver-query-log-config-associations 
delete-resolver-query-log-config              list-resolver-query-log-configs 
delete-resolver-rule                          list-resolver-rule-associations 
disassociate-firewall-rule-group              list-resolver-rules 
disassociate-resolver-endpoint-ip-address     list-tags-for-resource 
disassociate-resolver-query-log-config        put-firewall-rule-group-policy 
disassociate-resolver-rule                    put-resolver-query-log-config-policy 
get-firewall-config                           put-resolver-rule-policy 
get-firewall-domain-list                      tag-resource 
get-firewall-rule-group                       untag-resource 
get-firewall-rule-group-association           update-firewall-config 
get-firewall-rule-group-policy                update-firewall-domains 
get-resolver-dnssec-config                    update-firewall-rule 
get-resolver-endpoint                         update-firewall-rule-group-association 
get-resolver-query-log-config                 update-resolver-dnssec-config 
get-resolver-query-log-config-association     update-resolver-endpoint 
get-resolver-query-log-config-policy          update-resolver-rule
YuY:~/environment $ 



# aws cliバージョン2の場合
[cloudshell-user@ip-10-0-141-253 ~]$ aws --version
aws-cli/2.1.28 Python/3.8.8 Linux/4.14.225-168.357.amzn2.x86_64 exec-env/CloudShell exe/x86_64.amzn.2 prompt/off
[cloudshell-user@ip-10-0-141-253 ~]$ 
[cloudshell-user@ip-10-0-141-253 ~]$ aws route53resolver 
associate-resolver-endpoint-ip-address       delete-resolver-query-log-config             get-resolver-query-log-config                list-resolver-endpoint-ip-addresses          put-resolver-query-log-config-policy
associate-resolver-query-log-config          delete-resolver-rule                         get-resolver-query-log-config-association    list-resolver-endpoints                      put-resolver-rule-policy
associate-resolver-rule                      disassociate-resolver-endpoint-ip-address    get-resolver-query-log-config-policy         list-resolver-query-log-config-associations  tag-resource
create-resolver-endpoint                     disassociate-resolver-query-log-config       get-resolver-rule                            list-resolver-query-log-configs              untag-resource
create-resolver-query-log-config             disassociate-resolver-rule                   get-resolver-rule-association                list-resolver-rule-associations              update-resolver-dnssec-config
create-resolver-rule                         get-resolver-dnssec-config                   get-resolver-rule-policy                     list-resolver-rules                          update-resolver-endpoint
delete-resolver-endpoint                     get-resolver-endpoint                        list-resolver-dnssec-configs                 list-tags-for-resource                       update-resolver-rule
[cloudshell-user@ip-10-0-141-253 ~]$ 

aws-cliバージョン2の場合に、DNS Firewallの設定をCLIでどのように実施すればよいのかは、少し調べてみただけではよく分かりませんでした。また機会がある時に調査したいと思います。

おわりに

というわけで、CLIDNS Firewallの設定をすることが出来ました。
手順書の内容をコピペするだけではなく、これまで勉強した事を活かしながら手探りでやって、結果的に上手くいったので良かったです。
ただ、ハンズオン手順の「5.DNSクエリログの記録」と「6.片付け」がまだ残っているため、今度はそちらもCLIで実施したいと思います。

追伸:今回、「JAWS-UG CLI支部チックにやる」と冒頭で記載しましたが、CLI支部では変数を定義した時には必ずヒアドキュメントで中身の確認をしているのに、今回は省略しちゃいました。この辺、まだまだ徹底が足りないですね。

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

おわりに

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

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

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