DocBaseをIAM ロールに移行した話 このエントリをはてなブックマークに登録

2015年12月18日

ダニーダニー / ,

はじめに

DocBaseでは新機能追加以外にも、日々いろいろな改善を行ってます。
今回はセキュリティ周りの改善として、IAM ロールに移行した話を書きます。

IAM ロールに移行した理由

IAM ロールを使うことでアプリケーション側で、認証情報を管理をする必要がなくなるのでセキュリティを高めることができます。

Amazon EC2 の IAM ロール
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html

方針

IAM ロールに移行前の状態でDocBaseでは、AWS API を利用するための gem として、

  • aws-sdk-ruby v1
  • carrierwaveでS3に画像をアップロードするのにfog

を使ってました。

aws-sdk-rubyはAWSが提供してるAWSのAPIをRubyから使えるgemです。一方、fogの場合はfog-awsというgemで独自実装しています。

IAM ロールから認証情報を取得する仕組みはどちらのgemでもできますが、同じことやるgemが2つ入ってる状態になってしまっているので、なんとかaws-sdkのgemに統一できないかと調べました。

調べたところcarrierwave-awsがありました。carrierwave-awsはs3にアップロードするのにaws-sdk-rubyのv2を使ってましたので使えそうでした。

アプリ全体でaws-sdk-ruby v1からv2にアップデートして、fogは使わない方針にしました。

AWS SDK Ruby v2の認証情報取得の仕組み

IAM Roleの仕組みを追う – なぜアクセスキーを明記する必要がないのか
http://dev.classmethod.jp/cloud/aws/iam-role-accesskey
にAWS SDK Ruby v1についてはこちらの記事に書かれてますが、v2だとソースコードが全く新しくなっていたので調べました。

認証情報を取得する処理の実行順序

aws-sdk-core/lib/aws-sdk-core/credential_provider_chain.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

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

引数で渡した認証情報がセットされます。

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

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

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

ここで、IAM ロールから認証情報を取得するようになっています。

$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
aws-opsworks-ec2-role

でロール名が取得できます。

$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/aws-opsworks-ec2-role

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/aws-opsworks-ec2-role
{
  "Code" : "Success",
  "LastUpdated" : "2015-11-12T02:50:00Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIEINX6427VDT7WCS5Q",
  "SecretAccessKey" : "3P7xuqy12P94dd7K6MeCDXosxd5BcC9jAgvXl/xI",
  "Token" : "AQoDYXdzEOT//////////xEa8APyk97BOn0wmIEARN8YYhbgI6HweaISgh/PRkt8Ijhyc02wMBnp3o5xXbd/ZC+qBDJRwWGcz24377jRV16m9m4NE+9u1aBT+oyzQR9mkF2HBNo6gprHVNwwpRW8/H/0N92E2Ya4yILjJQhbjAkVsygLe30e/jtYumQ2epM2f/vYtBBcWlAWObqy9KUv80peFzRFfXsqHpjoowoWqeBV4WerzhOcSm1GJhgoybJKY8Ys5wsiS+wodAsp18bft34CtEUtOZjbbwOCSlcl/qIg42vy67B7H/1GZFiU9jhiMd/0eulvcRpxSPNEXrZacIf3LMr9Ju4QcI5lbey72SsbVbYfesX4JjjY4Z0zk5PLhGEIj+ejHGSA9Pao12X4yFyBW+GO4Na4p3zCyc3dDcAIysoejoFLUEA4LNwUcbYQ9kLGK1ayKa4L/c1dZbDsuX3AN+O05I1/E27wX98uRe1k0I9+4uDLi713ztosktNMBxwYgF2eiL8mjVQTfdAzhEN0fU8ufViY4gllmGCbPAu/mdjx9TOkRkp1rbnBFL57vqb8y5g2pTBVqb5K84FqVE3TXMhLqmP2f8zmBflqm6B0P8Vh1WsPxYkQ/vdWYIOuu8Ro09gYCawOrjvFDAylQOygOTZCp8sCtlgrT0eiiDLL8N+yIIn9j7IF",
  "Expiration" : "2015-11-12T09:06:30Z"
}

そのロール名を指定することで認証情報が取得できます。
これらの情報は、そのEC2インスタンスのサーバーからじゃないと取得できないようになってます。

開発環境と本番環境との切り分け方

個人ごとのローカルの開発環境(IAM ロールが使えない)場合

  • static_credentials
  • env_credentials
  • shared_credentials

のどれかで、認識情報を設定します。

本番環境の場合

EC2 インスタンスを IAM Role 付きで起動することで、instance_profile_credentialsが使えるので、アプリ側に認証情報を設定する必要はなくなります。

最後に

IAM Roleを使うことで、アプリケーション側に認識情報を設定する必要がなくなったのと、特定のEC2インスタンスに対して、特定のリソースのみ(例えば特定のS3のバケットのみアクセスできるように制限する)などのことがやりやすくなったのが良い点だと思いました。

宣伝

053c751a-9a40-43e2-ad6e-b75e78367451

DocBaseとは

チームを育てるをコンセプトにした情報共有サービスです。
メモという形で小さく始められる、エンジニア以外のメンバーでも使いやすい仕組み、情報をまとめて整理できる、柔軟な権限設定で様々なプロジェクトで使えるなど、積極的な情報共有と業務の効率化を実現し、チームの成長を促します。

詳しくはこちらから。
https://docbase.io

関連記事

クレイについてもっと知りたい方は…

  1. クレイの3つの強みを見てみる。
  2. WEBシステムのことなら何でもご相談ください。

「いいね!」で応援よろしくお願いします!

このエントリーに対するコメント

コメントはまだありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


トラックバック

we use!!Ruby on RailsAmazon Web Services

このページの先頭へ