AWS SDK for Ruby Version 2でIAM Roleを使った時にアクセスキーを設定しないで使える仕組み

## はじめに

http://dev.classmethod.jp/cloud/aws/iam-role-accesskey/

Version 1の場合の説明はこの記事に書かれてましたが、Version 2はソースコードの中身がVersion 1と全く違かったので調べてみました。

## アクセスキーを取得する処理の実行順序

aws-sdk-core/lib/aws-sdk-core/credential_provider_chain.rb

```rb
def resolve
  providers.each do |method_name, options|
    provider = send(method_name, options.merge(config: @config))
    return provider if provider && provider.set?
  end
  nil
end

def providers
  [
    [:static_credentials, {}],
    [:env_credentials, {}],
    [:shared_credentials, {}],
    [:instance_profile_credentials, {
       retries: 0,
       http_open_timeout: 1,
       http_read_timeout: 1,
    }],
  ]
end
```

1. static_credentials
2. env_credentials
3. shared_credentials
4. instance_profile_credentials

の順に実行されるが、アクセスキーが取得できたらそこで処理は終了。

## static_credentials

aws-sdk-core/lib/aws-sdk-core/credential_provider_chain.rb

```rb
def static_credentials(options)
  config = options[:config]
  Credentials.new(
    config.access_key_id,
    config.secret_access_key,
    config.session_token)
end
```

引数で渡したアクセスキーがアクセスキーとしてセットされる。

### 例

```rb
Aws::EC2::Client.new(
  access_key_id: 'access key',
  secret_access_key: 'secret access key'
)
```

## env_credentials

aws-sdk-core/lib/aws-sdk-core/credential_provider_chain.rb

```rb
def env_credentials(options)
  key =    %w(AWS_ACCESS_KEY_ID     AMAZON_ACCESS_KEY_ID     AWS_ACCESS_KEY)
  secret = %w(AWS_SECRET_ACCESS_KEY AMAZON_SECRET_ACCESS_KEY AWS_SECRET_KEY)
  token =  %w(AWS_SESSION_TOKEN     AMAZON_SESSION_TOKEN)
  Credentials.new(envar(key), envar(secret), envar(token))
end
```

環境変数で設定したものがアクセスキーとしてセットされる。

## shared_credentials

aws-sdk-core/lib/aws-sdk-core/shared_credentials.rb

.aws/credentialsに書いた設定からアクセスキーとしてセットされる。

## instance_profile_credentials

aws-sdk-core/lib/aws-sdk-core/instance_profile_credentials.rb

```rb
def refresh
  # Retry loading credentials up to 3 times is the instance metadata
  # service is responding but is returning invalid JSON documents
  # in response to the GET profile credentials call.
  retry_errors([JSON::ParserError, StandardError], max_retries: 3) do
    c = JSON.parse(get_credentials.to_s)
    @credentials = Credentials.new(
      c['AccessKeyId'],
      c['SecretAccessKey'],
      c['Token']
    )
    @expiration = c['Expiration'] ? Time.parse(c['Expiration']) : nil
  end
end

def get_credentials
  # Retry loading credentials a configurable number of times if
  # the instance metadata service is not responding.
  begin
    retry_errors(NETWORK_ERRORS, max_retries: @retries) do
      open_connection do |conn|
        path = '/latest/meta-data/iam/security-credentials/'
        profile_name = http_get(conn, path).lines.first.strip
        http_get(conn, path + profile_name)
      end
    end
  rescue
    '{}'
  end
end
```

http://169.254.169.254/latest/meta-data/iam/security-credentials/
を取得するとロール名が返って来る

そのロール名を元に

http://169.254.169.254/latest/meta-data/iam/security-credentials/ロール名

で取得することでアクセスキーの情報入ったJSONが返って来る。
なのでIAM Roleからアクセスキーの情報を取得するようになっている。

http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html

## 環境による切り分け方

### ローカルの開発環境(IAM Roleが使えない)場合

環境変数にアクセスキーを設定して動かす。

### 本番環境の場合

EC2でIAM Role付きで動かしている場合、環境変数にアクセスキーを設定する必要がない。