msawady’s engineering-note

なにも分からないエンジニアです。

【AWS】【Python】Lambdaを利用して他のAWSアカウントのEC2インスタンスを再起動する

Lambdaを利用した他アカウントの操作

やったことの流れは以下の通り。

  • IAMロール/ポリシーの設定、信頼関係の編集
  • 他のアカウントのIAMロールにSTSしてEC2クライアントを取得
  • tagの値で対象のインスタンスを抽出し、再起動

IAMロール/ポリシーの設定

Lambdaにアタッチするロールと、他のアカウント(操作対象のインスタンスを保持しているアカウント)側のIAMロールの2つを編集する必要があります。

LambdaのIAMロール/ポリシー

今回のケースで必要な権限(ポリシー)は以下の2つです。

  • AWSLambdaExecute (built-in):
    Lambda実行のための基本的な権限です。具体的には、cloudwatch logs へのフルアクセス、S3へのPut/Get

  • 他のアカウントのIAMロールへのSTS権限:
    他のアカウントのIAMロールへの一時的な認証情報を取得(STS)してインスタンスのコントロールを行うために、Lambda側には「他のアカウントのIAMロールへのSTSを行う権限」を与える必要が有ります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "arn:aws:iam::999999999999:role/OtherAccountRole"
            ]
        }
    ]
}

他のアカウント側のIAMロール/ポリシー

他のアカウント(操作対象のインスタンスを保持しているアカウント)では以下のポリシーを持ったIAMロールを作成します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:RebootInstances"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

また、このIAMロールの信頼済みエンティティに、上記のLambdaロールを追加する必要が有ります。IAM画面から信頼関係の編集を行い、以下のように編集します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:role/RebootLambdaRole"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Lambda のコード、テストイベント

Python3.6で書きます。

ソースコード

'''
AWS Lambda script for reboot instances.
This scripts need STS for IAM Role on Another Account.
'''

import boto3


def lambda_handler(event, context):
    sts_client = boto3.client('sts')
    sts_res = sts_client.assume_role(
        RoleArn=event['sts_target'],
        RoleSessionName='RebootOtherAccount'
    )
    cred = sts_res['Credentials']

    ec2_client = boto3.client(
        'ec2',
        aws_access_key_id=cred['AccessKeyId'],
        aws_secret_access_key=cred['SecretAccessKey'],
        aws_session_token=cred['SessionToken'],
    )

    full_info = ec2_client.describe_instances(
        Filters=[
            {
                'Name': 'tag:need_reboot',
                'Values': ['true']
            }
        ]
    )
    
    instance_ids = []
    for r in full_info['Reservations']:
        for i in r['Instances']:
            instance_ids.append(i['InstanceId'])

    return ec2_client.reboot_instances(
        DryRun=event.get('dry_run', True),
        InstanceIds=instance_ids,
    )

テストイベント

{
  "dry_run": true,
  "sts_target": "arn:aws:iam::999999999999:role/OtherAccountRole"
}

解説と補足

  • テストイベントから取得した他アカウントのIAMロールへのSTSを行い、取得した認証情報を利用してEC2クライアントを作成します。STSを利用して他アカウントのリソースにアクセスする場合は、clientの初期化時に以下のようにする必要があります。
    cred = sts_res['Credentials']

    ec2_client = boto3.client(
        'ec2',
        aws_access_key_id=cred['AccessKeyId'],
        aws_secret_access_key=cred['SecretAccessKey'],
        aws_session_token=cred['SessionToken'],
    )
  • 対象となるインスタンスをtagの値でフィルターします。ここではneed_reboot というタグにtrueと書いてあるインスタンスを抽出しています。
full_info = ec2_client.describe_instances(
        Filters=[
            {
                'Name': 'tag:need_reboot',
                'Values': ['true']
            }
        ]
    )
  • 細かいですが、DryRunフラグはちゃんと付けておきたいですね。
    return ec2_client.reboot_instances(
        DryRun=event.get('dry_run', True),
        InstanceIds=instance_ids,
    )

終わりに

以上です。IAMロールの設定のところで結構ハマりましたが、IAMロール/ポリシー、信頼関係などについて学ぶことが出来ました。 業務ではこんな感じのLambdaを書くことが増えたので、ブログの方にもメモを残していきたいと思います。