メモ > サーバ > サービス: AWS > S3バケットに他AWSアカウントからのアクセスを許可
S3バケットに他AWSアカウントからのアクセスを許可
以下のAWSアカウントがあるとする。
account1(111111111111)
account2(222222222222)
このとき、account1アカウントが所持しているS3バケットを、account2アカウントから読み書きできるように設定する。
■account1にて設定
※外部AWSアカウント「222222222222」用に、S3を操作するためのロールを作成する。
※ロールに割り当てる許可ポリシーは、検証なら「AmazonS3FullAccess」でもいいが、実案件ではできるだけ権限を絞るといい。
以下の例では、特定バケットに限定して操作を許可している。
ポリシー ExternalExampleDevelopPolicy として以下を作成。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [ … 許可したい操作を指定。
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [ … 許可したいバケットを指定。
"arn:aws:s3:::example-linkage-develop",
"arn:aws:s3:::example-linkage-develop/*"
]
}
]
}
ロール ExternalExampleDevelopRole として以下を作成
信頼されたエンティティタイプ: AWSアカウント
AWSアカウント: 別のAWSアカウント
アカウントID: 222222222222 … 許可したいAWSアカウントを指定。
オプション: 外部IDを要求する … 必須では無いが推奨されている。
外部ID: Abcd1234 … 必須では無いが推奨されている。
許可ポリシー: ExternalExampleDevelopPolicy … 上で作成したポリシーを指定。
参考までに、作成後に ExternalExampleDevelopRole の「信頼関係」を確認すると、以下のJSONを確認できる。
「Principal」の部分が、別のAWSアカウントを信頼するための指定。
外部IDを指定しなかった場合、Conditionの部分は「"Condition": {}」となる。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "Abcd1234"
}
}
}
]
}
■account2にて設定
ポリシー ExampleDevelopPolicy として以下を作成。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::111111111111:role/ExternalExampleDevelopRole" … 上で作成したロールの情報(アカウントIDとロール名)を指定する
}
]
}
ロール「ExampleDevelopRole」を作成し、上で作成した「ExampleDevelopPolicy」を割り当てる。
さらにEC2「Example-Develop-Web1(i-0e6194f055556b51b)」に対して、上で作成した「ExampleDevelopRole」を割り当てる。
上で設定したEC2にSSHで接続し、以下のとおりコマンドを実行する。(外部IDを指定しなかった場合、コマンドから「--external-id "Abcd1234"」部分は省く。)
AssumeRole に必要な認証情報(アクセスキー、シークレットキー、セッショントークン)が返される。
$ aws sts assume-role --role-arn "arn:aws:iam::111111111111:role/ExternalExampleDevelopRole" --role-session-name "TestSession" --external-id "Abcd1234" … 上で作成したロールの情報(アカウントID、ロール名、外部ID)を指定する
{
"AssumedRoleUser": {
"AssumedRoleId": "AROAXXXXXXXXXXXXX5CGE:TestSession",
"Arn": "arn:aws:sts::111111111111:assumed-role/ExternalExampleDevelopRole/TestSession"
},
"Credentials": {
"SecretAccessKey": "G/u8iH7jXXXXXXXXXXXXXXXXXXXXXXXXzuYZ+BWH",
"SessionToken": "FwoGZXIvYXdzEID//////////wEaDCa6rWxdIvTnBtc1MCKvAdsYLpWCFGwVeHNa3wY81UI8m4rhfWwqXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZjnF1Q0o/aCtsQYyLeltX4vBYejF6L6CbWk+nMrXkJ9Nv7DFb7W6vCOc59g9/Qj7tqRwWn2Tetopyg==",
"Expiration": "2024-04-26T07:58:05Z",
"AccessKeyId": "ASIAXXXXXXXXXXXX4C76"
}
}
上記内容をもとに、~/.aws/credentials に認証情報を設定する。
$ mkdir ~/.aws
$ vi ~/.aws/credentials
以下のとおり設定する。
[cross-account]
aws_access_key_id = ASIAXXXXXXXXXXXX4C76 … 取得したAccessKeyId。
aws_secret_access_key = G/u8iH7jXXXXXXXXXXXXXXXXXXXXXXXXzuYZ+BWH … 取得したSecretAccessKey。
aws_session_token = FwoGZXIvYXdzEID//////////wEaDCa6rWxdIvTnBtc1MCKvAdsYLpWCFGwVeHNa3wY81UI8m4rhfWwqXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZjnF1Q0o/aCtsQYyLeltX4vBYejF6L6CbWk+nMrXkJ9Nv7DFb7W6vCOc59g9/Qj7tqRwWn2Tetopyg==
… 取得したSessionToken。
以下のとおり接続する。
$ export AWS_PROFILE=cross-account
$ aws sts get-caller-identity --query 'Arn' | awk '{ print substr($1, index($1, "/")) }' | awk '{ sub("/", ""); sub("\"", ""); print }'
ExternalExampleDevelopRole/TestSession
以下でS3の内容を参照できる。
$ aws s3 ls example-linkage-develop
$ aws s3 ls example-linkage-develop/images/
以降は以下で接続できる。
ただし、取得したセッショントークンの有効期限は1時間となっている。(延長可能だが最大で12時間まで。)
$ export AWS_PROFILE=cross-account
$ aws s3 ls example-linkage-develop/images/
■PHPからの接続
以下のようにアクセスキー、シークレットキー、セッショントークンを指定することで、PHPからも接続できる。
ただし上記のとおり、この情報には有効期限がある。(一定時間が経過すると「400 Bad Request」「ExpiredToken」「The provided token has expired.」のエラーになる。)
<?php
require_once 'vendor/autoload.php';
use Aws\S3\S3Client;
use Aws\S3\Exception\S3Exception;
try {
// アクセスキーとシークレットアクセスキーを指定して接続
$client = new S3Client([
'credentials' => [
'key' => 'ASIAXXXXXXXXXXXX4C76',
'secret' => 'G/u8iH7jXXXXXXXXXXXXXXXXXXXXXXXXzuYZ+BWH',
'token' => 'FwoGZXIvYXdzEID//////////wEaDCa6rWxdIvTnBtc1MCKvAdsYLpWCFGwVeHNa3wY81UI8m4rhfWwqXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZjnF1Q0o/aCtsQYyLeltX4vBYejF6L6CbWk+nMrXkJ9Nv7DFb7W6vCOc59g9/Qj7tqRwWn2Tetopyg==',
],
'region' => 'ap-northeast-1',
'version' => 'latest',
]);
// バケットとディレクトリを指定して取得
$result = $client->listObjects([
'Bucket' => 'example-linkage-develop',
'Prefix' => 'images/',
'Delimiter' => '/',
]);
// ファイルを表示
foreach ($result['Contents'] as $content) {
echo $content['Key'] . '(' . $content['LastModified'] . ')' . '<br>';
}
} catch (S3Exception $e) {
exit('S3Exception: ' . $e->getMessage());
} catch (Exception $e) {
exit('Exception: ' . $e->getMessage());
}
■PHPからの接続(改修版)
以下のようにすると、STSを使って都度認証情報を取得する。これにより、有効期限の問題を回避できる。
ただし「EC2インスタンスでプログラムを実行していて、そのインスタンスに適切なIAMロールが割り当てられている」必要がある。
<?php
require 'vendor/autoload.php';
use Aws\Sts\StsClient;
use Aws\S3\S3Client;
// STS Clientの初期化
$stsClient = new StsClient([
'version' => 'latest',
'region' => 'ap-northeast-1',
]);
try {
// AssumeRole APIをコール
$result = $stsClient->assumeRole([
'RoleArn' => 'arn:aws:iam::111111111111:role/ExternalExampleDevelopRole',
'RoleSessionName' => 'TestSession',
'DurationSeconds' => 3600, // トークンの有効期限(秒)
'ExternalId' => 'Abcd1234', // 外部ID
]);
// 取得した認証情報
$credentials = $result->get('Credentials');
// 認証情報を使って新しいクライアントを作成
$client = new S3Client([
'version' => 'latest',
'region' => 'ap-northeast-1',
'credentials' => [
'key' => $credentials['AccessKeyId'],
'secret' => $credentials['SecretAccessKey'],
'token' => $credentials['SessionToken'],
]
]);
// バケットとディレクトリを指定して取得
$result = $client->listObjects([
'Bucket' => 'example-linkage-develop',
'Prefix' => 'images/',
'Delimiter' => '/',
]);
// ファイルを表示
foreach ($result['Contents'] as $content) {
echo $content['Key'] . '(' . $content['LastModified'] . ')' . '<br>';
}
} catch (Exception $e) {
exit('Exception: ' . $e->getMessage());
}
■バケットポリシーの設定
※ロールIDを変更しても変化なかったので、本当に設定できているかは改めて検証したい。
ただし最初に設定したポリシー ExternalExampleDevelopPolicy とロール ExternalExampleDevelopRole によってアクセス元を限定しているので、ここでの設定はあまり意味が無いような気はする。
最後に、AWSアカウントaccount1にて、S3バケットexample-linkage-developに対して、バケットポリシーを設定する。
$ export AWS_PROFILE=cross-account
$ aws sts get-caller-identity
{
"Account": "111111111111",
"UserId": "AROAXXXXXXXXXXXXX5CGE:TestSession",
"Arn": "arn:aws:sts::111111111111:assumed-role/ExternalExampleDevelopRole/TestSession"
}
「UserId」の値のうち、「:」より前がロールID、「:」より後ろがセッション名となっている。
この場合、以下のとおりバケットポリシーを設定する。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::example-linkage-develop/*",
"Condition": {
"StringNotLike": {
"aws:userId": [
"AROAXXXXXXXXXXXXX5CGE:*" … ロールIDを指定
]
}
}
}
]
}
これで完了。
特定の IAM ロールのみアクセスできる S3 バケットを実装する際に検討したあれこれ | DevelopersIO
https://dev.classmethod.jp/articles/s3-bucket-acces-to-a-specific-role/#toc-7
■メモ
受け入れ側でポリシーを変更すると、アクセスする側で再接続が必要みたい。
ただし「PHPからの接続(改修版)」の方法なら問題無いはず。
以下は参考ページ。
AWSのIAM Roleについて整理してみた #AWS - Qiita
https://qiita.com/NMRchan/items/7d5adc44bed2b53f0347
IAMユーザにIAMロールをアタッチする話 #AWS - Qiita
https://qiita.com/yukitsuboi/items/bf033c9d321dccdeedd4
IAMロール徹底理解 〜 AssumeRoleの正体 | DevelopersIO
https://dev.classmethod.jp/articles/iam-role-and-assumerole/
AssumeRole について DiveDeep する - サーバーワークスエンジニアブログ
https://blog.serverworks.co.jp/assume-role-divedeep
一時的なセキュリティ認証情報のリクエスト - AWS Identity and Access Management
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_temp_request.html#api_assumero...