発端
ローカルPCからAWSのAPIを呼び出す場合、アクセスキーをローカルに保存しておく必要がある。
ディスクは暗号化してるしパスワードでロックもされてるけど、大きい権限を持ったユーザのアクセスキーをベタ書きにしておくのは気がひける。
かといって何も出来ないユーザを作っても意味が無い。
ということで、権限の少ないユーザから大きな権限を持つロールを一時的に拝借する形にした。
aws-cliの自動AssumeRoleとの使い分け
aws-cli には 自動的にAssumeRoleを行ってくれる機能 がある。cliだけ使うならこちらを使ったほうがいいと思う。
mfa_serial
を指定すればワンタイムトークンも使えるし、クレデンシャルの有効期限が切れたら再入力を促してくれる。
ただ、AWS SDK for Ruby では自動的にAssumeRoleしてくれないようだった。
SDKでもクレデンシャルを共有したい場合は、ここに書いてある方法もありだと思う。
# In ~/.aws/credentials:
[development]
aws_access_key_id = foo
aws_access_key_id = bar
# In ~/.aws/config
[profile crossaccount]
role_arn = arn:aws:iam:...
source_profile = development
手順
-
AssumeRole しか出来ないユーザを作成
このユーザのアクセスキーをローカルPCに保存する。 -
AssumeRole 対象のロールを作成
実際に必要な権限をこのロールに含める。 -
ローカルPCから AssumeRole を実行
1.で作成したユーザのアクセスキーを使って 2.のロールをAssumeRole
する。 -
AWS の API を呼び出す
3.のAssumeRole
で取得した一時的なクレデンシャルを使って AWS の API を呼び出す。
IAMユーザの作成
このユーザのアクセスキーをローカルPCに保存し、実作業に必要な権限を持ったロールを AssumeRole する。
AssumeRole 時は External ID と MFA のトークンを入力させることでセキュリティ的にイイ感じにする。
(MFA の設定は各ロールで行う。「read onlyなロールだから MFA は不要」「power userロールは MFA 必須」など適宜。)
Management Consle にはログインしないのでパスワードは設定しない。
作成
ここらへんを参考にユーザを作成する。
AWSで最初にすべきこと ~運用ユーザの作成~
アクセスポリシー
- 指定しない
- ロール側のトラストポリシーで
Principal
にこのユーザを指定する(後述) - 今回は同一アカウント内で
AssumeRole
を行う(= root ユーザが同一人物となる)ので、使う側/使われる側のどちらかのポリシーで許可がおりていればよい模様。逆に、クロスアカウントでAssumeRole
を行うには、使う側/使われる側それぞれで自アカウントの root ユーザから許可を得る必要がある。
MFA の設定
初めての人はここらへんを参考に MFA の設定をする。
AWSアカウントの2段階認証を設定する
AWSアカウント作ったらこれだけはやっとけ!IAMユーザーとAuthyを使ったMFAで2段階認証
設定後 MFA デバイスの ARN をメモっとく。
arn:aws:iam::XXXXXXXXXXXX:mfa/my-user
IAMロールの作成
AssumeRole
対象のロール power-user
を作成する。
ここでは IAM と STS 以外の Action を許可する。
作成
Step 1 : Set Role Name
Step 1 はロール名に power-user
に入れるだけ。
Step 2 : Select Role Type
クロスアカウントではないけど他に選択肢がなさそうなのでこれ↓を選択する。
Step 3 : Establish Trust
-
Account ID
3rd partyでなく自分のAWSアカウントIDを入力する。 -
External ID
適当な文字列を入力する。
実際にAssumeRole
時にパラメータの1つとして使う。
これは口外してはいけない類のものだと思う。 -
Require MFA
今回はAssumeRole
時に MFA を使うのでチェックを入れる。
MFA 不要であればチェックは外す。
Step 4 : Set Permissions
IAM 以外の処理は全部許可するので Power User Access
を選択。
Select Pollicy Template > Power User Access
トラストポリシー
ロール作成後のトラストポリシーは下記のようになっているはず。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::XXXXXXXXXXXX:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "hogehoge"
},
"Bool": {
"aws:MultiFactorAuthPresent": true
}
}
}
]
}
デフォルトではアカウント単位でこのロールの利用許可を出している。
なので、最初に作成したユーザ my-user
だけが AssumeRole
できるよう設定する。
ユーザを追加できますよ感を出すために配列で指定するようにしてみた。
"Principal": {
"AWS": [
"arn:aws:iam::XXXXXXXXXXXX:user/my-user"
]
}
グループ単位や
user/*
での指定は出来なかった。
間接的に指定できるようにすると「混乱した使節問題」みたいなことが起きるからだろうか。よく分かってない。
と思ったらただのバグだった。
Can a Group ARN be specified as a Principal for a Bucket Policy?
ちなみに設定変更はロール詳細画面の Edit Trust Relationship から行う。
AssumeRoleの実行
AWS CLI
作成したIAMユーザのアクセスキーを保存し AssumeRole で一時的なクレデンシャルを取得してみる。
$ aws sts assume-role \
> --role-arn arn:aws:iam::XXXXXXXXXXXX:role/power-user \
> --role-session-name in-office \
> --external-id hogehoge \
> --serial-number arn:aws:iam::XXXXXXXXXXXX:mfa/my-user \
> --token-code XXXXXX
{
"AssumedRoleUser": {
"AssumedRoleId": "XXXXXXXXXXXXXXXXXXXXX:in-office",
"Arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/power-user/in-office"
},
"Credentials": {
"SecretAccessKey": "XXXXXXXXXX...",
"SessionToken": "XXXXXXXXXX...",
"Expiration": "2015-01-22T03:16:56Z",
"AccessKeyId": "XXXXXXXXXXXXXXXXXXXX"
}
}
普段は AssumeRole して設定ファイルを更新するまでの処理をまとめたシェルを使っている。
#!/bin/sh
set -e
role_name=$1
external_id=$2
token_code=$3
profile=$role_name
cmd="aws sts assume-role"
cmd="$cmd --role-arn arn:aws:iam::XXXXXXXXXXXX:role/$role_name"
cmd="$cmd --role-session-name in-office"
cmd="$cmd --external-id $external_id"
if [ -n "$token_code" ]; then
cmd="$cmd --serial-number arn:aws:iam::XXXXXXXXXXXX:mfa/my-user"
cmd="$cmd --token-code $token_code"
fi
creds=$($cmd)
access_key_id=$(echo $creds | jq --raw-output .Credentials.AccessKeyId)
secret_access_key=$(echo $creds | jq --raw-output .Credentials.SecretAccessKey)
session_token=$(echo $creds | jq --raw-output .Credentials.SessionToken)
aws configure set region ap-northeast-1 --profile $profile
aws configure set aws_access_key_id $access_key_id --profile $profile
aws configure set aws_secret_access_key $secret_access_key --profile $profile
aws configure set aws_session_token $session_token --profile $profile
aws configure list --profile $profile
API を呼び出すまでの一連の流れはこんな感じ。
$ aws-assume-role power-user hogehoge XXXXXX
Name Value Type Location
---- ----- ---- --------
profile power-user manual --profile
access_key ****************XXXX shared-credentials-file
secret_key ****************XXXX shared-credentials-file
region ap-northeast-1 config-file ~/.aws/config
$ aws ec2 describe-regions --profile power-user
$ aws-assume-role read-only hogehoge
Name Value Type Location
---- ----- ---- --------
profile read-only manual --profile
access_key ****************XXXX shared-credentials-file
secret_key ****************XXXX shared-credentials-file
region ap-northeast-1 config-file ~/.aws/config
$ aws ec2 describe-regions --profile read-only
AWS SDK for Ruby
同様にSDKの場合。
APIリファレンスには serial_number
と token_code
の記述がないけど渡せば動く。
普段は pry の history に external_id を残さないように気をつけてる。
$ aws.rb --region ap-northeast-1
Aws> assumed_role = Aws::AssumeRoleCredentials.new(
Aws| role_arn: 'arn:aws:iam::XXXXXXXXXXXX:role/power-user',
Aws| role_session_name: 'in-office',
Aws| external_id: 'hogehoge',
Aws| serial_number: 'arn:aws:iam::XXXXXXXXXXXX:mfa/my-user',
Aws| token_code: 'XXXXXX'
Aws| )
Aws> assumed_role.credentials.access_key_id
"XXXXXXXXXXXXXXXXXXXX"
Aws> assumed_role.credentials.secret_access_key
"XXXXXXXXXX..."
Aws> assumed_role.credentials.session_token
"XXXXXXXXXX..."
普段は AssumeRole して設定ファイルを更新するまでの処理をまとめたコマンドを使っている。
begin
require 'aws-sdk-core'
rescue LoadError
else
# aws.rb may already have been initialized
Aws.config[:region] ||= 'ap-northeast-1'
Pry::Commands.import(Pry::CommandSet.new {
block_command 'aws-assume-role' do |role_name, external_id, token_code|
system "~/bin/aws-assume-role #{role_name} #{external_id} #{token_code}"
end
})
end
API を呼び出すまでの一連の流れはこんな感じ。
$ aws.rb --region ap-northeast-1
Aws> aws-assume-role power-user hogehoge XXXXXX
Name Value Type Location
---- ----- ---- --------
profile power-user manual --profile
access_key ****************XXXX shared-credentials-file
secret_key ****************XXXX shared-credentials-file
region ap-northeast-1 config-file ~/.aws/config
Aws> ec2 = Aws::EC2::Client.new profile: 'power-user'
#<Aws::EC2::Client>
Aws> ec2.describe_regions.regions[0]
[Aws::EC2::Client 200 0.119853 0 retries] describe_regions()
{
:region_name => "eu-central-1",
:endpoint => "ec2.eu-central-1.amazonaws.com"
}
Aws> aws-assume-role read-only hogehoge
Name Value Type Location
---- ----- ---- --------
profile read-only manual --profile
access_key ****************XXXX shared-credentials-file
secret_key ****************XXXX shared-credentials-file
region ap-northeast-1 config-file ~/.aws/config
Aws> ec2 = Aws::EC2::Client.new profile: 'read-only'
#<Aws::EC2::Client>
まとめ
MFA はめんどう。