michimani.net

CloudTrail の証跡から IAM ポリシーを作成する IAM Access Analyzer の新機能を AWS CLI で試す

2021-04-08

CloudTrail に記録された証跡から IAM ポリシーを生成する機能が AWS IAM Access Analyzer に追加されました。今回はその機能を AWS CLI から使ってみます。

目次

新機能の概要

AWS IAM Access Analyzer の機能として、CLoudTrail に記録された証跡から IAM ポリシーを生成するという機能が発表されました。これはつまり、実際のリクエストに基づいて最低限必要な権限を付与したポリシーを生成できる1という機能になります。

AWS CLI から使ってみる

マネジメントコンソールからの操作については、上記の AWS ブログやクラスメソッドさんのブログで既に書かれているので、ここでは AWS CLI からその機能を使ってみます。

対応するコマンド

今回の機能を使うには、 accessanalyzer コマンドの start-policy-generation サブコマンドを使用します。

例のごとく、この記事を書いている時点では v1 の最新バージョンである 1.19.47 でのみ対応しており、 v2 の最新バージョンである 2.1.35 では使えません。

サブコマンドの詳細

accessanalyzer start-policy-generation のヘルプでオプションを確認します。

$ aws accessanalyzer start-policy-generation help
...
SYNOPSIS
            start-policy-generation
          [--client-token <value>]
          [--cloud-trail-details <value>]
          --policy-generation-details <value>
          [--cli-input-json <value>]
          [--generate-cli-skeleton <value>]
...

ふーん、なるほど。ということで --generate-cli-skeleton で詳細を確認してみます。

$ accessanalyzer start-policy-generation --generate-cli-skeleton

{
    "clientToken": "",
    "cloudTrailDetails": {
        "accessRole": "",
        "endTime": "1970-01-01T00:00:00",
        "startTime": "1970-01-01T00:00:00",
        "trails": [
            {
                "allRegions": true,
                "cloudTrailArn": "",
                "regions": [
                    ""
                ]
            }
        ]
    },
    "policyGenerationDetails": {
        "principalArn": ""
    }
}

割とシンプルですね。

ちなみに、今回の機能に関連して新たに追加されたサブコマンドは、 start-policy-generation を含めて下記の 4 つです。

IAM ポリシーの生成には少し時間がかかるようなので、生成をキャンセルしたり、生成中の一覧を取得したりできるコマンドが追加されています。

やってみる

では、早速 start-policy-generation で IAM ポリシーを生成してみます。

必須のオプションは --policy-generation-details のみなので、まずはこれのみ指定して実行してみます。

--policy-generation-details で指定するのは principalArn です。この ARN とは、生成する IAM ポリシーをアタッチする予定の IAM ユーザー または IAM ロールの ARN になります。今回は AWS CLI を実行するユーザー (ロール) の ARN を指定することにしますが、その ARN を取得するためには sts get-caller-identity を使用します。

$ PRINCIPAL_ARN=$( \
  aws sts get-caller-identity \
  --query "Arn" \
  --output text
) && echo "${PRINCIPAL_ARN}"

arn:aws:iam::111111111111:user/hogehoge

この値を使って、次のように実行してみます。アウトプットとして jobID が出力されるので、変数で受けます。

$ JOB_ID=$( \
  aws accessanalyzer start-policy-generation \
  --policy-generation-details principalArn=${PRINCIPAL_ARN} \
  --query "jobId" \
  --output text \
) && echo "${JOB_ID}"

An error occurred (ValidationException) when calling the StartPolicyGeneration operation: Missing cloudTrailDetails

そんな気はしていましたが、 --cloud-trail-details オプションで CloudTrail の条件も指定する必要がありました。

CloudTrail の条件は下記形式の JSON で指定します。

{
  "accessRole": "",
  "endTime": "1970-01-01T00:00:00",
  "startTime": "1970-01-01T00:00:00",
  "trails": [
    {
      "allRegions": true,
      "cloudTrailArn": "",
      "regions": [
        ""
      ]
    }
  ]
}

それぞれ必要なものを準備していきます。

対象となる CloudTrail の証跡の ARN

今回は一つの証跡を対象として、リージョンも東京 (ap-northeast-1) に絞って生成することにします。なので、先に対象の証跡の ARN を取得しておきます。

$ TRAIL_ARN=$( \
  aws cloudtrail list-trails \
  --query "Trails[0].TrailARN" \
  --output text \
) && echo "${TRAIL_ARN}"

arn:aws:cloudtrail:ap-northeast-1:111111111111:trail/hogehoge-trail

IAM ポリシー生成用の IAM ロールを作成する

accessRole に指定する IAM ポリシー生成用の IAM ロールは新たに作成する必要があるので、作成します。(マネジメントコンソールから実行する場合、初回は勝手に作ってくれます)
その際に、 CloudTrail の証跡が保存されている S3 バケット名が必要になるので、あらかじめ取得しておきます。

$ TRAIL_BUCKET_NAME=$( \
  aws cloudtrail get-trail \
  --name "${TRAIL_ARN}" \
  --query "Trail.S3BucketName" \
  --output text \
) && echo "${TRAIL_BUCKET_NAME}"

hogehoge-trail-bucket-000000

S3 バケット名を用いて、下記のような IAM ポリシードキュメントを作成します。

$ cat << EOF > access-role-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "cloudtrail:GetTrail",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iam:GenerateServiceLastAccessedDetails",
        "iam:GetServiceLastAccessedDetails"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket",
        "s3:GetBucketLocation"
      ],
      "Resource": [
        "arn:aws:s3:::${TRAIL_BUCKET_NAME}",
        "arn:aws:s3:::${TRAIL_BUCKET_NAME}/*"
      ]
    }
  ]
}
EOF

※対象となる CloudTrail の証跡が KMS 暗号化されている場合は、下記の権限も必要になります。

{
  "Effect": "Allow",
  "Action": [
    "kms:Decrypt"
  ],
  "Resource": [
    "arn:aws:kms:<region>:<account-id>:key/<key>"
  ],
  "Condition": {
    "StringLike": {
      "kms:ViaService": "s3.*.amazonaws.com"
    }
  }
}

生成した JSON をもとに IAM ポリシーを作成します。

$ ACCESS_ROLE_POLICY_ARN=$( \
aws iam create-policy \
--policy-name "iam-access-analizer-generate-policy-role-policy" \
--path "/sample/" \
--policy-document file://access-role-policy.json \
--query "Policy.Arn" \
--output text \
) && echo "${ACCESS_ROLE_POLICY_ARN}"

arn:aws:iam::111111111111:policy/sample/iam-access-analizer-generate-policy-role-policy

続いて、この IAM ポリシーをアタッチする IAM ロールを作成します。その際、 IAM Access Analyzer に対する信頼ポリシーが必要になるので、下記のような信頼ポリシードキュメント (JSON) を作成しておきます。

$ cat <<EOF > assume-role-policy-doc.json
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "access-analyzer.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

IAM ロールを作成します。

$ ACCESS_ROLE_ARN=$( \
  aws iam create-role \
  --role-name iam-access-analizer-generate-policy-role \
  --assume-role-policy-document file://assume-role-policy-doc.json \
  --query "Role.Arn" \
  --output text \
) && echo "${ACCESS_ROLE_ARN}"

arn:aws:iam::111111111111:role/iam-access-analizer-generate-policy-role

作成した IAM ロールに、先ほど作成した IAM ポリシーをアタッチします。

$ aws iam attach-role-policy \
--role-name iam-access-analizer-generate-policy-role \
--policy-arn "${ACCESS_ROLE_POLICY_ARN}"

cloud-trail-details オプション用の JSON を作成

ここまでで、 --cloud-trail-details オプションで指定するために必要な情報が揃いました。実際に start-policy-generation 実行時に指定する際には JSON ファイルとして指定するので、これまでの情報を JSON ファイルとして生成します。

なお、証跡の解析期間は 2021年に入ってから現時点まで とします。

$ cat << EOF > cloud-trail-details.json
{
  "accessRole": "${ACCESS_ROLE_ARN}",
  "startTime": "2021-01-01T00:00:00",
  "trails": [
    {
      "allRegions": false,
      "cloudTrailArn": "${TRAIL_ARN}",
      "regions": [
        "ap-northeast-1"
      ]
    }
  ]
}
EOF

start-generating-policy を実行

諸々準備が整ったのであらためて start-generating-policy を実行します。

$ JOB_ID=$( \
  aws accessanalyzer start-policy-generation \
  --policy-generation-details principalArn=${PRINCIPAL_ARN} \
  --cloud-trail-details file://cloud-trail-details.json \
  --query "jobId" \
  --output text \
) && echo "${JOB_ID}"

An error occurred (ServiceQuotaExceededException) when calling the StartPolicyGeneration operation: Policy Generation limit exceeded: policy generation CloudTrail time range

はい、失敗しました。

原因としては、 証跡の解析に指定できる期間は最大で 90 日 だからですね。ドキュメントを読みましょう。

Set up for policy template generation – You specify a time period of up to 90 days for IAM Access Analyzer to analyze your historical AWS CloudTrail events.

ということなので、 2021/02 〜 2021/03 の 2ヶ月間に絞って解析してみます。

$ cat << EOF > cloud-trail-details.json
{
  "accessRole": "${ACCESS_ROLE_ARN}",
  "endTime": "2021-04-01T00:00:00",
  "startTime": "2021-02-01T00:00:00",
  "trails": [
    {
      "allRegions": false,
      "cloudTrailArn": "${TRAIL_ARN}",
      "regions": [
        "ap-northeast-1"
      ]
    }
  ]
}
EOF
$ JOB_ID=$( \
  aws accessanalyzer start-policy-generation \
  --policy-generation-details principalArn=${PRINCIPAL_ARN} \
  --cloud-trail-details file://cloud-trail-details.json \
  --query "jobId" \
  --output text \
) && echo "${JOB_ID}"

f4fc04f6-d4b7-4155-a722-321a19885855

無事に実行されました。
get-generated-policy サブコマンドで実行状況を確認してみます。

$ aws accessanalyzer get-generated-policy \
--job-id "${JOB_ID}"

{
    "generatedPolicyResult": {
        "properties": {
            "cloudTrailProperties": {
                "endTime": "2021-04-01T00:00:00Z",
                "startTime": "2021-02-01T00:00:00Z",
                "trailProperties": [
                    {
                        "allRegions": false,
                        "cloudTrailArn": "arn:aws:cloudtrail:ap-northeast-1:111111111111:trail/hogehoge-trail",
                        "regions": [
                            "ap-northeast-1"
                        ]
                    }
                ]
            },
            "principalArn": "arn:aws:iam::111111111111:user/hogehoge"
        }
    },
    "jobDetails": {
        "jobId": "f4fc04f6-d4b7-4155-a722-321a19885855",
        "startedOn": "2021-04-08T12:27:54Z",
        "status": "IN_PROGRESS"
    }
}

"status": "IN_PROGRESS" になってますね。

ステータスだけ確認するには下記コマンドを実行します。

$ aws accessanalyzer get-generated-policy \
--job-id "${JOB_ID}" \
--query "jobDetails.status" \
--output text

SUCCEEDED

生成された IAM ポリシーを確認する

ポリシー生成ジョブのステータスが SUCCEEDED になったら、 get-generated-policy サブコマンドの出力結果に生成された IAM ポリシーのドキュメントが含まれています。注意したいのは、生成された IAM ポリシーの情報 (ARN など) ではなく、あくまでも JSON 形式のポリシードキュメントということです。なので、出力結果を JSON ファイルに出力します。

$ aws accessanalyzer get-generated-policy \
--job-id "${JOB_ID}" \
--query "generatedPolicyResult.generatedPolicies[0].policy" \
--output text \
| jq . > generated-policy.json \
&& cat generated-policy.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SupportedServiceSid0",
      "Effect": "Allow",
      "Action": [
        "ec2:AssociateRouteTable",
        "ec2:AttachInternetGateway",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:CreateInternetGateway",
        "ec2:CreateRoute",
        "ec2:CreateRouteTable",
        "ec2:CreateSecurityGroup",
        "ec2:CreateSubnet",
        "ec2:CreateTags",
        "ec2:CreateVpc",
        "ec2:DeleteSubnet",
        "ec2:DeleteVpc",
        "ec2:DescribeAccountAttributes",
        "ec2:DescribeAddresses",
        "ec2:DescribeAvailabilityZones",
        "ec2:DescribeCarrierGateways",
        "ec2:DescribeCustomerGateways",
        "ec2:DescribeDhcpOptions",
        "ec2:DescribeEgressOnlyInternetGateways",
        "ec2:DescribeFlowLogs",
        "ec2:DescribeHosts",
        "ec2:DescribeInstanceStatus",
        "ec2:DescribeInstances",
        "ec2:DescribeInternetGateways",
        "ec2:DescribeKeyPairs",
        "ec2:DescribeLaunchTemplates",
        "ec2:DescribeManagedPrefixLists",
        "ec2:DescribeNatGateways",
        "ec2:DescribeNetworkAcls",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DescribePlacementGroups",
        "ec2:DescribeRegions",
        "ec2:DescribeRouteTables",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSnapshots",
        "ec2:DescribeStaleSecurityGroups",
        "ec2:DescribeSubnets",
        "ec2:DescribeTags",
        "ec2:DescribeVolumeAttribute",
        "ec2:DescribeVolumeStatus",
        "ec2:DescribeVolumes",
        "ec2:DescribeVolumesModifications",
        "ec2:DescribeVpcAttribute",
        "ec2:DescribeVpcEndpointServiceConfigurations",
        "ec2:DescribeVpcEndpoints",
        "ec2:DescribeVpcPeeringConnections",
        "ec2:DescribeVpcs",
        "ec2:DescribeVpnConnections",
        "ec2:DescribeVpnGateways",
        "ec2:ModifySubnetAttribute",
        "ec2:ModifyVpcAttribute",
        "ecs:CreateCluster",
        "ecs:DescribeClusters",
        "ecs:DescribeServices",
        "ecs:DescribeTaskDefinition",
        "ecs:DescribeTasks",
        "ecs:ListAccountSettings",
        "ecs:ListClusters",
        "ecs:ListContainerInstances",
        "ecs:ListServices",
        "ecs:ListTagsForResource",
        "ecs:ListTaskDefinitionFamilies",
        "ecs:ListTaskDefinitions",
        "ecs:ListTasks",
        "ecs:PutClusterCapacityProviders",
        "ecs:RegisterTaskDefinition",
        "ecs:RunTask",
        "ecs:StopTask",
        "ecs:TagResource",
        "elasticloadbalancing:DescribeLoadBalancers",
        "kms:CreateKey",
        "kms:DescribeKey",
        "kms:GenerateDataKey",
        "kms:GetKeyPolicy",
        "kms:ListAliases",
        "kms:ListKeys",
        "lambda:GetFunction",
        "lambda:GetFunctionConfiguration",
        "ram:GetResourceShareAssociations",
        "resource-groups:ListGroups",
        "s3:CreateBucket",
        "s3:GetAccountPublicAccessBlock",
        "s3:GetBucketAcl",
        "s3:GetBucketLocation",
        "s3:GetBucketPolicy",
        "s3:GetBucketPolicyStatus",
        "s3:GetBucketPublicAccessBlock",
        "s3:GetBucketVersioning",
        "s3:GetBucketWebsite",
        "s3:ListAccessPoints",
        "s3:ListAllMyBuckets",
        "ssm:DeleteParameter",
        "ssm:GetParameter",
        "ssm:GetParametersByPath",
        "ssm:PutParameter",
        "sts:GetCallerIdentity"
      ],
      "Resource": "*"
    }
  ]
}

ちょっとした感動を覚えますね。

ちなみに、マネジメントコンソールから操作する場合は生成結果からこのドキュメントを確認し、エディタで編集し、 IAM ポリシーの生成、 IAM ロールにアタッチするところまで実行することができます。

AWS CLI でそこまでやろうとすると、上記の JSON を各種エディタで編集し (必要であれば) 、 iam crate-policy で IAM ポリシーを生成し、 iam attach-policy で IAM ロールにアタッチする必要があります。この部分に関しては新しい要素がないのでここでは省略します。

まとめ

CloudTrail に記録された証跡から IAM ポリシーを生成する機能が AWS IAM Access Analyzer に追加されたので AWS CLI からその機能を使ってみた話でした。

このアップデートを見たときは IAM ポリシーまで生成してくれるものだと思っていたのですが、実際には IAM ポリシードキュメントの生成まででした。マネジメントコンソールで操作する場合は、実際に IAM ポリシーの生成から IAM ロールへのアタッチまで連続して操作できますが、 AWS CLI ではそれぞれ必要なコマンドを実行して操作する必要があります。

必要な権限を吟味した IAM ポリシーの生成はとても大変な作業なので、実際の操作の傾向に基づいた IAM ポリシードキュメントを生成してくれるのはありがたいですね。ただし、この解析結果による権限で十分かというとそうではないので ― 例えば 90 日以上のスパンで実行するような定期作業への権限が欲しい場合 おそらくこの解析では拾えないので、生成されたドキュメントを適宜編集して利用する必要がありそうです。とはいえ、ベースとなるポリシードキュメントを生成してくれるのはいいですね。

生成されたポリシードキュメントに変更を加えずに使用するような想定であれば、 AWS CLI のコマンドでシェルスクリプト作って良い感じに効率化できそうです。


  1. 実際に生成されるのは JSON 形式のポリシードキュメントで、 IAM ポリシー自体が生成されるわけではない ↩︎

以上、よっしー (michimani) でした。