Skip to content

Instantly share code, notes, and snippets.

@y13i
Last active July 4, 2022 05:32
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save y13i/bf97e86e9c05f10262cf to your computer and use it in GitHub Desktop.
Save y13i/bf97e86e9c05f10262cf to your computer and use it in GitHub Desktop.
Rubyとaws-sdkとcredentials

前書き

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

例として、

$ ruby myec2.rb list

と叩くと

<AWS::EC2::Instance id:i-12345678>
<AWS::EC2::Instance id:i-12345679>

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

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

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

  1. IAM RoleのInstance Profileを使う

  2. 環境変数で渡す

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

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

  5. IAM RoleのEC2 Instance Profileを使う


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

  1. 環境変数で渡す

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

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

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

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

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

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

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

  1. ~/.aws/credentials~/.aws/config に書く

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

$ 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を使っている。

@aibou
Copy link

aibou commented Aug 30, 2018

突然すみません、たまたま検索に引っかかって見つけたのですが、Aws::CredentialProviderChain というのが便利なのでよかったら調べてみてください。

$ ruby -raws-sdk -e 'p Aws::CredentialProviderChain.new.resolve'
#<Aws::SharedCredentials profile_name="default" path="/Users/aibou/.aws/credentials">

$ AWS_PROFILE=other ruby -raws-sdk -e 'p Aws::CredentialProviderChain.new.resolve'
#<Aws::SharedCredentials profile_name="other" path="/Users/aibou/.aws/credentials">

$ AWS_ACCESS_KEY_ID=AKIAHOGE AWS_SECRET_ACCESS_KEY=hogefuga ruby -raws-sdk -e 'p Aws::CredentialProviderChain.new.resolve'
#<Aws::Credentials access_key_id="AKIAHOGE">

# EC2上で実行
$ ruby -raws-sdk -e 'p Aws::CredentialProviderChain.new.resolve'
#<Aws::InstanceProfileCredentials:0x00000000abc123 @retries=0, @ip_address="169.254.169.254", @port=80, @http_open_timeout=1, @http_read_timeout=1, @http_debug_output=nil, @backoff=#<Proc:0x00000000abc123@[path to ruby]/lib/ruby/gems/2.1.0/gems/aws-sdk-core-2.10.45/lib/aws-sdk-core/instance_profile_credentials.rb:64 (lambda)>, @mutex=#<Mutex:0x00000000abc123>, @credentials=#<Aws::Credentials access_key_id="ASIAOPQRSTU">, @expiration=2018-01-01 00:00:00 UTC>

@Amakata
Copy link

Amakata commented Feb 11, 2019

上記のAws::CredentialProviderChainは確かに便利ですね。しかしPrivate APIなので、そのまま安易にはつかえなさそうですね。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment