AWS
cli
IAM
STS
aws-sdk

MacからAWSにアクセスする時はAssumeRoleすることにした

More than 3 years have passed since last update.


発端

ローカル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



手順


  1. AssumeRole しか出来ないユーザを作成

    このユーザのアクセスキーをローカルPCに保存する。


  2. AssumeRole 対象のロールを作成

    実際に必要な権限をこのロールに含める。


  3. ローカルPCから AssumeRole を実行

    1.で作成したユーザのアクセスキーを使って 2.のロールを AssumeRole する。


  4. 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

クロスアカウントではないけど他に選択肢がなさそうなのでこれ↓を選択する。

select-role-type.png


Step 3 : Establish Trust


  • Account ID

    3rd partyでなく自分のAWSアカウントIDを入力する。


  • External ID

    適当な文字列を入力する。

    実際に AssumeRole 時にパラメータの1つとして使う。

    これは口外してはいけない類のものだと思う。


  • Require MFA

    今回は AssumeRole 時に MFA を使うのでチェックを入れる。

    MFA 不要であればチェックは外す。


establish-trust.png


Step 4 : Set Permissions

IAM 以外の処理は全部許可するので Power User Access を選択。

Select Pollicy Template > Power User Access


トラストポリシー

ロール作成後のトラストポリシーは下記のようになっているはず。


trust-policy

{

"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 から行う。

edit-trust-policy.png


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 して設定ファイルを更新するまでの処理をまとめたシェルを使っている。


aws-assume-role

#!/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_numbertoken_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 して設定ファイルを更新するまでの処理をまとめたコマンドを使っている。


.pryrc

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 はめんどう。