CloudTrail + Lambda를 활용하여 EC2 공인 IP 할당을 감지하고 통지해 봤습니다.
안녕하세요 클래스메소드 김재욱(Kim Jaewook) 입니다. 이번에는 CloudTrail + Lambda를 활용하여 EC2 공인 IP 할당을 감지하고 통지해 봤습니다.
IAM 정책 및 역할 생성
먼저 Lambda 함수에 EC2, SNS, CloudWatch에 대한 권한과 EC2 인스턴스, 네트워크 인터페이스를 검사하기 위해 [ec2:DescribeInstances]. [ec2:DescribeNetworkInterfaces]에 대한 권한을 할당합니다.
[arn:aws:sns:ap-northeast-2:xxxxxxxxxx]에는 SNS 토픽 Arn을 입력합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeNetworkInterfaces"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"sns:Publish"
],
"Resource": "arn:aws:sns:ap-northeast-2:xxxxxxxxxx"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
Lambda 함수 코드 작성
Lambda 함수의 런타임은 [Python 3.13]을 지정합니다.
[arn:aws:sns:ap-northeast-2:xxxxxxxxxx]에는 SNS 토픽 Arn을 입력합니다.
Lambda 함수는 AssociateAddress 또는 ModifyNetworkInterfaceAttribute 이벤트 발생 시 EC2 인스턴스에 퍼블릭 IP가 할당된 것을 감지하고 SNS로 경고를 전송합니다. 처음 EC2 인스턴스를 생성할 때 Public IP를 할당하거나 EIP 생성만으로 이메일을 전송하는 것은 아닙니다. EIP를 생성하고 EC2 인스턴스에 EIP를 할당하면 이메일이 전송됩니다.
※ 이번 블로그의 주 목적은 Private한 EC2 인스턴스에 공인 IP가 악의적으로 할당되었을 가능성을 고려한 구성입니다.
import json
import boto3
ec2 = boto3.client('ec2')
sns = boto3.client('sns')
SNS_TOPIC_ARN = 'arn:aws:sns:ap-northeast-2:xxxxxxxxxx'
def lambda_handler(event, context):
print("Received event:", json.dumps(event))
# 이벤트 정보 추출
detail = event.get('detail', {})
instance_id = detail.get('requestParameters', {}).get('instanceId')
eni_id = detail.get('requestParameters', {}).get('networkInterfaceId')
public_ip = detail.get('requestParameters', {}).get('publicIp')
if not instance_id and not eni_id:
print("No instanceId or networkInterfaceId in event")
return {'statusCode': 400, 'body': 'Missing required parameters'}
message_lines = []
if instance_id:
response = ec2.describe_instances(InstanceIds=[instance_id])
for reservation in response['Reservations']:
for instance in reservation['Instances']:
for iface in instance['NetworkInterfaces']:
if iface.get('Association', {}).get('PublicIp'):
message_lines.append(
f"Instance {instance_id} has public IP: {iface['Association']['PublicIp']}"
)
if eni_id:
response = ec2.describe_network_interfaces(NetworkInterfaceIds=[eni_id])
for iface in response['NetworkInterfaces']:
if iface.get('Association', {}).get('PublicIp'):
message_lines.append(
f"ENI {eni_id} has public IP: {iface['Association']['PublicIp']}"
)
if message_lines:
message = "\n".join(message_lines)
print("ALERT:", message)
sns.publish(
TopicArn=SNS_TOPIC_ARN,
Subject="⚠️ Public IP Attached to EC2/ENI",
Message=message
)
else:
print("No public IP associated")
return {
'statusCode': 200,
'body': 'Inspection complete'
}
EventBridge 규칙 생성
Lambda 함수를 생성했다면, 이제 EventBridge 규칙을 생성합시다.
이벤트 트리거 조건으로는 [AssociateAddress]와 [ModifyNetworkInterfaceAttribute]입니다.
{
"source": ["aws.ec2"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventName": ["AssociateAddress", "ModifyNetworkInterfaceAttribute"]
}
}
결과 확인
테스트를 위해 먼저 EIP를 생성합니다. 먼저 두 개의 EIP를 생성하는데, 하나의 EIP는 EC2 인스턴스에 직접 할당합니다.
[test-ec2-1]은 EC2 인스턴스 생성 시 Public IP 자동 할당을 했고, [test-ec2-2]는 조금 전에 생성한 EIP 중 하나를 할당했습니다.
이메일을 확인해 보면, [test-ec2-2]에 Public IP가 할당되었다고 이메일이 온 것을 확인할 수 있습니다.
문의 사항은 클래스메소드 코리아로!
클래스메소드 코리아에서는 다양한 세미나 및 이벤트를 진행하고 있습니다.
진행중인 이벤트는 아래 페이지를 참고해주세요.
AWS에 대한 상담 및 클래스 메소드 멤버스에 관한 문의사항은 아래 메일로 연락주시면 감사드립니다!
Info@classmethod.kr