AWS IAMロールの一時的な認証情報の取得と利用の仕組み
調べたこと
AWS IAMにはロールという仕組みがあり,ロールを使うことでユーザやサービスに,一時的にアクセス権限を付与することができます. この際,ロールを利用するユーザやサービスは一時的な認証情報を取得する,とドキュメントに書いてあるのですが, どうやって取得し,どうやってAPIリクエストに利用するのか,という部分がわからなかったので調べました.
cf. IAM - ロールの管理
IAM ユーザーまたは AWS のサービスは、AWS API コールを実行するための一時的なセキュリティ認証情報を取得できるロールを担うことができます。
調べた結果
- 一時的な認証情報はAWS Security Token Service (STS)のAssumeRoleというAPIを使って取得できる.
- EC2の場合は,169.254.169.254というリンクローカルアドレスでメタデータを提供しており,そこから取得できる.
- Lambdaの場合は,実行時の環境変数で取得できる.
- CLIやSDKは,EC2のメタデータや環境変数を使ってくれるので,開発者があまり意識せずとも,透過的に一時的な認証情報が使われてAPIをコールする.
cf. AssumeRole - AWS Security Token Service
調べた内容
(コマンドや出力の中のid関係は加工しています.)
どうやって取得するのか
ユーザの場合
まず,ユーザを新たに作り,アクセスキーを発行します.
$ aws iam create-user --user-name roletestuser202201
{
"User": {
"Path": "/",
"UserName": "roletestuser202201",
"UserId": "<user_id>",
"Arn": "arn:aws:iam::<account_id>:user/roletestuser202201",
"CreateDate": "2022-02-01T07:16:01+00:00"
}
}
$ aws iam create-access-key --user-name roletestuser202201
{
"AccessKey": {
"UserName": "roletestuser202201",
"AccessKeyId": "<access_key_id>",
"Status": "Active",
"SecretAccessKey": "<secret_access_key>",
"CreateDate": "2022-02-01T07:17:34+00:00"
}
}
このユーザにS3の読み込み権限ポリシをつけたロールを付与します.
$ aws iam create-role --role-name user-s3-read --assume-role-policy-document file://./user-role.json
{
"Role": {
"Path": "/",
"RoleName": "user-s3-read",
"RoleId": "<role_id>",
"Arn": "arn:aws:iam::<account_id>:role/user-s3-read",
"CreateDate": "2022-02-01T07:34:04+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<account_id>:user/roletestuser202201"
},
"Action": "sts:AssumeRole"
}
]
}
}
}
$ aws iam attach-role-policy --role-name user-s3-read --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::<account_id>:user/roletestuser202201" },
"Action": "sts:AssumeRole"
}
]
}
作ったユーザroletestuser202201で,STSのAssumeRome APIを使って,一時認証情報を発行します.
$ export AWS_ACCESS_KEY_ID=<access_key_id>
$ export AWS_SECRET_ACCESS_KEY=<secret_access_key>
$ aws sts assume-role --role-arn arn:aws:iam::<account_id>:role/user-s3-read --role-session-name user-role-session01
{
"Credentials": {
"AccessKeyId": "<session_access_key_id>",
"SecretAccessKey": "<session_access_key>",
"SessionToken": "<session_token>",
"Expiration": "2022-02-01T08:39:20+00:00"
},
"AssumedRoleUser": {
"AssumedRoleId": "<role_id>:user-role-session01",
"Arn": "arn:aws:sts::<account_id>:assumed-role/user-s3-read/user-role-session01"
}
}
このように,一時的な認証情報(アクセスキーID,シークレットアクセスキー,セッショントークン)が取得できました. 有効期限(Expiration)はコマンドを打った1時間後でした.
サービス(EC2)の場合
When the application runs, it obtains temporary security credentials from Amazon EC2 instance metadata, as described in Retrieving Security Credentials from Instance Metadata. These are temporary security credentials that represent the role and are valid for a limited period of time.
上記ドキュメントで書かれているように,EC2のインスタンスメタデータに一時認証情報があります. 実際の取得方法は以下の通りです.
cf. IAM roles for Amazon EC2 - Amazon Elastic Compute Cloud
EC2インスタンスに付与するロールを次のように作成しました. EC2インスタンスに付与するロールは,インスタンスプロファイルに属している必要があるらしいのですが, インスタンスプロファイルについては正直あまりよく理解せずにやっています.
cf. IAM roles for Amazon EC2 - Amazon Elastic Compute Cloud
$ aws iam create-instance-profile --instance-profile-name ec2-s3-read
{
"InstanceProfile": {
"Path": "/",
"InstanceProfileName": "ec2-s3-read",
"InstanceProfileId": "<instance_profile_id>",
"Arn": "arn:aws:iam::<account_id>:instance-profile/ec2-s3-read",
"CreateDate": "2022-02-01T08:08:31+00:00",
"Roles": []
}
}
$ aws iam create-role --role-name ec2-s3-read --assume-role-policy-document file://./ec2-role.json
{
"Role": {
"Path": "/",
"RoleName": "ec2-s3-read",
"RoleId": "<role_id>",
"Arn": "arn:aws:iam::<account_id>:role/ec2-s3-read",
"CreateDate": "2022-02-01T07:58:05+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
}
$ aws iam attach-role-policy --role-name ec2-s3-read --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
$ aws iam add-role-to-instance-profile --instance-profile-name ec2-s3-read --role-name ec2-s3-read
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
EC2インスタンスを作成し,作ったロールec2-s3-readを付与します(手順省略).
EC2インスタンスに接続し,http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-s3-read
をGETすることで,
一時認証情報が取得できました.
$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-s3-read
{
"Code" : "Success",
"LastUpdated" : "2022-02-01T08:09:19Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "<session_access_key_id>",
"SecretAccessKey" : "<session_secret_access_key>",
"Token" : "<session_token>",
"Expiration" : "2022-02-01T14:44:33Z"
}
サービス(Lambda)の場合
Lambdaの場合は単純で,ロールを付与すれば,ファンクション実行時の環境変数に一時認証情報が入ります.
cf. AWS Lambda 環境変数の使用 - AWS Lambda
- AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_SESSION_TOKEN - 関数の実行ロールから取得したアクセスキー。
Lambdaに付与するロールを次のように作成しました.
$ aws iam create-role --role-name lambda-s3-read --assume-role-policy-document file://./lambda-role.json
{
"Role": {
"Path": "/",
"RoleName": "lambda-s3-read",
"RoleId": "<role_id>",
"Arn": "arn:aws:iam::<account_id>:role/lambda-s3-read",
"CreateDate": "2022-02-01T07:44:16+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
}
$ aws iam attach-role-policy --role-name lambda-s3-read --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
次のようなコードを作り,実行してみました.
exports.handler = async (event, context) => {
console.log('AWS_ACCESS_KEY_ID = ', process.env.AWS_ACCESS_KEY_ID);
console.log('AWS_SECRET_ACCESS_KEY = ', process.env.AWS_ACCESS_KEY_ID);
console.log('AWS_SESSION_TOKEN = ', process.env.AWS_SESSION_TOKEN);
return "success"
};
START RequestId: 5545725b-198e-4ba0-bb6c-c672bfd96650 Version: $LATEST
2022-02-01T07:48:17.016Z 5545725b-198e-4ba0-bb6c-c672bfd96650 INFO AWS_ACCESS_KEY_ID = <session_access_key_id>
2022-02-01T07:48:17.017Z 5545725b-198e-4ba0-bb6c-c672bfd96650 INFO AWS_SECRET_ACCESS_KEY = <session_secret_access_key>
2022-02-01T07:48:17.017Z 5545725b-198e-4ba0-bb6c-c672bfd96650 INFO AWS_SESSION_TOKEN = <session_token>
END RequestId: 5545725b-198e-4ba0-bb6c-c672bfd96650
REPORT RequestId: 5545725b-198e-4ba0-bb6c-c672bfd96650 Duration: 6.01 ms Billed Duration: 7 ms Memory Size: 128 MB Max Memory Used: 55 MB Init Duration: 171.90 ms
どうやってAPIリクエストに利用するのか
一時情報を使ったAPIリクエストは,アクセスキーIDと,シークレットアクセスキーに加え,セッショントークンもAPIリクエストに含める必要があります
cf. AWS リソースを使用した一時的な認証情報の使用 - AWS Identity and Access Management
また、AWS STS から渡されるセッショントークンを API リクエストに追加します。セッショントークンは、HTTP ヘッダーに追加するか、X-Amz-Security-Token という名前のクエリ文字列パラメータに追加します。セッショントークンを追加するのは、HTTPヘッダーまたはクエリ文字列パラメータのいずれかです。
CLIの場合
次の3つを環境変数で渡すことで,一時情報を利用したAPIリクエスト(ロールの権限での実行)が行えます.
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_SESSION_TOKEN
cf. Environment variables to configure the AWS CLI - AWS Command Line Interface
前述の「ユーザの場合」で取得した一時認証情報で,s3のバケットをリスト表示してみます.
$ aws s3 ls s3://test202201292149
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
$ export AWS_ACCESS_KEY_ID=<session_access_key_id>
$ export AWS_SECRET_ACCESS_KEY=<session_secret_access_key>
$ export AWS_SESSION_TOKEN=<session_token>
$ aws s3 ls s3://test202201292149
2022-02-01 18:04:04 19856 graph.png
ユーザroletestuser202201だとアクセスが拒否されましたが,ロールの一時認証情報を使うことで,正常にバケットのリスト表示ができました.
SDKの場合
EC2インスタンスで実行していればメタデータから,CLI同様に環境変数があれば環境変数から,認証情報を使うようです.
cf. Setting credentials in Node.js - AWS SDK for JavaScript
- Loaded from AWS Identity and Access Management (IAM) roles for Amazon EC2
- Loaded from the shared credentials file (~/.aws/credentials)
- Loaded from environment variables
- Loaded from a JSON file on disk
- Other credential-provider classes provided by the JavaScript SDK
EC2インスタンス上で,次のようなスクリプトを実行してみました.
const S3Client = require('@aws-sdk/client-s3').S3Client;
const ListObjectsCommand = require('@aws-sdk/client-s3').ListObjectsCommand;
(async () => {
const s3client = new S3Client({ region: 'ap-northeast-1' });
try {
const data = await s3client.send(new ListObjectsCommand({ Bucket: 'test202201292149'}));
console.log('Success', data.Contents);
} catch (err) {
console.log('Error', err);
}
})();
$ node index.js
Success [
{
Key: 'graph.png',
LastModified: 2022-02-01T09:04:04.000Z,
ETag: '"1a488358622d779e54295f3b74fc9ac9"',
Size: 19856,
StorageClass: 'STANDARD',
Owner: {
DisplayName: '<display_name>',
ID: '<id>'
}
}
]