IAMユーザに直接割り当てられたポリシーをグループに移すPythonスクリプト

IAM

こんにちは、臼田です。

皆さん、AWS SDKを活用していますか?

今日はちょっとニッチなケースですが、IAMユーザに直接割り当てられたポリシーをIAMグループに移すスクリプトを書いたので、共有したいと思います。

IAMユーザに直接ポリシーを当ててはいけない

そもそもなぜこれを行わないといけないのか、ということですが

IAMのベストプラクティスに書かれています。

IAM ユーザーへのアクセス権限を割り当てるためにグループを使用する

個々の IAM ユーザーの権限を定義する代わりに、ジョブ機能 (管理者、開発者、監査など) に関連するグループを作成する方が便利です。次に、各グループに関連のあるアクセス権限を定義します。最後に、IAM ユーザーをそれらのグループに割り当てます。IAM グループのすべてのユーザーに、グループに割り当てられたアクセス許可が継承されます。このようにすれば、グループ全体の変更を一度でできるのです。お客様の会社内で社員の異動がある場合は、単に IAM ユーザーが所属する IAM グループを変更すればよいだけです。

本来は、グループを活用することによって後から発生する諸々の変更作業を便利に行うために、直接当てないことが推奨されています。

今回は本末転倒ですが、既に直接ポリシーを当ててしまっている状況で、とりあえず上記を満たすためにグループを作成してそちらにポリシーを移します。

もう少し言うと、このような行為を行う段階でアカウントの権限周りの棚卸しをすべきなので、このスクリプトをもし活用される方がいらっしゃったら、実行後に一通り見直して下さい。

重複した内容を確認し、同じ要素の権限は一つのグループにまとめましょう。

スクリプト共有

前提・仕様

  • ポリシーが直接アタッチされているユーザのlist(json)がある
  • ユーザーリストから直接アタッチされているポリシー情報を取得する(get_users_iam_policies.py)
  • ポリシー情報を適用したグループを作成する(create_iam_groups.py)
  • 作成されたグループをユーザに適用する(set_groups2users.py)
  • python 2.7

3つのスクリプトを作成したので説明していきます。

ポリシー情報の取得

前提のユーザーリストはこんな感じです。

# iam_users.json
["test_user01", "test_user02"]

スクリプトは下記の通り

# get_users_iam_policies.py
# -*- coding: utf-8 -*-

import sys
import json
import boto3
from collections import OrderedDict

# get user list file
f_users = sys.argv[1]
with open(f_users, 'r') as f:
	users = json.load(f)

iam = boto3.resource('iam')

# get user policies from iam
l_iam = []
for user in users:
	j_user = OrderedDict()
	user_iam = iam.User(user)
	managed_policies = user_iam.attached_policies.all()
	inline_policies = user_iam.policies.all()
	
	j_user['name'] = user
	j_user['managed_policies'] = []
	j_user['inline_policies'] = []

	for mp in managed_policies:
		mp_doc = OrderedDict()
		mp_doc['PolicyName'] = mp.policy_name
		mp_doc['PolicyArn'] = mp.arn
		j_user['managed_policies'].append(mp_doc)

	for ip in inline_policies:
		ip_doc = OrderedDict()
		ip_doc['UserName'] = user
		ip_doc['PolicyName'] = ip.policy_name
		ip_doc['PolicyDocument'] = ip.policy_document
		j_user['inline_policies'].append(ip_doc)

	l_iam.append(j_user)

print json.dumps(l_iam)

実行するとこうなります

$ python get_users_iam_policies.py iam_users.json | jq . > policies.json
$ cat policies.json
[
  {
    "name": "test_user01",
    "managed_policies": [
      {
        "PolicyName": "AmazonEC2ReadOnlyAccess",
        "PolicyArn": "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
      }
    ],
    "inline_policies": []
  },
  {
    "name": "test_user02",
    "managed_policies": [],
    "inline_policies": [
      {
        "UserName": "test_user02",
        "PolicyName": "AmazonS3AccessAllow",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": "s3:*",
              "Resource": [
                "arn:aws:s3:::<BUCKET-NAME>",
                "arn:aws:s3:::<BUCKET-NAME>/*"
              ],
              "Effect": "Allow"
            },
            {
              "NotAction": "s3:*",
              "NotResource": [
                "arn:aws:s3:::<BUCKET-NAME>",
                "arn:aws:s3:::<BUCKET-NAME>/*"
              ],
              "Effect": "Deny"
            }
          ]
        }
      }
    ]
  }
]

各ユーザから管理ポリシー(managed_policies)とインラインポリシー(inline_policies)が取得できていることが確認できます。

boto3では、管理ポリシーの操作についてはattached_policyとして、インラインポリシーについてはpolicyとして基本的には扱います。

名前が通常利用するものと違いややこしいですが、IAMでは管理ポリシーとインラインポリシーの2種類が存在するため、この両方について処理してあげる必要があります。

また、アタッチする際には管理ポリシーではPolicy ARN、インラインポリシーではPolicy Documentが必要になるのでそれぞれ取得します。

jsonの可読性のためOrderDictを利用してjsonに出力する際の順序を保っています。

グループの作成

作成されたポリシーの内容に基づいてグループを作成します。

#create_iam_groups.py
# -*- coding: utf-8 -*-

import sys
import json
import boto3


# load policy file
f_name = sys.argv[1]
with open(f_name, 'r') as f:
	users = json.load(f)

iam = boto3.resource('iam')

for user in users:
	# create group
	ig_name = user['name'] + '-group'
	group = iam.create_group(GroupName = ig_name)
	print group

	# atache managed policies
	for mp in user['managed_policies']:
		r = group.attach_policy(PolicyArn = mp['PolicyArn'])

	# add inline policies
	for ip in user['inline_policies']:
		p_name = ip['PolicyName']
		p_doc = json.dumps(ip['PolicyDocument'], indent=4)
		r = group.create_policy(
			PolicyName = p_name,
			PolicyDocument = p_doc
		)

実行すると下記のようになります。

$ python create_iam_groups.py policies.json
iam.Group(name=u'test_user01-group')
iam.Group(name=u'test_user02-group')

一応iam.Groupが返ってきていることをprintしていますがあまり意味はありません。

命名規則として、「<ユーザ名>-group」という名前でグループを作成しています。

インラインポリシー作成時には、indentを設定してあげないとマネジメントコンソールからも確認しづらくなってしまいますので整形して出してあげます。

ここでも2つのポリシーは扱いが違い、管理ポリシーはattach_policyとなり、インラインポリシーはcreate_policyとなります。

グループをユーザに割り当て

割り当てるスクリプトは下記のとおりです。

# set_groups2users.py
# -*- coding: utf-8 -*-

import sys
import json
import boto3


# load policy file
f_name = sys.argv[1]
with open(f_name, 'r') as f:
	users = json.load(f)

iam = boto3.resource('iam')

for user in users:
	# group
	ig_name = user + '-group'
	group = iam.Group(ig_name)
	print group
	r = group.add_user(UserName = user)

実行すると下記のようになります。

$ python set_groups2users.py iam_users.json
iam.Group(name=u'test_user01-group')
iam.Group(name=u'test_user02-group')

今回もiam.Groupをprintしていますがおまけです。

これは一番単純で、add_userしているのみとなります。

なお、マネジメントコンソールで一度ユーザを開いた後にこのスクリプトを実行し、ユーザの表示を更新するとグループが割り当たっていないように表示する問題が私の環境では発生しました。

実際には表示上だけの問題だったようなので、一度アカウントを切り替えたりしたら治りました。

見ている一つの画面だけが正しい情報ではないことに気をつける必要がありそうですね。

仕上げ

最後にユーザに直接アタッチしているポリシーを外す作業がありますが、こちらは敢えてスクリプトは作成していません。

実際のユーザの動作に影響がありますので、手動で確認しましょう。

最後に

あんまりよく使うようなものではありませんが、せっかく書いたのと、iamをboto3から扱う際の参考にでもなればと思い公開しました。

マネジメントコンソール上で利用している名前と、awscliやAWS SDKで利用する名前が違っていたり、操作感が異なっていたりすることは結構あるので、スクリプトを書いたらなるべくアウトプットしていきたいです。