Lambda で Switch ロールに対応した複数アカウントのインスタンス一覧の取得

# 概要
AWS を**複数アカウント**で運用する場合、**Switch ロールでアカウントユーザを切り替えることがベストプラクティス**になっています。
これは**マスターアカウントでユーザを一元管理**し、マスターアカウントに紐づく**子アカウントではユーザを作成せずロールの切り替え**によって権限管理ができるからです。

この方法を利用することでユーザの管理はマスターで一元管理できるので便利ですが、一方で、不用意なユーザを子アカウントに作成しないといった制限もかかります。ユーザを作成できなければ不用意なシークレットキー、アクセスキーの管理からも開放されます。

一方、プログラムから AWS リソースにアクセスする場合、一般的にシークレットキー、アクセスキーを発行することがあると思います。しかし、Switch ロールを利用する場合は**子アカウントへのユーザ作成を極力避けなければなりません**。

今回はこのような場合でもリソースにアクセスできるような **「Switch ロールに対応した複数アカウントのインスタンス一覧の取得」**の例を解説します。

# 仕組み
Lambda を利用して、引数で渡されたアカウントIDと切り替え先のロール名によって、複数アカウントの切り替えを行います。
また、切り替え先のロールには、インスタンス一覧が取得できる権限を付与しておきます。

# lambda コード
今回の Lambda コードです。Python で書いています。

```python:handler.py
import boto3
import json
from boto3.session import Session
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
 
# 引数で渡された AWS アカウント/ロールのクレデンシャルを取得
def sts_assume_role(account_id, role_name, region):
    role_arn = "arn:aws:iam::" + account_id + ":role/" + role_name
    session_name = "test"

    client = boto3.client('sts')
 
    # AssumeRole で一時クレデンシャルを取得
    response = client.assume_role(
        RoleArn=role_arn,
        RoleSessionName=session_name
    )
 
    session = Session(
        aws_access_key_id=response['Credentials']['AccessKeyId'],
        aws_secret_access_key=response['Credentials']['SecretAccessKey'],
        aws_session_token=response['Credentials']['SessionToken'],
        region_name=region
    )
 
    return session

def lambda_handler(event,context):
    print("Received event: " + json.dumps(event, indent=2))

    account_id = event.get('body')['account']
    role_name = event.get('body')['role']
    region = event.get('body')['region']
 
    # account_id = event['account']
    # role_name = event['role']
    # region = event['region']
    
    # イベントで指定されたAWSアカウント/ロールのクレデンシャルを取得
    session = sts_assume_role(account_id, role_name, region)
 
    ec2 = session.client('ec2')
 
    # 取得したクレデンシャルを使ってインスタンスリソースを取得
    instances = ec2.describe_instances(MaxResults=123)["Reservations"]
    # logger.info(instances)

    instanceList = []

    # インスタンス一覧を取得
    for i in instances:
        for j in i["Instances"]: 
            values= {
                'InstanceId':        j.get("InstanceId"),
                'Status':            j.get("State")["Name"],
                'InstanceType':      j.get("InstanceType"),
                'PrivateIpAddress':  j.get("PrivateIpAddress"),
                'SubnetId':          j.get("SubnetId"),
                'AvailabilityZone':  j.get("Placement")["AvailabilityZone"],
                'PublicIpAddress':   j.get("PublicIpAddress"),
                'ImageId':           j.get("ImageId"),
                'KeyName':           j.get("KeyName"),
                'Tags':              j.get("Tags"),
            }

            instanceList.append(values)

    return {
        'isBase64Encoded': False,
        'statusCode': 200,
        'headers': {},
        'body': instanceList
    }
```

マスターアカウント（1111111111）のロールです。
このロールは Lambda にアタッチします。
複数の AWS の子アカウントに対して lambda から Switch ロールが実行できるように権限を付与します。

```text:AWSGetResourceByLambdaReadOnlyRole
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::99999999999:role/AWSGetResourceByLambdaReadOnlyRole",
            "Effect": "Allow"
        },
        {
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::88888888888:role/AWSGetResourceByLambdaReadOnlyRole",
            "Effect": "Allow"
        },
    ]
}
```

子アカウントのロールです。
信頼関係をマスターアカウントのIDからのアクセスを許可するように付与します。
子アカウントのロールは、単にEC2のリソースを取得できる権限を付与しているだけです。

```text:信頼されたエンティティ
arn:aws:iam::1111111111:role/AWSGetResourceByLambdaReadOnlyRole
```


```text:AWSGetResourceByLambdaReadOnlyRole
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ec2:Describe*",
                "ec2:SearchTransitGatewayRoutes",
                "ec2:Get*"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}
```
# 解説
注目すべき点は下記のコードです。

```python:handler.py
def sts_assume_role(account_id, role_name, region):
    role_arn = "arn:aws:iam::" + account_id + ":role/" + role_name
    session_name = "foobar"

    client = boto3.client('sts')

    # AssumeRoleで一時クレデンシャルを取得
    response = client.assume_role(
        RoleArn=role_arn,
        RoleSessionName=session_name
    )

    session = Session(
        aws_access_key_id=response['Credentials']['AccessKeyId'],
        aws_secret_access_key=response['Credentials']['SecretAccessKey'],
        aws_session_token=response['Credentials']['SessionToken'],
        region_name=region
    )

    return session
```

引数にとったアカウントIDとロール名によって一時的な Switch ロール用のクレデンシャルを発行できます。この一時クレデンシャルを利用して、Switch ロール先の子アカウントのリソース情報を取得することができます。

あとは、シークレットキー、アクセスキーとを用いたときの AWS SDK の利用方法と同じです。必要なリソース情報を取得し、煮るなり焼くなりしましょう。

私は、これを API GateWay にデプロイし外部から API を叩けるようにし、スプレッドシートでインスタンス一覧を管理するようにしています。

# まとめ
やっぱり表計算で AWS リソースを管理したくなるのが、エンジニアのさがなのですかね。
一度スプレッドシートで管理しておけば、AWS にログインしなくても誰でもリソース情報が確認できるようになるので便利です。
特にインスタンスが何台動いているのか、無駄なインスタンスが動いていないかをすぐに把握できます。

また、インフラへの問い合わせも減って、苦労も削減できてみんなはっぴーです。
みんなでインフラの問い合わせを減らしましょうー！！