はじめに
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
- static_credentials
- env_credentials
- shared_credentials
- 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のバケットのみアクセスできるように制限する)などのことがやりやすくなったのが良い点だと思いました。
宣伝
DocBaseとは
チームを育てるをコンセプトにした情報共有サービスです。
メモという形で小さく始められる、エンジニア以外のメンバーでも使いやすい仕組み、情報をまとめて整理できる、柔軟な権限設定で様々なプロジェクトで使えるなど、積極的な情報共有と業務の効率化を実現し、チームの成長を促します。
詳しくはこちらから。
https://docbase.io
このエントリーに対するコメント
日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)
- トラックバック
「いいね!」で応援よろしくお願いします!