Tech Sketch Bucket of Technical Chips by TIS Inc.

CloudFormationとLambdaを用いたAWSアカウントの初期設定

Pocket

AWSはEC2やS3のようなシステムを支える基盤としてのサービスだけでなく、
高度なセキュリティの実現や監査証跡の記録を行うためのIAMやCloudTrailのような各種サービスも充実しています。
しかしそれらのサービスの多くの機能はアカウント作成直後の状態では有効化されていません。

実際に業務でAWSを活用するのであれば、こうしたサービスを適切に利用することは欠かせません。
またAWSに詳しい人ばかりが運用に携わるわけではないことを考えると、AWSを初めて使うようなユーザでも簡単に設定できることが望ましいです。
幸い、AWSはこうした作業を自動化するためのAPIやサービスを豊富に提供しています。
今回は今年の4月から利用可能になった、AWS CloudFormationとAWS Lambdaの連携機能であるLambda-backed Custom Resourceを使って各種初期設定を行うCloudFormationテンプレートを作成してみました。

AWS CloudFormation

AWS CloudFormationは一定の形式で書かれたTemplateに従って、AWSの各種サービスが提供している機能やリソースを自動的に作成・設定してくれるサービスです。AWSのAPIを直接利用しようとすると、実行する環境を用意するにも様々なことを覚えなければいけませんが(例えばIAM RoleやAccessKeyについてなど)、CloudFormationを使えば、テンプレートさえ事前に作りこんでおけばユーザは必要なパラメータを入力するだけで必要な環境を得ることができます。

とは言え、CloudFormation単体では扱えない設定項目も存在します。CloudFormationはAWSの各種リソースを管理することを主眼においているため、例えばAccountPasswordPolicyのようなアカウント自身の設定の中にはCloudFormationで扱えないものもあります。

またCloudFormationによって作成したリソースは、CloudFormationのStackを消してしまうと一緒に削除されてしまいます。これはAWS上のリソースをまとめて管理する上で非常に便利な機能なのですが、今回のようにアカウント全体のセキュリティに関する設定の場合は、ユーザが間違えて消してしまう可能性があるのは望ましくありません。そこで今回はAWS Lambdaを組み合わせました。

AWS Lambda

AWS Lambdaは、EC2インスタンスの様な実行環境を自前で用意することなく、条件に応じて登録したコードを自動的にクラウド上で実行してくれるサービスです。CloudFormationから処理を呼び出すだけであればEC2インスタンスとcloud-initを用いても可能ですが、AWS Lambdaを使うことにより以下のメリットが得られます。

  • 安い
    • EC2インスタンスを起動すると最低でも1時間分の料金がかかりますが、Lambdaなら実行時間と使用するリソース量に応じた料金だけで済みます
  • 早い
    • EC2インスタンスを新規構築すると起動まで最低でも数分かかりますが、Lambdaならすぐに実行できます
  • ログが残る
    • EC2インスタンスでスクリプトを実行するとログはそのインスタンス内で生成されるため、インスタンスを止めてもログを残しておくためにはS3やCloudWatch Logs等の別の場所に転送しておく必要があります
    • Lambdaなら実行ログは自動的にCloudWatch Logsに保存され、Management Console等から簡単に確認することができます。

便利なAWS Lambdaですが、登録した処理を開始するためには何らかのトリガーが必要になります。
直接AWS LambdaのAPIを呼び出すという方法も有りますが、今回はAWSに詳しくない人でも簡単に実行できるように、CloudFormationのLambda-backed Custom Resourceを使用しました。

Lambda-backed Custom Resource

Lambda-backed Custom Resourceは、CloudFormationからパラメータ付きでLambda Functionを呼び出し、その実行結果を受け取ることができるCloudFormationのResourceの一つです。
公式ドキュメントはこちらにあります。

このLambda-backed Costom Resourceを使えば、AWS CloudFormationからAWS Lambdaにパラメータを渡して実行し、AWS Lambdaから取得した情報を元にStackを構築したりすることができます。うまく使うと外部リソースとの連携などが容易になる機能で、今回はこの機能を、ユーザが入力したパラメータをLambda Functionに渡して実行するために使っています。CloudFormation経由でLambdaを扱えるようになることで、テンプレートさえ用意されていればAWSの知識がそれほど無い人でも簡単にLambdaを利用できるようになります。

今回自動化する初期設定の内容

今回は以下の内容をAWS Lambdaから設定しています。

AccoundPasswordPolicyの設定

IAM Userを個別に払い出してパスワードの管理を各自に任せた場合、強度の弱いパスワードが使われる可能性があります。
今回は事前に定義したいくつかのレベルの中から選択する形で、
パスワードの最小文字数、英数記号全ての使用必須の有無、パスワード有効期限の有無などのパスワードポリシーを設定しています。

CloudTrailの全リージョンでの有効化とCloudWatch Logsとの連携

AWSの全ての操作履歴を残すCloudTrailの利用は、不正なアクセスの検知やアクセス証跡の記録に欠かせません。
またCloudTrailとCloudWatch Logsを連携させておくと、特定の条件に一致したログを抽出するといったことも簡単にできるようになります。
ただしCloudTrailのログはS3上に保存されるため、若干のS3利用料金が発生します。

CloudTrailについては過去の記事でも簡単に紹介しています。

Rootアカウントでのログイン検出時にアラートメールを送信

AWSでは原則として全ての権限を持つRootアカウントを日常的に利用すべきではなく、IAM Userを個別に作成して必要な権限だけを付与した上で利用することが推奨されています。(公式サイトのIAM Best Practices参照)
このようなBest Practicesに従って運用している場合、Rootアカウントでログインされるというのは不正アクセスである可能性が高いと言えます。今回はRootアカウントでのログインがCloudTrailによって記録された際にメール通知を行うように設定しています。

実際のコード

コードの本体は、以下のGitHub Repositoryで公開しています。
実行方法についてはREADMEをご覧下さい。

今回の記事ではLambda-backed Custom Resourceに関連する特徴的な箇所だけピックアップして紹介します。

CloudFormationのLambda-backed Custom Resource

前者のLambdaFunctionの部分でLambda Functionを新規作成し、後者のExecuteLambdaFunctionの部分でLambda Functionを実行するためのLambda-backed Custom Resourceを作成しています。

CloudFormationのCustom Resourceは他の一般的なResourceと違って、Typeの項目が固定値ではなく"Custom::(任意文字列)"の形で名前をつけることができます。今回はTypeとしてCustom::InitAWSという名前をつけています。ここでつけた名前は、後で "Fn::GetAtt" でResponseDataを参照するためのKeyとして使われます。例えば { "Fn::GetAtt" : ["InitAWS", "SomeKey"] } とすると、Lambdaが返したResponseData内の"SomeKey"の値を取得することができます。

Properties内に書かれているKeyの内、ServiceTokenはこのCustomResourceがRequestを送信する先を表しています。
今回はLambda Functionを呼び出すので、同じTemplate内で作成しているLambda FunctionのARN (Amazon Resource Name)を指定しています。
Properties内のその他のKeyは、全てパラメータとしてLambda Functionに渡されます。
ここではユーザがCloudFormation Parametersとして入力した値をLambda Functionに渡しています。

Lambda側

今回はNode.jsのコードをLambda上で実行しています。
AWS Lambdaに登録されたコードは、以下の様なOS/ミドルウェアの環境で実行されます。

AWS Lambdaに渡すzipファイルの中にnode_modules以下を同梱しておけば、任意のモジュールも利用することができます。

Lambda-backed Custom ResourceからLambda Functionが呼び出されると、Lambda Function作成時に指定したFunctionが実行されます。
今回のコードの場合は、先ほどのTemplate上でHandlerとして指定したindex.handlerに対応する、index.js ファイル内のexports.handlerの箇所のFunctionが呼び出されます。
CloudFormationから送られたパラメータは event.ResourceProperties 内に格納されているので、それをもとにHandler内で処理を実行し、結果に応じてResponseを返せば処理は終了します。
sendResponseのfunctionは公式サイトのドキュメント内で例示されたものをそのまま使っています。英語版のドキュメントではこの部分がcfn-responseというmoduleに置き換えられているようなので、今後は自前で定義する必要が無くなるかもしれません。

今回のTemplateでは利用していませんが、responseData内に格納した値は、CloudFormation側でFn::GetAttを用いて取り出すことができます。これをうまく使えば、CloudFormation単体では難しかった様々なことができそうです。

まとめ

AWS CloudFormationはインフラ構築を自動化する非常に強力なツールでしたが、
その範囲がAWS Lambdaとの連携にも広がったことでさらに活用の場が広がったと思います。
今回初めてNode.jsのコードを書いたのですが、非同期処理に慣れはいるものの、AWS SDKを使ったちょっとした処理の範囲であれば、他の言語でSDKを使ったことがある人ならそこまで苦労せずに作れるとおもいます。

今回のテンプレートでは誰でもはじめに設定しておくとよい3項目を実装していますが、
今回設定した内容以外にも、

  • 標準的な運用ルールに合わせたIAM PolicyやIAM Groupの生成
  • 課金額が一定額を超えたときのアラート送信設定
  • 業務上利用される特定IP以外からのアクセスを検知した際のアラート送信設定

など、AWSアカウントに初期設定しておくと良い項目は色々とあると思います。
皆さんも自分にあった形にカスタマイズしたテンプレートを作ってみてはいかがでしょうか。

エンジニア採用中!私たちと一緒に働いてみませんか?