はじめに
運用アカウントからメンバーアカウントの開発・ステージング環境のEC2/RDSを自動起動停止するために、Lambda関数を作成しました。
今回の記事では、運用アカウントからメンバーアカウントのEC2/RDS情報を取得して、SQSにメッセージを送ります。
概要
①OUから開発/ステージング環境のアカウントを取得
②アカウントのEC2/RDS情報を取得
③SQSにメッセージを送る
構成図
前提条件
- セキュリティアカウントでconfin委任済み
- SQS作成済み
以下のIAMロール作成済み
- Lambda実行ロール
管理アカウント、セキュリティアカウントにスイッチするための権限 - 管理アカウントにスイッチするためのロール
Organizationsからアカウント情報を取得する権限 - セキュリティアカウントにスイッチするためのロール
Config実行権限
構築
- 環境変数
- OU_ID
- 組織単位 (OU) ID
- MANAGEMENT_ID
- 管理アカウントID
- SECURITY_ID
- セキュリティアカウントID
- AGGREGATOR_NAME
- Configアグリゲータ名
- SQS_URL
- SQSのURL
- OU_ID
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."
}