IAMアクセスキーの自動ローテーションを実装した話
IAMアクセスキーの自動ローテーションを実装した話
はじめに
セキュリティに強い会社に最近入社したので、初めの1本目はセキュリティに関連したことでも書こうかなと思い、色々と考えた結果、直近で実装した「AWS汎用ダッシュボードの構成」について書いていこうと思います。
このダッシュボードは、「1つのダッシュボードから、複数のAWSアカウントのリソース情報を横断的に可視化したい」という要件がありましたので、それを実現するための認証方式で実装しました。
そこで実装したのが下記の構成で、踏み台用のIAMユーザの認証情報を利用し、各AWSアカウントのIAMロールをAssumeRoleする構成にしました。※本来であれば、IAMユーザではなく、IAMロールを使用することをAWSでは推奨していますが、常時見える状態にしたいという要件上、今回はIAMユーザを使うパターンで実装してます。
EC2
↓
IAMユーザ(Access Key/Secret Access Key)
↓
IAMロール
↓
情報を見たいAWSアカウント群
図式化すると、こんな感じですね。ここでは、IAMユーザのアクセスキーのローテーションも併せて実装してます。

では、上記でやった内容をより、次章以降で詳しく書いていこうと思います。
実装編
(1)IAMユーザの作成
まず、下記のポリシーを持つIAMユーザを作成します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAssumeTargetRole",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::111111111111:role/CrossAccount-AAAA-TargetRole",
"arn:aws:iam::222222222222:role/CrossAccount-AAAA-TargetRole",
"arn:aws:iam::333333333333:role/CrossAccount-AAAA-TargetRole",
"arn:aws:iam::444444444444:role/CrossAccount-AAAA-TargetRole"
]
}
]
}
(2)ターゲットAWSアカウント側での認証情報の作成作業
複数のAWSアカウントに対して、手順(1)で作成したIAMユーザからのアクセスに対してReadOnlyアクセス権限を付与する必要があるので、1個1個作成するのではなく、StackSetsの使用がベストプラクティスになるので、下記のCfnテンプレートを使ってデプロイします。
AWSTemplateFormatVersion: '2010-09-09'
Description: >
Creates a custom role in THIS account that trusts a specific IAM principal
from another AWS account (user or role), defined via Parameters.
Parameters:
TrustedPrincipalArn:
Type: String
Description: ARN of the IAM User or IAM Role allowed to assume this role.
Default: arn:aws:iam::999999999999:user/AAAA-user
RoleName:
Type: String
Default: CrossAccount-AAAA-TargetRole
Description: Name of the role to create in this (target) account.
ManagedPolicyArns:
Type: List<String>
Default: arn:aws:iam::aws:policy/ReadOnlyAccess
Description: >
AWS managed or customer managed policy ARNs to attach to the role.
MaxSessionDurationSeconds:
Type: Number
Default: 3600
AllowedValues: [900, 3600, 43200]
Description: Max session duration for the role in seconds (15m, 1h, or 12h).
Resources:
CrossAccountRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref RoleName
MaxSessionDuration: !Ref MaxSessionDurationSeconds
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: TrustSpecificPrincipal
Effect: Allow
Principal:
AWS: !Ref TrustedPrincipalArn
Action:
- sts:AssumeRole
- sts:TagSession
ManagedPolicyArns: !Ref ManagedPolicyArns
(3)アクセスキーのローテーション
ここまでで、下記のリソースが作成されました。
| 概要 | 説明 |
|---|---|
| source側認証設定 | 特定のAWSアカウントに対して、AssumeRoleする権限のみを付与したIAMユーザ |
| ターゲット側認証設定 | 特定のIAMユーザからのみ、 AssumeRoleを許可する権限 |
ここまで実装すれば、EC2側に認証情報を設定さえすれば、クロスアカウントアクセスによるAWS環境情報の取得ができるようになりました。
ただ、AWSのベストプラクティス的には、IAMユーザの認証情報をOS内に格納しっぱなしは、セキュリティリスクが高いと評価されてしまうので、非常によろしくないです。
そのため、プラスアルファの対応として、アクセスキーのローテーションを実装する必要があります。
現時点では、IAMユーザのアクセスキーのローテーション機能は、AWS側では実装されていないので、そこはユーザ側で実装する必要があります。
使用するAWSサービスとしては、LambdaとSecretsManagerの2つです。
(3.1) SecretsManager側の実装
「app/access-keys」という名前で、シークレットを作成し、Access Key/Secret Access Keyを格納する。
(3.2) Lambda側の実装
EventBridge scheduler+Lambdaで定期的に手順(3.1)のシークレットを定期的にローテーションする仕組みを設定する。
サンプルコードも下記に記載します。(Python3.10)
import boto3
import json
import os
IAM_USER = os.environ["IAM_USER"]
SECRET_NAME = os.environ["SECRET_NAME"]
def lambda_handler(event, context):
iam = boto3.client("iam")
secrets = boto3.client("secretsmanager")
# Get current secret (for checking the current value. Not currently used)
try:
secret_value = secrets.get_secret_value(SecretId=SECRET_NAME)
current_creds = json.loads(secret_value["SecretString"])
except Exception as e:
print(f"Failed to get current secret: {e}")
current_creds = None
# Get a list of existing access keys
keys = iam.list_access_keys(UserName=IAM_USER)["AccessKeyMetadata"]
print(f"Existing keys: {[k['AccessKeyId'] for k in keys]}")
# Issue a new access key
new_key = iam.create_access_key(UserName=IAM_USER)["AccessKey"]
print(f"Created new access key: {new_key['AccessKeyId']}")
# Update Secrets Manager values
new_secret_string = json.dumps({
"access_key": new_key["AccessKeyId"],
"secret_key": new_key["SecretAccessKey"]
})
secrets.put_secret_value(
SecretId=SECRET_NAME,
SecretString=new_secret_string
)
print("Updated Secrets Manager with new credentials")
# Delete the old access key (all but the new one)
for key in keys:
if key["AccessKeyId"] != new_key["AccessKeyId"]:
iam.delete_access_key(
UserName=IAM_USER,
AccessKeyId=key["AccessKeyId"]
)
print(f"Deleted old access key: {key['AccessKeyId']}")
return {"status": "success"}
上記で、IAMアクセスキーをローテーションし、Secrets Managerに更新後のデータが格納される仕組みとなってます。
あとは、EC2側の認証ファイルをCron+Scriptでちょちょっと書き換えるようにすればOKです。
最後に
はい、まあこんな感じで、IAMユーザの認証情報をOS内に埋め込む運用において、セキュリティを意識した構成で実装しましたよ。という内容の記事でした。
この記事がどなたかの参考になれば幸いです。
Discussion