発端
オンプレミス環境で EC2 Instance Profile を使いたいというユースケースを聞きました。
SDKやCLIの中には、EC2 Instance Profileを利用するために、
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/role名
へアクセスを行い、テンポラリのクレデンシャルを取得する仕組みが入っています。
このURLへのアクセスが出来れば、オンプレミス環境でもEC2 Instance Profileと同じような事が出来るんじゃないの?というのが発端で、実際に作ってみました。
コード
#!/usr/bin/env ruby
require 'sinatra'
require 'aws-sdk-core'
set :bind, '0.0.0.0'
role_arn=ARGV.last || ""
abort "usage: ruby ec2role.rb [ -p PORT ] arn:aws:iam:111111111111:role/ROLENAME" unless role_arn.match /^arn:aws:iam:/
sts = Aws::STS::Client.new(region: 'us-east-1')
json="n/a"
Thread.new do
loop do
begin
resp = sts.assume_role(role_arn:role_arn,role_session_name:"ec2role.rb")
rescue => error
abort "ERROR: unable to fetch credential / "+error.to_s
end
json= <<EOL
{
"Code" : "Success",
"AccessKeyId" : "#{resp.credentials.access_key_id}",
"SecretAccessKey" : "#{resp.credentials.secret_access_key}",
"Token" : "#{resp.credentials.session_token}",
"Expiration" : "#{resp.credentials.expiration.iso8601}"
}
EOL
puts json
sleep resp.credentials.expiration-Time.now-60*5 # update credential 5 mins before it expires
end
end
get '/latest/meta-data/iam/security-credentials/' do
'temp'
end
get '/latest/meta-data/iam/security-credentials/temp' do
json
end
使い方
まず、AssumeRoleを行うためのIAMユーザと、AssumeRoleされる側のRoleを作成します。
RoleはCross-Account access用のRoleを作成し、AssumeRoleを行うユーザを信頼します。
(同じアカウント同士であっても信頼する必要があります)
RubyのコードはIAMユーザの credential を使用して起動する必要があります。
クライアントにはLinuxを想定します(iptablesを使用するため)。
送信されるパケットのうち、169.254.169.254 宛のパケットを転送するために、
# /sbin/sysctl -w net.ipv4.ip_forward=1
# /sbin/iptables -A FORWARD -m tcp -p tcp --dst 169.254.169.254 --dport 80 -j ACCEPT
# /sbin/iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
# /sbin/iptables -t nat -A OUTPUT -m tcp -p tcp --dst 169.254.169.254 --dport 80 -j DNAT --to-destination 127.0.0.1:4567
みたいな感じで、パケットの宛先を書き換えてしまいます。
Rubyのコードを実行しているログが下記のようになります。
~/work/ec2role$ ruby ec2role.rb arn:aws:iam::407613804811:role/power
== Sinatra/1.4.5 has taken the stage on 4567 for development with backup from Thin
Thin web server (v1.6.3 codename Protein Powder)
Maximum connections set to 1024
Listening on 0.0.0.0:4567, CTRL+C to stop
{
"Code" : "Success",
"AccessKeyId" : "ASIAxxxxxxxxxxxxxxxxxxxxxxx",
"SecretAccessKey" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Token" : "hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge",
"Expiration" : "2014-12-02T10:26:26Z"
}
127.0.0.1 - - [02/Dec/2014:18:26:47 +0900] "GET /latest/meta-data/iam/security-credentials/ HTTP/1.1" 200 4 0.0052
127.0.0.1 - - [02/Dec/2014:18:26:47 +0900] "GET /latest/meta-data/iam/security-credentials/temp HTTP/1.1" 200 554 0.0005
起動時にテンポラリのクレデンシャルを取りに行き、クライアントからアクセスが来たら渡しています。
Expireする5分前に取り直しをします。
iptablesがない場合は?
SDKやCLIには、 http://169.254.169.254/ のアドレスが埋め込まれているので、それを書き換えてしまえば使えます。
AWS CLIでは、botocore/utils.py
METADATA_SECURITY_CREDENTIALS_URL = (
# 'http://169.254.169.254/latest/meta-data/iam/security-credentials/'
'http://localhost:4567/latest/meta-data/iam/security-credentials/'
)
みたいな感じです。
追記
某meet-upで、instance-id とかには対応できないの?というご意見をいただきました。 出来ます!!
sinatraでは ./public が static content として扱われるので、
~/work/ec2role$ mkdir -p public/latest/meta-data
~/work/ec2role$ echo -n i-12345678 > public/latest/meta-data/instance-id
~/work/ec2role$ echo hoge > public/latest/user-data
みたいにファイルを作っておけば、
$ curl http://169.254.169.254/latest/user-data
hoge
$ curl http://169.254.169.254/latest/meta-data/instance-id
i-12345678
127.0.0.1 - - [09/Dec/2014:15:44:00 +0900] "GET /latest/user-data HTTP/1.1" 200 20 0.0018
127.0.0.1 - - [09/Dec/2014:15:44:30 +0900] "GET /latest/meta-data/instance-id HTTP/1.1" 200 10 0.0009
みたいに返す事が可能です。
Have fun!