1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Lambda】運用アカウントからメンバーアカウントのEC2/RDSを自動起動停止①

Posted at

はじめに

運用アカウントからメンバーアカウントの開発・ステージング環境のEC2/RDSを自動起動停止するために、Lambda関数を作成しました。

今回の記事では、運用アカウントからメンバーアカウントのEC2/RDS情報を取得して、SQSにメッセージを送ります。

概要

①OUから開発/ステージング環境のアカウントを取得
②アカウントのEC2/RDS情報を取得
③SQSにメッセージを送る

構成図

(雑な構成図ですみません、、、)
GetAccount.png

前提条件

  • セキュリティアカウントでconfin委任済み
  • SQS作成済み

以下のIAMロール作成済み

  • Lambda実行ロール
    管理アカウント、セキュリティアカウントにスイッチするための権限
  • 管理アカウントにスイッチするためのロール
    Organizationsからアカウント情報を取得する権限
  • セキュリティアカウントにスイッチするためのロール
    Config実行権限

構築

  • 環境変数
    • OU_ID
      • 組織単位 (OU) ID
    • MANAGEMENT_ID
      • 管理アカウントID
    • SECURITY_ID
      • セキュリティアカウントID
    • AGGREGATOR_NAME
      • Configアグリゲータ名
    • SQS_URL
      • SQSのURL
get-account.py
import boto3
import json
import os
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def assume_role(account_id):
    try:
        sts = boto3.client("sts")
        role_name = os.environ["SWITCH_ROLE"]
        role_arn = f"arn:aws:iam::{account_id}:role/{role_name}"
        resp = sts.assume_role(
            RoleArn=role_arn,
            RoleSessionName="CrossAccountSession"
        )
        creds = resp["Credentials"]
        return boto3.Session(
            aws_access_key_id=creds["AccessKeyId"],
            aws_secret_access_key=creds["SecretAccessKey"],
            aws_session_token=creds["SessionToken"]
        )
    except Exception as e:
        logger.error(f"Error assuming role for account {account_id}: {e}")
        raise

def get_accounts_in_ou(org_client, ou_id):
    try:
        accounts = []
        next_token = None

        while True:
            params = {"ParentId": ou_id}
            if next_token:
                params["NextToken"] = next_token

            response = org_client.list_accounts_for_parent(**params)
            accounts.extend(response.get("Accounts", []))
            next_token = response.get("NextToken")
            if not next_token:
                break

        return accounts
    except Exception as e:
        logger.error(f"Error getting accounts in OU {ou_id}: {e}")
        raise

def get_account_tags(org_client, account_id):
    try:
        response = org_client.list_tags_for_resource(ResourceId=account_id)
        return response.get("Tags", [])
    except Exception as e:
        logger.error(f"Error getting tags for account {account_id}: {e}")
        raise

def run_config_queries(config_client, account_id, aggregator_name):
    queries = {
        "EC2": f"""
            SELECT
                accountId,
                awsRegion,
                resourceId,
                configuration.instanceType,
                tags
            WHERE
                resourceType = 'AWS::EC2::Instance'
                AND accountId = '{account_id}'
        """,
        "RDS": f"""
            SELECT
                accountId,
                awsRegion,
                resourceId,
                configuration.dbInstanceIdentifier,
                configuration.engine,
                tags
            WHERE
                resourceType = 'AWS::RDS::DBInstance'
                AND accountId = '{account_id}'
        """,
        "Aurora": f"""
            SELECT
                accountId,
                awsRegion,
                resourceId,
                configuration.dbClusterIdentifier,
                configuration.engine,
                tags
            WHERE
                resourceType = 'AWS::RDS::DBCluster'
                AND accountId = '{account_id}'
        """
    }

    try:
        all_resources = {}
        for key, query in queries.items():
            results = []
            next_token = None
            while True:
                params = {
                    "ConfigurationAggregatorName": aggregator_name,
                    "Expression": query
                }
                if next_token:
                    params["NextToken"] = next_token

                response = config_client.select_aggregate_resource_config(**params)
                results.extend(response.get("Results", []))
                next_token = response.get("NextToken")
                if not next_token:
                    break

            all_resources[key] = [json.loads(resource_json) for resource_json in results]

        return all_resources
    except Exception as e:
        logger.error(f"Error running Config queries: {e}")
        raise

def send_resource_to_sqs(sqs_client, queue_url, resource):
    try:
        sqs_client.send_message(
            QueueUrl=queue_url,
            MessageBody=json.dumps(resource)
        )
    except Exception as e:
        logger.error(f"Error sending resource to SQS: {e}")
        raise

def send_all_resources_to_sqs(sqs_client, queue_url, account_id, resources):
    try:
        for ec2 in resources["EC2"]:
            send_resource_to_sqs(sqs_client, queue_url, {
                "AccountId": account_id,
                "Region": ec2.get("awsRegion"),
                "InstanceId": ec2.get("resourceId"),
                "Tags": ec2.get("tags", []),
                "ResourceType": "EC2"
            })

        for rds in resources["RDS"]:
            send_resource_to_sqs(sqs_client, queue_url, {
                "AccountId": account_id,
                "Region": rds.get("awsRegion"),
                "DBInstanceIdentifier": rds.get("configuration", {}).get("dbInstanceIdentifier"),
                "Engine": rds.get("configuration", {}).get("engine"),
                "Tags": rds.get("tags", []),
                "ResourceType": "RDS"
            })

        for aurora in resources["Aurora"]:
            send_resource_to_sqs(sqs_client, queue_url, {
                "AccountId": account_id,
                "Region": aurora.get("awsRegion"),
                "DBClusterIdentifier": aurora.get("configuration", {}).get("dbClusterIdentifier"),
                "Engine": aurora.get("configuration", {}).get("engine"),
                "Tags": aurora.get("tags", []),
                "ResourceType": "Aurora"
        })
    except Exception as e:
        logger.error(f"Error sending resources to SQS: {e}")
        raise

def lambda_handler(event, context):
    try:
        ou_id         = os.environ["OU_ID"]
        management_id = os.environ["MANAGEMENT_ID"]
        security_id   = os.environ["SECURITY_ID"]
        aggre_name    = os.environ["AGGREGATOR_NAME"]
        sqs_url       = os.environ["SQS_URL"]

        sqs_client = boto3.client("sqs")

        # 各アカウントに接続
        org_session = assume_role(management_id)
        config_session = assume_role(security_id)

        # OrganizationsとConfigのクライアントを作成
        org_client = org_session.client("organizations", region_name="us-east-1")
        config_client = config_session.client("config")

        # アカウントをOUから取得
        accounts = get_accounts_in_ou(org_client, ou_id)

        # SQSに送信するためのリソースを取得
        for account in accounts:
            account_id = account["Id"]
            tags = get_account_tags(org_client, account_id)

            # "Env"タグが"dev"または"stg"であるアカウントのみを対象
            env_tag = None
            for tag in tags:
                if tag.get("Key") == "Env" and tag.get("Value") in ["dev", "stg"]:
                    env_tag = tag
                    break

            if not env_tag:
                continue

            # Configの高度なクエリを実行
            resources = run_config_queries(config_client, account_id, aggre_name)

            # SQSにリソースを送信
            send_all_resources_to_sqs(sqs_client, sqs_url, account_id, resources)

    except Exception as e:
        return {
            "status": "error",
            "error": str(e),
            "message": "An error occurred while processing the request."
        }

    return {
        "status": "success",
        "message": "Resources successfully processed and sent to SQS."
    }

実行結果

SQSにメッセージが送信されていることを確認
スクリーンショット 2025-07-30 18.28.07.png

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?