IAMの既存ポリシーの名前に惑わされない


最近こういう事件がありました。

AWS上のDynamoDBに対する権限を本来持っていないはずのエンジニアが、ローカルでDynamoDB localを試そうとして色々作業していたら、AWS上にテーブルを作成していた。やばい。

最初は「マジか。」となったんですが、幸いDynamoDBのテーブルをまったく作ったことのないAWSアカウント上だったので大事にいたりませんでした。

めでたしめでたし👏

となるはずはもちろんないので、原因を探りました。

IAMをよく見てみた

結論から言うと、AWSLambdaFullAccess という既存のポリシーをアタッチしたIAMユーザーのAccess Key IDとSecret Access Keyを設定したSDKでテーブルの作成を実行していたことが原因でした。

このポリシーの概要を確認すると

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudformation:DescribeChangeSet",
                "cloudformation:DescribeStackResources",
                "cloudformation:DescribeStacks",
                "cloudformation:GetTemplate",
                "cloudformation:ListStackResources",
                "cloudwatch:*",
                "cognito-identity:ListIdentityPools",
                "cognito-sync:GetCognitoEvents",
                "cognito-sync:SetCognitoEvents",
                "dynamodb:*",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcs",
                "events:*",
                "iam:GetPolicy",
                "iam:GetPolicyVersion",
                "iam:GetRole",
                "iam:GetRolePolicy",
                "iam:ListAttachedRolePolicies",
                "iam:ListRolePolicies",
                "iam:ListRoles",
                "iam:PassRole",
                "iot:AttachPrincipalPolicy",
                "iot:AttachThingPrincipal",
                "iot:CreateKeysAndCertificate",
                "iot:CreatePolicy",
                "iot:CreateThing",
                "iot:CreateTopicRule",
                "iot:DescribeEndpoint",
                "iot:GetTopicRule",
                "iot:ListPolicies",
                "iot:ListThings",
                "iot:ListTopicRules",
                "iot:ReplaceTopicRule",
                "kinesis:DescribeStream",
                "kinesis:ListStreams",
                "kinesis:PutRecord",
                "kms:ListAliases",
                "lambda:*",
                "logs:*",
                "s3:*",
                "sns:ListSubscriptions",
                "sns:ListSubscriptionsByTopic",
                "sns:ListTopics",
                "sns:Publish",
                "sns:Subscribe",
                "sns:Unsubscribe",
                "sqs:ListQueues",
                "sqs:SendMessage",
                "tag:GetResources",
                "xray:PutTelemetryRecords",
                "xray:PutTraceSegments"
            ],
            "Resource": "*"
        }
    ]
}

と書かれています。Lambdaと、Lambda経由でいろんなサービスを実行するための諸々の権限が含まれている…という感じですかね。最初にこのポリシーをアタッチするときにポリシーの名前だけ見ていて、これをアタッチすればLambdaを見たり編集できるぜと勘違いをして設定していたかと思います。ただ、実際に与えられてる権限は上記の通りそれだけではありませんでした🤮

対策

今回は AWSLambdaFullAccess が原因ではあったのですが、他のポリシーでも同様の現象を引き起こす可能性があります。そこでいくつか対策を考えてみました。

1. 権限を適切に付与する

まずは権限を適切に付与できているのか確認し、できていなければ修正を行おうと思います。

適切に権限が付与されているか確認するのには、IAMで各ユーザーに付与されているアクセスアドバイザーが便利です。アクセスアドバイザーでは

  • アクセスできるサービス
  • アクセスできるサービスの権限がどのポリシーで付与されているか
  • 最後に使用したのはいつか

がわかります。

もしそのユーザーに対して余計な権限が付与されているようであれば、アタッチされているポリシーやロールの見直しを行いましょう。

2. そもそもIAMユーザーを無闇矢鱈に発行しない

そもそもそのユーザーアカウントは必要なのか考えたほうがよいかもしれません。
不要なアカウントは発行しないような設計を行うべきです。

3. スイッチロールを使う

何かあったらクリティカルなAWSアカウントに不用意にユーザーを作りたくない。けど、要所要所で作業はできるようにしておきたい。そんな場合はスイッチロールできるように設定しておくといいかもしれません。

本番用と開発用とでAWSアカウント2つ用意しておき、普段は開発用のAWSアカウントのみを使用するようにし、本番のAWSアカウントでは必要最低限のユーザー、ロールだけにしておく。そして必要に応じてロールをスイッチするようにすれば事故の可能性は減らせるかなと思います。

スイッチロールに関しては

Swith Roleで複数のAWSアカウント間を切替える

に詳しく書かれています。もし本番と開発とでAWSアカウントが別れていない場合は、組織の大小やサービスの状態などにもよるでしょうが、アカウントを分ける検討をするとよいかと思います。

4. 既存のポリシーをアタッチするときはちゃんと確認する

当前ですが、何するかわからないポリシーをアタッチしてはいけないということですね。

終わりに

今回のように、DynamoDBなどは1つのAWSアカウントに対して同じ名前のテーブルは一つしか作成できないので、万が一テーブルの破棄などを誤って行ったらサービスが死んでしまう可能性があります。これはだいぶ怖いですね。

IAMは本当難しいなぁと最近感じるところですが、ちゃんと管理できてれば安心感がグッと変わってきます。安心感は開発のしやすさにダイレクトに影響してきますので、しっかり管理していきたいところです。