Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

EC2でアプリケーションシークレットを安全に管理する方法 〜AWS KMSとS3による暗号化の実践〜

EC2にアプリケーションをデプロイする際には、Auto Scalingグループの活用がおすすめです。Auto Scalingグループを使えば、需要の増減に応じた自動的なスケールアップ・ダウンや、障害インスタンスからの自動復旧が、すべて手動操作なしで実現できます。ただし、これらを正しく機能させるには、各インスタンスが起動完了後すぐに本番トラフィックを処理できる状態になっている必要があります。新しいサーバーで手動設定を行ったり、Capistranoで初回デプロイを実行してから公開する運用に慣れている方にとっては、そこまでの自動化に少し追加の手間が必要になるでしょう。

具体例:Railsアプリケーションの自動デプロイ

例として、ユーザー向けWebアプリケーションをRailsで構築しているケースを見てみましょう。各インスタンスは起動完了の時点で、ロードバランサーから転送されるリクエストに即座に応答できるよう、Railsアプリが待機している必要があります。これを実現するために、筆者はまずAnsibleでプロビジョニングしたインスタンス(アプリ本体、nginxなどをセットアップ済み)のスナップショットからカスタムAMIを作成しました。続いて、Auto Scalingグループのユーザーデータ(userdata)から呼び出すスクリプトを用意します。このスクリプトはCapistranoのデプロイ処理を再現するもので、gitから最新コードを取得し、bundlerを実行するといった処理を行います。これでアプリと依存関係はすべて揃いました。残る課題は何でしょうか?

アプリケーションシークレット:デプロイ担当者の悩みの種

ここで立ちはだかるのがアプリケーションシークレット(機密情報)の扱いです。Gitリポジトリなど漏えいのおそれがある場所には保存したくない一方で、アプリの実行時には必ず参照できなければなりません。しかもAuto Scaling環境では、シークレットが必要になった瞬間に人間が手動で配置することはできません。

この問題への答えの一つが、HashiCorp製の「Vault」です。アプリが必要とするまでシークレットを秘匿し続けるという課題のために特化して開発された、非常に優れたソフトウェアです。ただし欠点もあります。Vault自体をプロビジョニングし、運用し続けなければならない点です。つまり、常時稼働させて管理すべきサービスがもう一つ増えてしまうのです。

もう一つの選択肢は、共有ストレージ(ここでは当然S3)にシークレットを保存し、そのバケットやキーへのアクセス権を自分たちのインスタンスだけに限定する方法です。これはIAMロールを使えば実現できます。IAMロールにポリシーをアタッチし、制限対象のS3リソースへのアクセスを許可すればよいのです。しかし、すべてのシークレットを平文のままS3に置くと、意図しない露出のリスクが依然として残ります。うっかりバケットへのアクセス権を持つ他者に、あるいは最悪の場合インターネット全体にデータを公開してしまう可能性もあるのです。

シークレットをS3に保存する前に暗号化しておき、アプリ側で必要なときに読み込んで復号できるとしたら、素晴らしいと思いませんか?

秘密の素材:Amazon KMS(Key Management Service)

AmazonのKey Management Service(KMS)は、暗号化キーを操作するためのAPIを提供するサービスです。IAMロールとAws::S3::Encryptionモジュールを組み合わせれば、わずか数行のコードで、S3上では暗号化されたままのシークレットをアプリケーションへ読み込めます。

本題に入る前に、KMSとS3を組み合わせたシークレット保存手法について素晴らしい記事を執筆されたDon Mills氏に感謝を伝えたいと思います。筆者は同氏のアプローチを少し変更し、IAMロールに依存するとともに、KMSキーのIDをシークレットとは別に管理する方式を採用しました(キー情報をS3上のシークレットと一緒に保存しない形です)。

KMSはマスター暗号化キーを生成し、それを使ったデータの暗号化・復号を可能にします。暗号化を依頼すると、KMSはマスターキーに基づく一時キーを発行します。実際の暗号化・復号には、この一時キーが使われます。

キーを生成するには、IAMコンソールを開いて「Encryption Keys(暗号化キー)」のリンクを選択します。キー作成時には、このキーを使用できるIAMユーザーやロールの指定を求められます。ここで、Auto Scalingグループに属するEC2インスタンスに割り当てる予定のロールを選択してください。そしてキーのARNを控えておきましょう。後ほど使用します。

ルー作り:KMSとIAMを半々で

キーを作成したら、IAMコンソールで先ほど選択したIAMロールを編集します。以下のようなポリシーをアタッチして、シークレット保存先のバケットへのアクセスを許可しましょう。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1476277816000",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:HeadObject"
            ],
            "Resource": [
                "arn:aws:s3:::yourbucket/secrets.yml"
            ]
        }
    ]
}

キーの設定とポリシーのアタッチが完了すれば、Aws::S3::Encryption::Clientインスタンスを通じてKMSとS3を操作できるようになります。以下は、シークレットファイルを取得し、その内容を環境変数へ読み込むサンプルコードです。

begin
  es3 = Aws::S3::Encryption::Client.new(kms_key_id: ENV['KMS_KEY_ID'])
  YAML.load(es3.get_object(bucket: "yourbucket", key: "secrets.yml").body.read).each do |k, v|
    ENV[k] ||= v # ローカルのENV設定は上書きしない
  end
rescue ArgumentError
  # ENVにKMS_KEY_IDが存在しない場合に発生。対応は不要
rescue Aws::S3::Errors::NoSuchKey
  # シークレットファイルが見つからない場合。対応は不要
end

まず、KMSキーのIDを指定して新しいクライアントオブジェクトを生成します。キーのARN(キー作成時にIAMコンソールへ表示されます)は、KMS_KEY_ID環境変数に格納しておきます。コンストラクタにキーIDを渡せば、一時的な復号キーの取得はライブラリが自動的に処理してくれます。オプションでAws::S3::Clientインスタンスを指定することも可能です。これは、KMSとの通信とは別の認証情報でS3と通信したい場合に役立ちます。ただし、前述の手順でIAMロールを設定していれば指定は不要です。Aws::S3::Encryption::Clientが、IAMロールから取得した認証情報を使って新しいAws::S3::Clientインスタンスを自動的に生成してくれるためです。

暗号化対応のS3クライアントの準備が整ったら、#get_objectでS3からデータを取得し、KMSが提供するキーで復号します。データさえ手に入れば、あとは自由に扱えます。筆者らのデータはYAML形式のため、パースしてキー/値のペアをENVへ投入し、アプリケーションコードから利用できるようにしています。

このコードをRailsアプリケーションのinitializerファイルに置けば準備完了です。もちろん、シークレットを事前にS3へ保存しておくことが前提ですが。:) 適切なIAMロールが割り当てられた稼働中インスタンスでIRBコンソールを開けるなら、次のようなコードでシークレットを保存できます。

# /path/to/secrets.yml のデータを暗号化してS3へ保存
Aws::S3::Encryption::Client.new(kms_key_id: ENV['KMS_KEY_ID']).
  put_object(bucket: "yourbucket", key: "secrets.yml", body: File.read("/path/to/secrets.yml"))

すぐにサービス提供を開始

これで、Auto Scalingグループに新しく追加されるすべてのインスタンスに対して、シークレットを暗号化したまま常に利用可能な状態にできます。アプリも開発者も、誰もが幸せになれる構成ですね! :)


  1. 【AWS】Amazon EC2セキュリティグループの作成・編集・削除方法を徹底解説

    セキュリティグループは、Amazon EC2インスタンスのセキュリティにおいて極めて重要な役割を果たします。セキュリティグループは、インスタンスへの着信(インバウンド)および発信(アウトバウンド)接続を制御し、すべてのEC2インスタンスに対して仮想ファイアウォールとして機能します。EC2インスタンスを起動する際には、セキュリティグループの指定が求められます。指定しなかった場合は、デフォルトのセキュリティグループが自動的に適用されます。また、インスタンスを起動した後でも、関連付けられたセキュリティグループは簡単に変更できるため安心です。実は、セキュリティグループはネットワークインターフェースに関

  2. RailsアプリでTwitterログインを実装する方法:OmniAuth-Twitter完全ガイド

    このチュートリアルでは、RailsアプリケーションのユーザーがTwitterアカウントを使ってログインできるようにする方法を解説します。OAuthなどのツールのおかげで、この実装はとても簡単に行えるようになりました。 今回は、OmniAuth用のTwitterストラテジーを含む「OmniAuth-Twitter」を使用します。 それでは始めましょう! はじめに:プロジェクトの準備 まずはRailsアプリケーションを生成します。ターミナルで以下のコマンドを実行してください。 rails new Tuts-Social -T 次にGemfileを開き、bootstrap gemを追加します。