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






