
IAM Access Analyzer の内部アクセス(Internal access)機能を単一のAWSアカウント上で使ってみた
あしざわです。
先日のAWS re:Inforce 2025 にて、IAM Access Analyzer の内部アクセス(Internal access)機能が発表されました。
この機能によって、組織のIAMに関するガバナンス・コンプライアンスをより向上させることができます。
本記事では、Organizationsではない単一のAWSアカウント上で簡単な機能検証を行います。
概要
内部アクセス機能を利用すると、特定のS3・RDS・DynamoDBなどのリソースへアクセスできる、 AWSアカウント上にあるすべてのIAMプリンシパル (IAMユーザー、ロール)を特定できます。
これまでのIAM Access Analyzerのサポート範囲はこちらの2つでした。
- 外部アクセス
- 未使用アクセス
外部アクセスは指定した信頼ゾーン (AWSアカウントもしくはOrganizations組織) 外からのアクセスを検知、未使用アクセスはAWSアカウント内のIAMプリンシパルの使用状況を監視します。
内部アクセスの登場によって、信頼ゾーン内にあるIAMプリンシパルの最小権限の実現や、機微情報が保管されているS3バケット等へのアクセスを検知できるようになりました。
組織のガバナンス・コンプライアンスをより強化できる機能といえます。
やってみた
今回は、内部アクセス機能を利用して以下を検証します。
- 内部アクセスアナライザーの検出結果を検知させる
- 検出結果をSNS経由でメール通知させる
検証全体の構成図はこちら
なお、検証は単一のAWSアカウントで実施するため、信頼ゾーンはAWSアカウントとします(Organizations組織ではありません)
事前に、以下リソースを作成しています。
S3バケット
- リソース名:
internal-access-test-bucket-{AWSアカウントID}
バケットポリシーは以下
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowInternalRoleAccess",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"s3:GetObject",
"s3:ListBucket",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"${bucket_arn}",
"${bucket_arn}/*"
],
"Condition": {
"StringLike": {
"aws:PrincipalArn": "arn:aws:iam::${account_id}:role/internal-*"
}
}
}
]
}
IAMロール
- リソース名:
internal-role
、internal-malicious-role
- IAMポリシー(共通):
ReadOnlyAccess
信頼ポリシーは以下
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${account_id}:root"
},
"Action": "sts:AssumeRole"
}
]
}
はじめに、メール通知用のSNSトピック および EventBridgeルールを作成します。
SNSトピック(internal-access-analyzer-topic
)を作成し、サブスクリプションで受信可能なメールアドレスを設定。Subscription Confirmationまで完了させます。
EventBridgeルール(internal-access-analyzer-rule
)を作成し、イベントパターンとしてこちらを設定。
{
"source": ["aws.access-analyzer"],
"detail-type": ["Internal Access Finding"]
}
マネジメントコンソールのイベントパターンからこのように指定できました。しっかりとアップデートに追従しているようです。
ターゲットには、先ほど作成したSNSトピックを指定します。実行ロールは新規作成しました。
ルールが作成できました。
続いて、内部アナライザーを作成します。
検出結果のタイプでResource analysis - Internal access
を選択し、アナライザーの名前を入力しました。
Resources to analyzeは、〜by pasting in resource ARN
を指定します。これを選べばリソースを個別に分析対象として指定できます。
事前に作成したS3バケットのARNを入力しました。
このように表示されます。
試しに、ここで存在しないS3バケット名を指定してもバリデーションで弾かれず、入力できました。
ARNのリンク先はS3バケットの詳細画面に遷移するようになっていますが、存在しないバケットの場合はエラー画面になります。
内部アクセスアナライザーを作成できました。このまましばらく待ちます。
数分後、35件の検出結果を検知しました。
SNSで設定したメール通知も35件届いていました。
ここでは事前に作成したIAMロール2つのみを検出させたかったのですが、うまくいきませんでした。
この原因は、IAMの評価論理の仕様にあります。
S3バケットへのアクセスは以下のどちらか一方があれば許可されます。
- リソースベースポリシー (S3バケットポリシーなど)で明示的に許可されている
- アイデンティティベースポリシー (IAMポリシーなど)で明示的に許可されている
事前作成したS3バケットのバケットポリシーではarn:aws:iam::${account_id}:role/internal-*
のARNに一致するIAMプリンシパルのみを許可しています。
しかし実際には、同一AWSアカウント上にあるIAMプリンシパルがReadOnlyAccessやAdministratorアクセスのような広めのIAM許可ポリシーを持っていれば、このARNに一致しなくてもS3バケットにアクセスできます。
そのため、同一AWSアカウントの指定したIAMロールからのみアクセスを許可したい場合は、バケットポリシー内に 明示的な許可設定 と 明示的な拒否設定 のどちらも必要になります。
ここで私はバケットポリシーを以下のように変更しました。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyAllOthers",
"Effect": "Deny",
"Principal": {
"AWS": "*"
},
"Action": "s3:*",
"Resource": [
"${bucket_arn}",
"${bucket_arn}/*"
],
"Condition": {
"StringNotLike": {
"aws:PrincipalArn": [
"arn:aws:iam::${account_id}:role/internal-*",
"arn:aws:iam::${account_id}:role/<普段検証で利用しているロール>"
]
}
}
}
]
}
バケットポリシー変更後、以下のようなエラーとなってしまいました。
各検出結果の詳細を見てみると、Access Analyzerがリソースのメタデータにアクセスできないというエラーのようでした。
外部アクセス
内部アクセス
私はS3バケットの制限を厳しくし過ぎてしまった結果、アナライザーがリソースにアクセスできなくなったのだと想定しました。
IAM Access AnalyzerのアナライザーはAWSServiceRoleForAccessAnalyzer
というサービスリンクロール経由でリソースにアクセスします。
許可ポリシーとして設定されている AccessAnalyzerServiceRolePolicy
は現時点で以下のポリシードキュメントで構成されていました。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AccessAnalyzerServiceRolePolicy",
"Effect": "Allow",
"Action": [
"dynamodb:GetResourcePolicy",
"dynamodb:ListStreams",
"dynamodb:ListTables",
"ec2:DescribeAddresses",
"ec2:DescribeByoipCidrs",
"ec2:DescribeSnapshotAttribute",
"ec2:DescribeSnapshots",
"ec2:DescribeVpcEndpoints",
"ec2:DescribeVpcs",
"ec2:GetSnapshotBlockPublicAccessState",
"ecr:DescribeRepositories",
"ecr:GetAccountSetting",
"ecr:GetRegistryPolicy",
"ecr:GetRepositoryPolicy",
"elasticfilesystem:DescribeFileSystemPolicy",
"elasticfilesystem:DescribeFileSystems",
"iam:GetRole",
"iam:ListEntitiesForPolicy",
"iam:ListRoles",
"iam:ListUsers",
"iam:ListRoleTags",
"iam:ListUserTags",
"iam:GetAccountAuthorizationDetails",
"iam:GetUser",
"iam:GetGroup",
"iam:GenerateServiceLastAccessedDetails",
"iam:GetServiceLastAccessedDetails",
"iam:ListAccessKeys",
"iam:GetLoginProfile",
"iam:GetAccessKeyLastUsed",
"iam:ListRolePolicies",
"iam:GetRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:ListUserPolicies",
"iam:GetUserPolicy",
"iam:ListAttachedUserPolicies",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:ListGroupsForUser",
"kms:DescribeKey",
"kms:GetKeyPolicy",
"kms:ListGrants",
"kms:ListKeyPolicies",
"kms:ListKeys",
"lambda:GetFunctionUrlConfig",
"lambda:GetLayerVersionPolicy",
"lambda:GetPolicy",
"lambda:ListAliases",
"lambda:ListFunctions",
"lambda:ListLayers",
"lambda:ListLayerVersions",
"lambda:ListVersionsByFunction",
"organizations:DescribeAccount",
"organizations:DescribeOrganization",
"organizations:DescribeOrganizationalUnit",
"organizations:ListAccounts",
"organizations:ListAccountsForParent",
"organizations:ListAWSServiceAccessForOrganization",
"organizations:ListChildren",
"organizations:ListDelegatedAdministrators",
"organizations:ListOrganizationalUnitsForParent",
"organizations:ListParents",
"organizations:ListRoots",
"rds:DescribeDBClusterSnapshotAttributes",
"rds:DescribeDBClusterSnapshots",
"rds:DescribeDBSnapshotAttributes",
"rds:DescribeDBSnapshots",
"s3:DescribeMultiRegionAccessPointOperation",
"s3:GetAccessPoint",
"s3:GetAccessPointPolicy",
"s3:GetAccessPointPolicyStatus",
"s3:GetAccountPublicAccessBlock",
"s3:GetBucketAcl",
"s3:GetBucketLocation",
"s3:GetBucketPolicyStatus",
"s3:GetBucketPolicy",
"s3:GetBucketPublicAccessBlock",
"s3:GetMultiRegionAccessPoint",
"s3:GetMultiRegionAccessPointPolicy",
"s3:GetMultiRegionAccessPointPolicyStatus",
"s3:ListAccessPoints",
"s3:ListAllMyBuckets",
"s3:ListMultiRegionAccessPoints",
"s3express:GetAccessPoint",
"s3express:GetAccessPointPolicy",
"s3express:GetBucketPolicy",
"s3express:ListAllMyDirectoryBuckets",
"s3express:ListAccessPointsForDirectoryBuckets",
"sns:GetTopicAttributes",
"sns:ListTopics",
"secretsmanager:DescribeSecret",
"secretsmanager:GetResourcePolicy",
"secretsmanager:ListSecrets",
"sqs:GetQueueAttributes",
"sqs:ListQueues"
],
"Resource": "*"
}
]
}
S3バケットにIAM Actionを個別に許可するとメンテナンスが大変なので、このサービスリンクロールごとバケットポリシーの拒否対象外にしました。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyAllOthers",
"Effect": "Deny",
"Principal": {
"AWS": "*"
},
"Action": "s3:*",
"Resource": [
"${bucket_arn}",
"${bucket_arn}/*"
],
"Condition": {
"StringNotLike": {
"aws:PrincipalArn": [
"arn:aws:iam::${account_id}:role/internal-*",
"arn:aws:iam::${account_id}:role/cm-ashizawa.hiroaki",
"arn:aws:iam::${account_id}:role/aws-service-role/access-analyzer.amazonaws.com/AWSServiceRoleForAccessAnalyzer"
]
}
}
}
]
}
おおよそ24時間後、再チェックが実行され検出結果が表示されました。
S3バケットに記載したDenyポリシーのConditionで除外した、ワイルドカードを含む3種類のIAMプリンシパルのみで検知していますね。想定通りです。
料金
最後に、気になるのが料金です。
英語版のPricingページによると、IAM Access Analyzerの内部アクセスは以下の料金体系となっています。
分析対象のリソース1つにつき、9.00 USD/月
検証後の翌日、Cost Exlorerを確認すると1日で9.00USD 課金されていました。
9.00 USDを1ヶ月30日分に均したりしてくれないようです。気をつけましょう。
さいごに
ここまで、IAM Access Analyzer の内部アクセス(Internal access)機能の概要と使い方を検証しました。
途中で簡単に解説したIAMの評価論理は、こちらの記事がとてもわかりやすいです。ぜひ一通り読んでみてください。
IAM Access Analyzerの内部アクセス機能は、組織内のリソースアクセス状況を可視化する強力なツールです。特に、S3バケットなどの機微なリソースに対するアクセス権限を網羅的に把握できる点が価値的です。
ただし、IAMの評価論理を正しく理解していないと、意図した通りの検出結果が得られない場合があります。リソースベースポリシーとアイデンティティベースポリシーの両方を考慮し、明示的な拒否設定も適切に行う必要があります。
また、1リソースあたり月額9.00USDという料金設定のため、分析対象リソースの選定は慎重に行うことをおすすめします。コスト効率を考慮しつつ、ガバナンス強化に活用したい機能です。
以上です。