前書き

AWS SDKを使ったアプリケーションを作る時credentialsの扱いがいつも面倒なので、ベストプラクティス的なものを考えていきたい。

例として、

$ ruby myec2.rb list

と叩くと



とEC2のインスタンス一覧を標準出力するだけのアプリケーションを作ってみる。

credentialsをアプリケーション内で取得する方法

大雑把に分けて4つある。

  1. IAM RoleのInstance Profileを使う
  2. 環境変数で渡す
  3. アプリケーションのオプションや設定ファイルで渡す
  4. ~/.aws/credentials~/.aws/config に書く

1. IAM RoleのEC2 Instance Profileを使う

EC2上で実行する場合に最も便利でセキュアな方法。aws-sdkが自動でメタデータから取得してくれるため、アプリケーション内に明示的に何かを書く必要はない。

2. 環境変数で渡す

aws-sdkでは環境変数 AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY がセットされている場合、これらを自動で読む機能がある。

$ export AWS_ACCESS_KEY_ID=********
$ export AWS_SECRET_ACCESS_KEY=********
$ ruby myec2.rb list

こちらもaws-sdkが処理してくれるので、アプリケーション内に明示的に何かを書く必要はない。

3. アプリケーションのオプションや設定ファイルで渡す

アプリケーションの起動時に以下のようにオプションで渡したり、どこかに設定ファイルを置いてそこに決め打っておく方法。

$ ruby myec2.rb list --access-key-id ******** --secret-access-key ********

流石にaws-sdk側では処理してくれないので、自分でこれらから取得した値を使うように実装する必要がある。

4. ~/.aws/credentials や ~/.aws/config に書く

  1. A New and Standardized Way to Manage Credentials in the AWS SDKs – AWS Security Blog
  2. AWS SDK for Rubyで新標準となったCredentials管理方法を使ってみる – 雑記帳(2014-07-11)

あたりを参照。アプリケーションでは以下のように渡せるようにしたい。

$ ruby myec2.rb list --profile myprofile

上記記事を見ると分かるように、~/.aws/credentialsはaws-sdk側のサポートがある(default profileは暗黙的にロードされる)。~/.aws/config はこれを読み込む専用のgemがある。

実装例

上記4つの方法すべてを使えるようにして、できたのがこちら。

require "thor"
require "aws-sdk"
require "aws_config"

module MyEc2
  class CLI < Thor
    class_option :access_key_id,     default: nil,              aliases: [:k]
    class_option :secret_access_key, default: nil,              aliases: [:s]
    class_option :region,            default: "ap-northeast-1", aliases: [:r]
    class_option :profile

    desc "list", "List all instances in region."
    def list
      ec2.instances.each {|instance| p instance}
    end

    private

    def aws_config
      hash = {}

      if options[:profile]
        aws_config = AWSConfig.profiles[options[:profile]]

        if aws_config
          hash.update aws_config.config_hash
        else
          provider = AWS::Core::CredentialProviders::SharedCredentialFileProvider.new profile_name: options[:profile]
          hash.update credential_provider: provider
        end
      else
        hash.update access_key_id: options[:access_key_id] if options[:access_key_id]
        hash.update secret_access_key: options[:secret_access_key] if options[:secret_access_key]
      end

      hash.update region: options[:region] if options[:region]
      hash
    end

    def ec2
      @ec2 ||= AWS::EC2.new aws_config
    end
  end
end

MyEc2::CLI.start ARGV

これだけ方法が多いとかなり複雑になるので、privateメソッド aws_config を定義し、その中で空ハッシュに条件に応じて情報を足していく一連の処理を行い、それを元に AWS::EC2 オブジェクトを生成している。

流れとしては、まず--profileが指定されている場合はそのprofileを取得しようとする。

      if options[:profile]
        aws_config = AWSConfig.profiles[options[:profile]]

        if aws_config
          hash.update aws_config.config_hash
        else
          provider = AWS::Core::CredentialProviders::SharedCredentialFileProvider.new profile_name: options[:profile]
          hash.update credential_provider: provider
        end

~/.aws/credentials~/.aws/config 両方に対応するように書いてある。

次に、profileが指定されていなかった場合かつ --access-key-id--secret-access-key が指定されている場合はそれらの情報を使うようにしている。

      else
        hash.update access_key_id: options[:access_key_id] if options[:access_key_id]
        hash.update secret_access_key: options[:secret_access_key] if options[:secret_access_key]

最後にリージョンは --profile と同時に指定することも多いという予想から、この位置で処理している。

      hash.update region: options[:region] if options[:region]

このように書くことで --profile--access-key-id--secret-access-key のいずれも指定されなかった場合はIAM Instance Profileや環境変数から暗黙的にcredentialsが取得されるようになる。

まとめ

aws−sdk は暗黙的にcredentialsを受け取る方法が沢山あるので、 aws−sdk を使ったアプリをcredentialsを明示的に受け取ることが前提の実装にすることはやめよう。

ちなみに

実装例にはRubyでCLIアプリを作るときに超絶便利なThorを使っている。

元記事はこちらです。
y13i / ruby_aws_sdk_credentials.md