はじめに
AWSを使い始めてから一番「ちゃんと理解しないといけない」と感じたのがIAMだった。
最初はとりあえずAdministratorAccessを付けて動かしていたが、それがどれだけ危険かを理解してから設計を見直した。PHPのバックエンドではあまり意識しなかった「誰が何にアクセスできるか」という概念が、AWSでは全てのサービスの基盤になっている。
IAMの構成要素を整理する
IAMの主な構成要素:
ユーザー(User)
→ 人間が使うアカウント
→ アクセスキーまたはパスワードで認証
グループ(Group)
→ ユーザーをまとめる入れ物
→ グループにポリシーをアタッチすると全メンバーに適用
ロール(Role)
→ AWSサービスや外部IDが使う権限
→ EC2・ECS・Lambdaなどがロールを「引き受ける」
ポリシー(Policy)
→ 何ができるかを定義するJSONドキュメント
→ ユーザー・グループ・ロールにアタッチ
IDプロバイダー(Identity Provider)
→ GitHub ActionsやGoogleなど外部の認証を信頼する
ポリシーの構造
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadWrite", // 任意のID(説明用)
"Effect": "Allow", // Allow または Deny
"Action": [ // 許可するAPI操作
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [ // 操作対象のリソース
"arn:aws:s3:::my-bucket/*"
],
"Condition": { // 条件(任意)
"StringEquals": {
"s3:prefix": ["uploads/"]
}
}
}
]
}
ARN(Amazon Resource Name)
AWSリソースを一意に識別する文字列。
arn:aws:s3:::my-bucket → S3バケット
arn:aws:s3:::my-bucket/* → バケット内の全オブジェクト
arn:aws:iam::123456789012:user/john → IAMユーザー
arn:aws:iam::123456789012:role/my-role → IAMロール
arn:aws:ecs:ap-northeast-1:123456789012:cluster/production → ECSクラスター
arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:production/* → Secrets Manager
*でワイルドカードを使える。arn:aws:s3:::*は全バケットのすべてのオブジェクトという意味になるので、できるだけ絞った指定にする。
最小権限の原則
「必要最小限の権限だけを与える」という考え方。
// 悪い例 — 全権限を与える
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
// 悪い例 — S3の全操作を許可
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
// 良い例 — 特定バケットの特定操作のみ
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-app-uploads/*"
}
最小権限を実践する手順:
① まず最小の権限でデプロイを試みる
② 権限エラーが出たら必要な権限を追加
③ CloudTrailで実際に使われたAPIを確認して不要な権限を削除
ロールの設計パターン
ECSタスクロール(アプリが使う権限)
// ecs-task-role-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3Access",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-bucket",
"arn:aws:s3:::my-app-bucket/*"
]
},
{
"Sid": "SecretsManagerAccess",
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": [
"arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:production/*"
]
},
{
"Sid": "CloudWatchLogsAccess",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
タスク実行ロール(ECSがECRからイメージを取得するために必要)
// ecs-execution-role-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:ap-northeast-1:123456789012:log-group:/ecs/*"
},
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:production/*"
}
]
}
タスクロールとタスク実行ロールを分けるのが重要。
タスク実行ロール (ecsTaskExecutionRole):
ECSのコントロールプレーンが使う
ECRからイメージを取得する
CloudWatch Logsにログを送る
Secrets Managerから環境変数を取得する
タスクロール (ecsTaskRole):
コンテナ内のアプリが使う
S3へのアクセス
外部APIの呼び出し
DynamoDBへのアクセスなど
GitHub Actionsロール
// github-actions-role-trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": [
"repo:my-org/my-repo:ref:refs/heads/main",
"repo:my-org/my-repo:ref:refs/heads/develop"
]
}
}
}
]
}
// github-actions-role-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecs:DescribeTaskDefinition",
"ecs:RegisterTaskDefinition",
"ecs:UpdateService",
"ecs:DescribeServices"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": ["iam:PassRole"],
"Resource": [
"arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"arn:aws:iam::123456789012:role/ecsTaskRole"
]
}
]
}
iam:PassRoleはECSのタスク定義を更新するときに必要。デプロイロールがECSタスクにロールを渡す権限。
ポリシーの種類
管理ポリシー(Managed Policy):
AWS管理ポリシー → AWSが作成・管理(例: AmazonS3FullAccess)
カスタマー管理ポリシー → 自分で作成・複数のロールにアタッチ可能
インラインポリシー(Inline Policy):
ユーザー/グループ/ロールに直接埋め込む
1つのエンティティにしか使えない
→ 再利用しないポリシーに使う
# カスタマー管理ポリシーを作成
aws iam create-policy \
--policy-name ECSTaskPolicy \
--policy-document file://ecs-task-policy.json
# ロールにアタッチ
aws iam attach-role-policy \
--role-name ecsTaskRole \
--policy-arn arn:aws:iam::123456789012:policy/ECSTaskPolicy
IAMユーザーのベストプラクティス
ルートアカウントを使わない
AWSアカウントを作成すると「ルートユーザー」が作られる。
ルートユーザーは全権限を持つが日常的に使ってはいけない。
やること:
① ルートユーザーにMFAを設定する
② 管理者用のIAMユーザーを作る
③ 以降はIAMユーザーで作業する
④ ルートユーザーは請求設定の変更など必要な場面だけ使う
アクセスキーを使わない
アクセスキーは漏洩すると即悪用される。
GitHubに誤ってコミットした瞬間にAWSが検知してメールが来る(経験済み)。
アクセスキーの代替:
EC2/ECS/Lambda → IAMロール(自動で取得)
GitHub Actions → OIDC(前述)
ローカル開発 → aws configure または aws-vault
やむを得ずアクセスキーを使う場合:
→ 必要最小限の権限のみ付与
→ 定期的にローテーション(90日以内)
→ 使わなくなったら即削除
# アクセスキーの一覧確認
aws iam list-access-keys --user-name my-user
# 最終使用日時の確認(使われていないキーを発見)
aws iam get-access-key-last-used \
--access-key-id AKIAIOSFODNN7EXAMPLE
Permission Boundary
IAMユーザーやロールに設定できる権限の上限。
// permission-boundary.json
// このポリシーを境界として設定すると、
// S3とCloudWatchのみしか許可できなくなる
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:*", "logs:*"],
"Resource": "*"
}
]
}
# ロールの作成時にPermission Boundaryを設定
aws iam create-role \
--role-name limited-role \
--assume-role-policy-document file://trust-policy.json \
--permissions-boundary arn:aws:iam::123456789012:policy/PermissionBoundary
開発者にIAMロールの作成権限を与えるが、自分より強い権限のロールを作れないようにする場合に使う。
SCPでOrganization全体に制限をかける
複数のAWSアカウントを管理する場合、AWS OrganizationsのSCP(Service Control Policy)で全アカウントに制限をかけられる。
// 特定リージョン以外では全操作を禁止
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": [
"ap-northeast-1",
"us-east-1" // グローバルサービス用
]
}
}
}
]
}
CloudTrailでIAMの操作を監視する
import boto3
cloudtrail = boto3.client('cloudtrail', region_name='ap-northeast-1')
# 特定のユーザーの操作履歴を取得
response = cloudtrail.lookup_events(
LookupAttributes=[
{
'AttributeKey': 'Username',
'AttributeValue': 'my-user'
}
],
MaxResults = 10,
)
for event in response['Events']:
print(f"{event['EventTime']} - {event['EventName']} - {event['Username']}")
# IAM関連の操作履歴をフィルタ
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventSource,AttributeValue=iam.amazonaws.com \
--query 'Events[*].{Time:EventTime,Event:EventName,User:Username}' \
--output table
「誰がどのIAMロールを変更したか」を追跡できる。セキュリティインシデント発生時の調査に使う。
実務でよくあるIAMのミス
① ワイルドカード(*)を使いすぎる
→ 具体的なリソースARNを指定する
② 開発用のAdministratorAccessを本番に流用する
→ 環境ごとに権限を分ける
③ アクセスキーを.envやコードに書く
→ IAMロールやOIDCを使う
④ 使っていないユーザー/ロール/ポリシーを放置する
→ 定期的に棚卸しする
⑤ ロールにポリシーを直接書く(インラインポリシーの濫用)
→ 再利用するならカスタマー管理ポリシーにする
⑥ タスクロールとタスク実行ロールを混同する
→ ECSの場合は必ず分ける
⑦ 権限を追加するだけで削除しない
→ IAM Access Analyzerで不要な権限を検出する
IAM Access Analyzerの活用
# Access Analyzerを有効化
aws accessanalyzer create-analyzer \
--analyzer-name my-analyzer \
--type ACCOUNT
# 外部からアクセス可能なリソースの一覧
aws accessanalyzer list-findings \
--analyzer-name my-analyzer \
--filter '{"status": {"eq": ["ACTIVE"]}}'
# 実際に使用されていない権限を分析
aws iam generate-service-last-accessed-details \
--arn arn:aws:iam::123456789012:role/my-role
# 結果を取得
aws iam get-service-last-accessed-details \
--job-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
--query 'ServicesLastAccessed[?TotalAuthenticatedEntities==`0`]'
使われていないサービスへの権限を検出して削除できる。
まとめ
- ポリシーはEffect・Action・Resourceの3要素で構成される
- 最小権限の原則を守り
*の使用を最小化する - ECSはタスクロール(アプリ)とタスク実行ロール(ECS基盤)を分ける
- GitHub ActionsはOIDCを使ってアクセスキーを排除する
- ルートアカウントにMFAを設定し日常的に使わない
- アクセスキーはIAMロールで代替できる場合は使わない
- CloudTrailでIAM操作を監視する
- IAM Access Analyzerで不要な権限を定期的に棚卸しする
AWSを触り始めた当初は「とりあえず動けばいい」でAdministratorAccessを付けていたが、最小権限を意識してから「このサービスに必要な権限は何か」を考える習慣がついた。IAMは地味だが全サービスの基盤なので、最初にちゃんと理解しておいてよかった。