0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

新しいブランチが作成されたら、既存のIAMポリシーを自動更新するLambdaを書いてみた

Posted at

はじめに

所定のレポジトリ内に、ブランチが作成されるたび、特定のポリシーの権限を自動更新するLambdaを書いてみました。
結局この内容は使用されなかったのですが、せっかく書いたので、記事にします。

やりたいこととしては
以下の記事を参考にして、所定のブランチに対してアクセス制限をかけるIAMポリシーを作成する

新しいブランチが作成されるたびに、そのブランチ名を上記で作成したポリシーに追加する

です。

目次

  1. フローの説明
  2. コードの解説
  3. 終わりに

フローの説明

今回の流れは以下となります。
※うち➁~⑥がLambdaの処理
①新しいブランチの作成がトリガーとなり、Lambdaにeventが渡される
➁渡されたeventから必要情報を抽出
③更新対象のポリシーの必要情報を抽出
④新しいVersionのポリシーを、既存ポリシーをコピーして作成
⑤新しく作成されたブランチ名を、更新対象のポリシーに反映
⑥最も古いVersionのポリシーを削除

コードの解説

以下が実際のコードになります。
この後詳細を説明していきます。

import boto3
import json
import copy

def get_new_policy_document(old_document, branch_name):
    new_document = copy.deepcopy(old_document)
    new_document["Statement"][0]["Condition"]["StringEquals"]["codecommit:References"].append(f'refs/heads/{branch_name}')
    return(new_document)

def lambda_handler(event, context):
    # CodeCommitからのイベント情報を取得
    record = event['Records'][0]
    repository_name = record['eventSourceARN'].split(':')[5]
    ref = event['Records'][0]['codecommit']['references'][0]['ref']
    branch_name = ref.split('/')[-1]
    print(f"New branch {branch_name} was created in {repository_name} repository.")
    
    # IAMポリシーの更新

    iam = boto3.client('iam')
    policy_arn = 'arn:aws:iam::133285731447:policy/MusakaTestForDevelopmentMember'
    response = iam.list_policy_versions(PolicyArn=policy_arn)
    latest_version = response['Versions'][0]['VersionId']
    policy = iam.get_policy_version(PolicyArn=policy_arn, VersionId=latest_version)
    
    policy_version = iam.create_policy_version(
        PolicyArn=policy_arn,
        PolicyDocument=json.dumps(get_new_policy_document(policy["PolicyVersion"]["Document"], branch_name)),
        SetAsDefault=True
    )
    iam.delete_policy_version(
        PolicyArn=policy_arn, 
        VersionId=response['Versions'][-1]['VersionId']
        )

    print(f"IAM policy {policy_arn} was updated with new policy version {policy_version['PolicyVersion']['VersionId']}.")

ではフローの順番に説明します。

新しいブランチの作成がトリガーとなり、Lambdaにeventが渡される

コンソール上からLambdaにアクセスし、「トリガーを追加」を選択します。
その後、CodeCommitを選択し、必要な項目を埋めていきます。
今回は「ブランチまたはタグを作成する」がリッスンするイベントになります。
image.png

渡されたeventから必要情報を抽出

ここまでで、ブランチが作成されるたびにeventがLambdaに渡されるフローが完成しました。
ここから、Lambdaの処理について説明していきます。
ゴールとしては、渡されたeventからレポジトリ名とブランチ名を取得して、以下をプリントすることです。

"New branch {branch_name} was created in {repository_name} repository.
import boto3
import json
import copy

def get_new_policy_document(old_document, branch_name):
    new_document = copy.deepcopy(old_document)
    new_document["Statement"][0]["Condition"]["StringEquals"]["codecommit:References"].append(f'refs/heads/{branch_name}')
    return(new_document)

def lambda_handler(event, context):
    # CodeCommitからのイベント情報を取得
    record = event['Records'][0]
    repository_name = record['eventSourceARN'].split(':')[5]
    ref = event['Records'][0]['codecommit']['references'][0]['ref']
    branch_name = ref.split('/')[-1]
    print(f"New branch {branch_name} was created in {repository_name} repository.")

・必要なモジュールのインポート
今回は以下のモジュールが必要なのでインポートします。

import boto3
import json
import copy

※コードの順番的に、def get_new_policy_document(old_document, branch_name):関数がありますが、こちらの説明は後回しにします。

・渡されたeventから必要な情報の抽出
では渡されたeventから、レポジトリ名とブランチ名を取得していきます。
そもそもeventがどんな形で渡されるかというと、こんな感じのJSONで渡されます。
※以下はあくまで一例で、実際の値ではありません。こちらはLambdaのテストで以下を選択することで確認可能です。

image.png

{
  "Records": [
    {
      "awsRegion": "us-east-1",
      "codecommit": {
        "references": [
          {
            "commit": "5c4ef1049f1d27deadbeeff313e0730018be182b",
            "ref": "refs/heads/master"
          }
        ]
      },
      "customData": "this is custom data",
      "eventId": "5a824061-17ca-46a9-bbf9-114edeadbeef",
      "eventName": "TriggerEventTest",
      "eventPartNumber": 1,
      "eventSource": "aws:codecommit",
      "eventSourceARN": "arn:aws:codecommit:us-east-1:123456789012:my-repo",
      "eventTime": "2016-01-01T23:59:59.000+0000",
      "eventTotalParts": 1,
      "eventTriggerConfigId": "5a824061-17ca-46a9-bbf9-114edeadbeef",
      "eventTriggerName": "my-trigger",
      "eventVersion": "1.0",
      "userIdentityARN": "arn:aws:iam::123456789012:root"
    }
  ]
}

そのためには、以下のコードが必要になります。

    record = event['Records'][0]
    repository_name = record['eventSourceARN'].split(':')[5]
    ref = event['Records'][0]['codecommit']['references'][0]['ref']
    branch_name = ref.split('/')[-1]

JSONで必要な値にアクセスするには順番に下っていけばよいので
①「Event」の中の「Records」配列の0番目を変数recordに格納
➁「Records配列」0番目の中の「eventSourceARN」を:で割った5番目を変数repository_nameに格納
③「Records配列」0番目の中の「codecommit」の中の「reference配列」の0番目の「ref」を変数refに格納
④refに格納されたrefs/heads/masterの中の後ろから一つ目

という順番で地道に必要な情報にアクセスしています。
はい、ここまで問題なければ以下がプリントされることになります。

"New branch {branch_name} was created in {repository_name} repository.

更新対象のポリシーの必要情報を抽出

次は以下のコードを用いて、更新対象のポリシーの情報を取得していきます。

iam = boto3.client('iam')
    policy_arn = 'arn:aws:iam::133285731447:policy/MusakaTestForDevelopmentMember'
    response = iam.list_policy_versions(PolicyArn=policy_arn)
    latest_version = response['Versions'][0]['VersionId']
    policy = iam.get_policy_version(PolicyArn=policy_arn, VersionId=latest_version)

まずiamという変数にboto3.client('iam')で取得した値を格納しています。
そもそもboto3ってなに?という説明ですと、要するにPythonを使用してAWSリソースを使用することができるSDKの一種となります。

例えばS3サービスのアクションを実行したい場合は、以下のように利用することが出来ます。

import boto3

s3 = boto3.client('s3')

# S3クライアント上の 'list_buckets' メソッドを呼び出す
response = s3.list_buckets()

# バケットの一覧を表示
print(response['Buckets'])

今回必要なアクションはIAMですので、boto3.client('IAM')をIAMという変数に代入することを最初に実施しているわけです。
以下公式ドキュメントです。

このコードでは
①更新ポリシーのARNを指定
➁list_policy_versionsで対象ポリシーのversionをリストアップ
③取得されたポリシーのversionのうち最も新しいものを変数latest_versionに格納
④get_policy_versionで更新ポリシー最新versionの情報を取得
という流れの処理が書かれています。

➁番以降を説明します。

以下を見ると、必要な引数を確認することができます。

以下がレスポンスとして返ってきますので、その中から必要情報を変数latest_versionに格納します。
そもそもlatest_versionが必要だった理由ですが、次のget_policy_versionの引数としてVersionIdが必要だったからです。

{
    'Versions': [
        {
            'Document': 'string',
            'VersionId': 'string',
            'IsDefaultVersion': True|False,
            'CreateDate': datetime(2015, 1, 1)
        },
    ],
    'IsTruncated': True|False,
    'Marker': 'string'
}

この後、get_policy_versionでARNで指定したポリシーの最新Versionの詳細を取得します。
以下は取得できる例です。

この後中にある「Document」に対して更新をかけるので、ここで更新対象のポリシーの情報をまとめて取得しています。

{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "RestrictBranches",
                    "Effect": "Deny",
                    "Action": [
                        "codecommit:GitPush",
                        "codecommit:DeleteBranch",
                        "codecommit:PutFile",
                        "codecommit:DeleteFile",
                        "codecommit:MergeBranchesByFastForward",
                        "codecommit:MergeBranchesBySquash",
                        "codecommit:MergeBranchesByThreeWay",
                        "codecommit:MergePullRequestByFastForward",
                        "codecommit:MergePullRequestBySquash",
                        "codecommit:MergePullRequestByThreeWay"
                    ],
                    "Resource": "arn:aws:codecommit:*:*:test",
                    "Condition": {
                        "StringEqualsIfExists": {
                            "codecommit:References": [
                                "refs/heads/develop"
                            ]
                        },
                        "Null": {
                            "codecommit:References": False
                        }
                    }
                }
            ]
        },
        "VersionId": "v5",
        "IsDefaultVersion": true,
        "CreateDate": "2023-03-10T06:41:18.000000+00:00"
    },
    "ResponseMetadata": {
        "RequestId": "d097edca-8b6e-4c66-9fff-6883d15d9a47",
        "HTTPStatusCode": 200,
        "HTTPHeaders": {
            "x-amzn-requestid": "d097edca-8b6e-4c66-9fff-6883d15d9a47",
            "content-type": "text/xml",
            "content-length": "2673",
            "date": "Sun, 12 Mar 2023 06:16:26 GMT"
        },
        "RetryAttempts": 0
    }
}

新しいVersionのポリシーを、既存ポリシーをコピーして作成

ここでは、一つ上のセクションで取得したポリシーをコピーして、さらにその中の「Document」に変更を加えていきます。


def get_new_policy_document(old_document, branch_name):
    new_document = copy.deepcopy(old_document)
    new_document["Statement"][0]["Condition"]["StringEquals"]["codecommit:References"].append(f'refs/heads/{branch_name}')
    return(new_document)

 policy_version = iam.create_policy_version(
        PolicyArn=policy_arn,
        PolicyDocument=json.dumps(get_new_policy_document(policy["PolicyVersion"]["Document"], branch_name)),
        SetAsDefault=True
    )

まずは関数の解説から
引数としては、old_documentとbranch_nameを受け取ります。

①受け取ったold_documentをコピーし、new_documentという変数に格納
➁new_document内のdocumentに引数として渡されたbranch_nameを追加
③new_documentを返す

以上で関数の解説は終了です。

新しく作成されたブランチ名を、更新対象のポリシーに反映

上記で解説した関数の処理を、create_policy_versionの中で実施します。
create_policy_versionの詳細は以下より確認できます。

必要な引数としては以下になります。

response = client.create_policy_version(
    PolicyArn='string',
    PolicyDocument='string',
    SetAsDefault=True|False
)

そのためPolicyDocumentの指定で、 get_new_policy_document(old_document, branch_name)関数で取得したnew_documentをjson形式に変換しています。
補足すると、この関数の中で引数として渡しているのは、
①policy = version = response['Versions'][0]['VersionId']
policy = iam.get_policy_version(PolicyArn=policy_arn, VersionId=latest_version)

➁branch_name = ref.split('/')[-1]

になります。
はい、これで新しいブランチが作成されるたび、そのブランチ名をポリシーに自動追加する
が実装できました。

ただしこれではIAMポリシーのversionが無限に増え続けてしまうので、以下の記述で最も古いIAMポリシーを削除します。

    iam.delete_policy_version(
        PolicyArn=policy_arn, 
        VersionId=response['Versions'][-1]['VersionId']
        )

以上コードの解説を終了します。

終わりに

頑張ってコードを書いたのですが、所定のブランチに対してアクセス権限を管理したいのであれば
①PowerUserポリシーをアタッチ
➁アクセスを制限したい箇所に対して、denyをかける

で実装できたので、なんというか空回りしてしまった感じがあります。
とはいえ、Boto3のよい勉強になったので結果的にはよかったかと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?