AWSの複雑な仕組みをTerraform化で紐解く事例

インフラエンジニアも、そうでないエンジニアも、程度の差こそあれAWS(パブリッククラウド)の知識は欠かせないものになりつつあります。しかしながら、食わず嫌いだけならまだしも、AWSにそこそこ取り組み続けているのに、しばしば嫌気がさすことすらあるのは、AWSの複雑さが原因の1つであるといっていいでしょう。

今回は、そんな複雑な仕組みの一つを、Terraform用にコード化することでココロのスキマ、お埋めします。



Infrastructure as Code の過程

Infrastructure as Code によって、手動管理をやめて自動化することのメリットは言うまでもないですが、今回は自動化する過程にこそ非常に大きなメリットがある、という視点にスポットを当てていきます。

AWSは多機能で便利であるがゆえに、複雑です。そして、複雑であるがゆえに、お客さんに簡単に扱ってもらえるよう、操作が簡素化されています。それは自然な成り行きではありますが、1つの画面操作で S3 / SNS / CloudWatch / IAM / etc… と自動的に複数のリソースが作成されたその認識状態を、私はあまり好みません。

全てを正しく把握できていない、ふわふわした状況では、全てを削除したい時や、トラブルシューティング、リソース設計において、正確迅速に対処できないからです。

そのため、一度は管理画面で自動作成するものの、1つ1つのリソースを順に追って、丁寧にコード化することで全容を把握することが、AWSを正しく扱うための必須事項であると考えます。その、過程を追う流れを見ていただければ、と思います。


CloudTrail を設定しよう

今回は、EC2 や RDS といった大事なリソースに影響なく、しかし程よく複雑な CloudTrail を題材にしてみます。

CloudTrail は、APIの利用履歴を保存してくれるサービスです。ただ保存するだけでも意味はなくもないですが、そのログを監視して特定の条件を検知し、通知する、といったことまでやるのがコイツの存在意義かと思います。

ではまず、管理画面のキャプチャでございます。

CloudTrail の作成




当初はリージョン毎の設定だったのですが、途中から1つのリージョンで設定するだけで全リージョンの情報を保存してくれるようになって、便利になりました。

ここで『作成』を押すと、CloudTrail と S3バケット が作成されます。

CloudWatch Logs を作成

作成後に設定を見に行くと、CloudWatch Logs の作成が下部に追加されています。最初の CloudTrail 作成時に出さなかったのは、一見の複雑さを抑えるためでしょうか、それとも CloudTrail 作成時のタイムラグの都合でしょうか、とにかく検知に必要なので設定します。



これで CloudWatch 内のログ項目に、ロググループが作成されました。


IAM を作成

CloudWatch Logs に LogStream を作成したり、ログイベントを保存するために、IAM Role が必要となるため、作成することになります。



これで、IAM Role と そのポリシー が作成されることになります。


メトリックスフィルタを作成

デフォ名だと CloudWatch Logs に「CloudTrail/DefaultLogGroup」というロググループができています。この中に、「ログストリーム」が自動的に作成されて、ログが溜まっていきます。

このログに対して、一定条件のフィルタをするためには、ロググループに対して「メトリックスフィルタ」を作成することになります。



もう1ページあります。



これで、メトリックスフィルタが作成されます。このフィルタパターン

は、IAM User によるコンソールログインに失敗したログを検知します。ただし、なぜかRootユーザーだけはログイン失敗のログ自体が保存されないので、検知することができません。

他に、こんな感じのも追加したら、オフィスなどの特定外でのログイン成功を検知できます。


そして、ここにきて「メトリックス名前空間」という単語が出てきたりして、そろそろイヤになってくる頃合いでしょうか。私もキャプチャ取るのに飽きてきました。


アラームを作成

メトリックスフィルタを作成しただけだと、特に何もおきないので、



アラームを作成します。フィルタに引っかかったら(>= 1)SNSに送る、という感じです。



ここでSNSを作成することも可能ですが、このキャプチャでは先にSNSでトピック作成を済ませています。


ここまでのリソースまとめ

ここまでで、特定のログがあったらSNSをキックする、というこれらの土台ができました。

  • CloudTrail
  • S3バケット
  • IAM Role と Policy (Trail による CloudWatch Logs用)
  • CloudWatch Logs LogGroup
  • CloudWatch Metric Filter
  • CloudWatch Alarm
  • SNS Topic (Lambda用)

  • 完全に飽きたところでしょうが、これだけだと何もアクションが無いので、コーヒーブレイクを挟んで紐解き作業に移っていきます。


    CloudTrail+CloudWatch をTerraform化しよう

    このままLambdaに突入するとワケワカメになるので、いったんここまでをTerraformコードにしてみましょう。さきほどまとめたリソース群を、丁寧にコーディングすることで、辟易した心を清めるのです。

    管理画面で作成したリソース1つ1つを順に追っていき、Terraformのドキュメントと照らしあわせてリソースの仕様を確認しつつ、細部を自分好みに仕上げていきます。

    以下のコードは、執筆時点で最新の Terraform 0.6.16 として書いたものになります。

    CloudTrail

    Terraform のドキュメントページも貼っておきます。
  • AWS: cloudtrail – Terraform by HashiCorp
  • これ自体はさほど難しいものではないですが、必要な関連リソースが3つもあるので、じゃんじゃんいきましょう。

    S3

    S3バケットが必要なので作成します。ログごときをずっと放置するともったいないばあさんに怒られるので、一定期間で GLACIER に移動したり削除しています。ポリシーは、自動作成の内容だとアカウントIDが入ったりして下記内容よりもう少しパスが深いのですが、複数のアカウントを管理しているため共通化できるよう、少々簡略化しています。
  • AWS: aws_s3_bucket – Terraform by HashiCorp
  • バケット名の変数は、variables.tf といったファイルに括りだすことで、別アカウントのコードにて編集するファイルを限定することと、世界ユニークな命名において確実に被らないようにすることを目的としています。


    IAM

    TrailさんがCloudWatch Logsできるように、Roleを作ってあげます。こちらもS3同様、共通化のために一部簡略化しています。

    注意点として、IAMは新規作成と、それを利用するリソース作成を同時に行うと、IAM作成タイムラグのために、後の関連リソース作成でエラーになることがあります。そのため、二段階の実行に分けたうちの先に実行する運用ルールがベターです。
  • AWS: aws_iam_role – Terraform by HashiCorp

  • CloudWatch Logs LogGroup と Metric Filter

    Trailさんが、この LogGroup に対して IAM Role を使って LogStream の作成と、ログの書き込みを行ってくれます。デフォ名はあまり好きじゃなかったので簡素なものに変更しています。

    Metric Filter は今回は直に条件を書き込んでますが、数が多い場合はハッシュ変数を駆使して小奇麗にした方がよさ気です。
  • AWS: aws_cloudwatch_log_metric_filter – Terraform by HashiCorp

  • CloudWatch Alarm

    MetricFilter に引っかかったログを捉えてSNSをキックします。引っかかったかどうかは、値が 1以上 になることで判定しています。
  • AWS: cloudwatch_metric_alarm – Terraform by HashiCorp

  • SNS

    CloudWatch Alarm の受け皿としてSNS Topicを作成します。

    そしてそのアクションとして Subscription を作成するのですが・・・見ての通り今回は Lambda Function を起動するので、まだ続きます。。
  • AWS: sns_topic_subscription – Terraform by HashiCorp


  • Lambda を作成しよう

    ここまでのコード化によって、今回の主な目的である『AWSの複雑な仕組みを紐解く』のは大枠完了しています。自動的にあっちこっち作られたリソースは何と何で、実はCloudWatchを分解するとこうなっている、というのが大体見えたはずだからです。

    ただ、これで終わると今回の CloudTrail を使う真の目的である、『予期せぬコンソールログインを検知する』を達成できていません。上記の SNS Subscription をもし E-Mail にした場合、MetricFilterで 1 を検知しましたよー という全く意味を成さない内容のメールが届くだけになるため、詳細な内容を可視化するために、どうしても Lambda に頼る必要があります。

    Lambda Python

    今回の Lambda Function は下記のようなもので、ポイントは

  • 登録した MetricFilter で引っかかるログの詳細を取得・成形し、SNS経由でメールする
  • コンソールログイン発生から、ログ保存、MetricFilter、Lambda発動まで、約9分前後かかるため、15分以内のログから採取している(重複通知を許容)
  • 1つのメーリングリストに送ることにしたので、複数アカウントからきても内容を見分けやすいよう、IAMエイリアス名を設定して使っている
  •  


    SNS

    最終的な通知先として、Topicを作成し、作成後に管理画面からE-Mailを登録します。


    IAM

    Lambda用のRoleを作成します。今回はVPC用の自動作成の内容を元に、最低限な内容にしましたが、いつもは他のLambdaでも使っているのでEC2とかRDSも入っています。


    Lambda

    そして、、ふぅ~~、最後にzipに固めて、Lambdaを作成します。
  • AWS: aws_lambda_permission – Terraform by HashiCorp

  • IAMエイリアス名

    おっと、忘れてました。IAMダッシュボードの「IAM ユーザーのサインインリンク」の横にある、『カスタマイズ』で、アカウントを判別できる名前を登録しておきます。これはLambdaスクリプトで個人的必要性に応じて使っているだけなので、全然必須じゃないです。

    本当は、管理画面右上に表示される、アカウント名を使えればそれでよかったのですが、APIでの取得が見つからなかったので、これで代用した感じになります。


    Terraformを実行する

    これで、1回目はIAM関連のみを実行し、その後に残りを追加実行することで、CloudTrail+Lambda のコンソールログインログ検知システムのできあがりとなります。


    検知メールの確認

    少し時間が経てば、S3 や CloudWatch Logs にログが保存されていくのを確認できます。

    そして、自前で仕込んだ検知システムの動作確認をするために、パスワード(コンソールログイン)を有効にした、適当な IAM User に対して、ワザとログイン失敗をしてみます。すると、十数分後には下記のようなメールが届くはずです。


    ログの内容は面倒くさい、というよりは選りすぐって減らす理由も特にないので、JSONをそのまま成形して出力しただけになります。

    何もないよりは、こういう仕込みをしておくと、アカウントのセキュリティを向上させることができるので、Terraform+Lambdaコードを丸っとコピペして実行しておくだけでも、やっておくとよいのかな~と思います。

    また、Config というリソース変化を記録するサービスもあるので、同様に有効にしておくとよいです。ただ、Config自体はTerraformが対応していないので、Config で利用する S3 や IAM を同じような手順でコード化して、最後にConfigを手動で有効にするだけにするとよいでしょう。

    1つのアカウントしか管理しないならば、これらは不要かもしれませんが、複数アカウントならば最初からコード化してしまうほうが、ココロのスキマを作らずに済むはずです。



    ・・・まぁ、ぶっちゃけ、こういう記事は、書いてて全く楽しくないですね!Terraformのコード事例が世の中にそれほど多くないので、役に立つかなと思って書き始めたものの、完全に後悔して何度か放り投げようとしてしまいました。

    また、Terraformのコード管理と実行は、GitLab CI で行っているのですが、そこまで図示しようとすると汚物にまみれたような記事になりそうでしたので、この辺で切り上げたいと思います。

    Lambda のコーディングで心を癒やしつつ取り組むのが、長続きのコツです、多分!!