본문 바로가기
네트워크 (LAN,WAN)

AWS 환경에서 Security Group과 NACL 활용한 접근 통제 가이드

by 날으는물고기 2025. 3. 8.

AWS 환경에서 Security Group과 NACL 활용한 접근 통제 가이드

728x90

AWS 환경에서 iptables와 같은 로컬 방화벽을 직접 운영하지 않고 대신 제공되는 Security GroupNetwork ACL(NACL)을 어떻게 활용하고 점검하는지, 그리고 AWS 접근 통제 전반을 어떻게 관리하면 좋은지, 또한 새로운 클러스터를 생성할 때 공용 액세스를 기본적으로 차단하고, 필요 시 최소 권한 원칙(Least Privilege)을 적용하여 예외를 두는 방법입니다.

  1. AWS에서의 로컬 방화벽 대체 수단
  2. 보안 그룹(Security Group)의 개념, 특징, 확인 방법
  3. 네트워크 ACL(NACL)의 개념, 특징, 확인 방법
  4. Security Group vs NACL 비교
  5. 공용 접근 기본 통제 확인 방법
  6. 추가적으로 확인할 보안 설정 (IAM, Private Subnet, VPC Peering 등)
  7. 새로운 클러스터 공용 액세스 제한 안내 및 실무 적용 지침
  8. AWS 접근 통제 관리 가이드
  9. Python 스크립트를 이용한 보안 그룹 점검 및 활용 예시
  10. 운영자 / 보안 담당자를 위한 역할별 활용 방안
  11. 종합 결론 및 점검 체크리스트

AWS에서의 로컬 방화벽 대체 수단

온프레미스 환경에서 iptables와 같은 로컬 방화벽을 직접 설정·운영했다면, AWS에서는 이러한 역할을 하는 기본 보안 도구로 Security Group(EC2 인스턴스 레벨)과 Network ACL(NACL)(서브넷 레벨)이 제공됩니다.

  • Security Group: 인스턴스(EC2) 수준에서 동작하는 가상 방화벽 (Stateful)
  • NACL: 서브넷(Subnet) 수준에서 동작하는 IP 기반 필터 (Stateless)

이 둘을 적절히 조합하여 EC2 인스턴스와 서브넷 레벨에서 이중 보안을 적용하는 것이 일반적입니다.

AWS 보안 그룹(Security Group, SG)

1. 보안 그룹 개요

보안 그룹(Security Group)은 AWS 인스턴스(EC2) 수준에서 적용되는 가상 방화벽입니다.

  • 상태 저장(Stateful) 방식으로 작동하여, 인바운드에서 허용된 트래픽은 자동으로 아웃바운드에서 허용됩니다.
  • 허용(Allow) 규칙만 설정 가능하므로, 원치 않는 트래픽은 별도 Deny 규칙 없이 자동 차단됩니다.

✅ 주요 특징

  • EC2 인스턴스 단위로 적용
  • 기본적으로 모든 인바운드 트래픽 차단 → 명시적으로 허용된 트래픽만 통과
  • 아웃바운드는 기본적으로 모든 트래픽 허용 (필요 시 아웃바운드도 제한 가능)
  • 상태 저장(Stateful) → 인바운드에서 허용된 연결은 아웃바운드에서 자동 허용
  • 한 인스턴스에 최대 5개 보안 그룹 적용 가능
  • Deny 규칙 설정 불가능 (Allow만 가능)

2. 보안 그룹 확인 방법

(1) AWS CLI를 통한 확인

aws ec2 describe-security-groups --region ap-northeast-2
  • 현재 리전에 존재하는 모든 보안 그룹의 정보를 확인할 수 있습니다.
  • 인바운드, 아웃바운드 규칙, 적용 중인 인스턴스 등에 대한 상세 정보를 반환합니다.

특정 보안 그룹만 조회

aws ec2 describe-security-groups --group-ids sg-xxxxxxxx
  • 특정 보안 그룹의 세부 정보를 조회합니다.

(2) AWS 콘솔(Management Console) 확인

  1. AWS Management Console 접속
  2. EC2 서비스로 이동
  3. 좌측 메뉴에서 Security Groups 클릭
  4. 인스턴스에 적용된 보안 그룹 및 규칙을 확인

네트워크 ACL(Network ACL, NACL)

1. NACL 개요

NACL(Network ACL)서브넷(Subnet) 단위에서 적용되는 IP 기반 접근 제어 리스트(ACL)입니다.

  • 상태 비저장(Stateless)이므로, 인바운드 규칙과 아웃바운드 규칙을 각각 명시해야 합니다.
  • 허용(Allow)과 거부(Deny) 규칙을 모두 설정할 수 있습니다.

✅ 주요 특징

  • 서브넷(Subnet) 단위로 적용
  • 상태 비저장(Stateless) → 인바운드와 아웃바운드 규칙을 별도로 설정
  • 허용(Allow)과 거부(Deny) 규칙을 모두 사용 가능
  • 룰의 우선순위가 존재(Number 필드, 낮은 번호가 먼저 평가)
  • IP 단위로 세분화된 제어 가능
  • 기본적으로 VPC 생성 시 자동 생성되는 Default NACL은 모든 트래픽 허용

2. NACL 확인 방법

(1) AWS CLI

aws ec2 describe-network-acls --region ap-northeast-2
  • 현재 리전에 존재하는 모든 Network ACL을 조회합니다.

특정 NACL만 조회

aws ec2 describe-network-acls --network-acl-ids acl-xxxxxxxx
  • 특정 NACL의 룰 설정(인바운드/아웃바운드)을 확인합니다.

(2) AWS 콘솔

  1. AWS Management Console 접속
  2. VPC 서비스로 이동
  3. 좌측 메뉴에서 Network ACLs 선택
  4. 서브넷과 연결된 NACL 정보를 확인

Security Group vs NACL 차이점

비교 항목 보안 그룹 (Security Group) 네트워크 ACL (NACL)
적용 대상 EC2 인스턴스 서브넷
동작 방식 상태 저장 (Stateful) 상태 비저장 (Stateless)
허용/거부 규칙 지원 여부 허용(Allow)만 가능 허용(Allow), 거부(Deny) 모두
기본 설정 인바운드: 모든 트래픽 차단
아웃바운드: 모든 트래픽 허용
기본적으로 모든 트래픽 허용
룰 적용 순서 가장 제한적인 규칙 우선 적용 룰 번호(Number)가 낮을수록 우선
  • 보안 그룹: 인스턴스별로 세밀한 제어가 가능하며, 상태 저장(Stateful) 특성상 관리가 비교적 단순
  • NACL: 서브넷 단위에서 방어벽을 한 번 더 설정, 허용/거부를 모두 제어 가능, 우선순위 룰 기반

공용 접근 기본 통제 확인 방법

EC2 인스턴스가 퍼블릭 인터넷에 직접 노출되지 않도록 하려면, Security GroupNACL 모두에서 공용 접근 여부를 점검해야 합니다.

1. 보안 그룹에서 공용 접근 확인

(1) 특정 보안 그룹의 설정 확인

aws ec2 describe-security-groups --group-ids sg-xxxxxxxx
  • 0.0.0.0/0 또는 ::/0가 인바운드 규칙에 있다면, 외부에서 해당 포트로 접근이 가능합니다.

(2) SSH(22) 포트 공용 열림 여부 확인

aws ec2 describe-security-groups --query "SecurityGroups[*].IpPermissions[?FromPort==`22`]" --region ap-northeast-2
  • 결과에 0.0.0.0/0 또는 ::/0가 포함되어 있다면, 인터넷에서 SSH로 접근 가능 상태입니다.

(3) 특정 IP만 허용하도록 수정

aws ec2 authorize-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 22 --cidr 203.0.113.0/32
  • 203.0.113.0/32 IP만 SSH(22) 포트에 접근을 허용하는 예시입니다.

2. NACL에서 공용 접근 확인

(1) 특정 NACL 설정 확인

aws ec2 describe-network-acls --network-acl-ids acl-xxxxxxxx
  • InboundRulesOutboundRules에서 0.0.0.0/0(모든 IPv4)나 기타 불필요하게 넓은 범위를 확인

(2) 공용 접근 차단을 위한 NACL 규칙 추가

aws ec2 create-network-acl-entry --network-acl-id acl-xxxxxxxx --rule-number 100 --protocol tcp --port-range From=22,To=22 --egress false --cidr-block 0.0.0.0/0 --rule-action deny
aws ec2 create-network-acl-entry --network-acl-id acl-xxxxxxxx --rule-number 110 --protocol tcp --port-range From=3389,To=3389 --egress false --cidr-block 0.0.0.0/0 --rule-action deny
  • 위 예시는 SSH(22)와 RDP(3389)에 대한 공용 접근을 차단하는 규칙을 추가한 것입니다.
  • NACL은 룰 번호에 따라 우선순위가 결정되므로, 중복되지 않는 번호로 설정해야 합니다.

추가적으로 확인할 보안 설정

1. IAM 역할 확인

AWS 인스턴스에 직접 iptables를 구성하기보다, IAM 역할을 사용하여 EC2 인스턴스에 할당된 권한을 최소화하는 것이 일반적입니다.

aws iam list-roles
  • EC2에 부여된 IAM 역할이 필요한 권한만 갖도록(Least Privilege) 설정되었는지 확인합니다.

2. VPC Peering 및 Private Subnet 활용

  • EC2 인스턴스를 Private Subnet에 배치하고, 인터넷을 통한 직접 연결을 피하는 것이 이상적
  • VPC Peering, AWS PrivateLink 등을 통해 내부 트래픽만 허용하는 방식을 고려
  • AWS에서는 로컬 방화벽(iptables) 대신 보안 그룹(Security Group)NACL을 통해 네트워크 보안을 관리
  • 보안 그룹상태 저장(Stateful), 허용(Allow) 규칙만 가능
  • NACL상태 비저장(Stateless), 허용(Allow)·거부(Deny) 규칙 모두 가능
  • 0.0.0.0/0 또는 ::/0와 같은 광범위 허용 규칙이 있는지 주기적으로 점검
  • IAM, VPC 설계, 서브넷 구성 등을 함께 고려하여 종합적인 보안 체계를 구축

 

이로써 EC2 인스턴스나 서브넷이 공용 인터넷에 불필요하게 노출되지 않도록 보안성을 강화할 수 있습니다.

새로운 클러스터의 공용 액세스 제한 안내

새롭게 생성되는 클러스터는 기본적으로 사용자의 VPC 내에서 격리되어 인터넷에서 직접 접근 불가능하도록 구성하는 것이 바람직합니다. 만약 공용 액세스가 필요하다면 Security GroupNACL 설정을 통해 최소 권한 원칙(Least Privilege)을 적용해야 합니다. 다음은 새로운 클러스터를 포함해 AWS 환경을 구축·운영할 때 적용할 보안 지침입니다.

1. 기본 네트워크 설정

  1. 클러스터는 기본적으로 Private Subnet에 배치
  2. 퍼블릭 서브넷에 배치해야 하는 경우, 공용 액세스를 명시적으로 허용하고 필요한 트래픽만 제한적으로 허용
  3. VPC Peering, AWS PrivateLink 등을 사용해 내부 트래픽 위주로 통신

2. 보안 그룹(Security Group) 설정

  • 인바운드 트래픽 기본 차단 (명시적으로 허용한 트래픽만 통과)
  • 아웃바운드 트래픽은 기본 허용 → 필요 시 제한
  • SSH(22), RDP(3389), Kubernetes API(6443) 등은 가능한 한 공용 접근 차단
  • 외부 액세스가 꼭 필요한 경우, 특정 IP만 허용

AWS CLI 예시

aws ec2 describe-security-groups --region ap-northeast-2
aws ec2 authorize-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 22 --cidr 203.0.113.0/32
  • 위 명령어는 203.0.113.0 IP만 22번 포트(SSH)를 허용하는 예시입니다.

3. 네트워크 ACL(NACL) 설정

  • 기본적으로 인바운드 트래픽을 모두 허용하거나, 또는 최소화된 형태로 구성
  • 공용 인터넷을 통해 들어오는 SSH(22) 및 RDP(3389) 등은 명시적으로 Deny Rule 추가

AWS CLI 예시

aws ec2 create-network-acl-entry --network-acl-id acl-xxxxxxxx --rule-number 100 \
  --protocol tcp --port-range From=22,To=22 --egress false --cidr-block 0.0.0.0/0 --rule-action deny

aws ec2 create-network-acl-entry --network-acl-id acl-xxxxxxxx --rule-number 110 \
  --protocol tcp --port-range From=3389,To=3389 --egress false --cidr-block 0.0.0.0/0 --rule-action deny

4. IAM 및 인증 설정

  • EC2 인스턴스 접근은 가능하면 IAM 역할(Role)과 권한을 통해 제어
  • 개별 사용자 키 기반 인증은 최소화
  • Least Privilege 원칙 적용

IAM 역할 확인

aws iam list-roles

5. 로깅 및 모니터링

  • AWS CloudTrail 활성화 → API 호출 로깅
  • VPC Flow Logs 활성화 → 네트워크 트래픽 추적
  • Amazon GuardDuty & AWS Config 등을 통해 이상 행동 감지

6. 예외 승인 프로세스

  • 공용 액세스 필요 시, 보안팀 승인을 거쳐 예외 적용
  • 특정 기간, 특정 IP, 특정 포트만 열어두는 방식 권장
  • 정기적으로 예외 정책 재검토

정리

  1. 클러스터는 기본적으로 Private Subnet에 배치
  2. 보안 그룹과 NACL을 병행하여 공용 액세스 차단
  3. IAM 역할 및 최소 권한 원칙 준수
  4. CloudTrail, VPC Flow Logs를 통한 모니터링
  5. 공용 액세스 필요 시 보안팀 승인 후 제한적 허용

AWS 접근 통제 관리 가이드

1. AWS 접근 통제의 중요성

AWS 환경에서 잘못된 보안 그룹, NACL, IAM 권한 설정은 곧바로 보안 사고로 이어질 수 있습니다.

  • 예: SSH(22), RDP(3389)가 0.0.0.0/0로 개방되면 무차별 대입 공격(Brute Force)의 대상이 됨
  • DB 포트(3306, 5432 등)가 인터넷에 노출되면 데이터 유출 위험 증가

운영자와 보안 담당자는 주기적으로 접근 통제 정책을 검토하고 모니터링해야 합니다.

2. AWS 접근 통제 원칙

  1. 기본적으로 모든 트래픽 차단, 필요한 트래픽만 명시적으로 허용
  2. 최소 권한 원칙(Least Privilege)
  3. 보안 그룹과 NACL 병행 사용 (인스턴스와 서브넷 레벨 이중 보호)
  4. IAM을 통한 권한 세분화 (개별 사용자, 서비스별 접근 제한)
  5. CloudTrail, VPC Flow Logs, WAF 등을 통한 모니터링 및 방어

AWS 네트워크 계층별 접근 통제

1. 보안 그룹(Security Group)

  • EC2 인스턴스/클러스터 레벨에서 적용
  • Stateful
  • Allow 규칙만 지정
  • 인바운드 트래픽은 기본 차단, 허용 규칙만 통과

보안 그룹 설정 예시

aws ec2 create-security-group --group-name "secure-sg" --description "Security Group with restricted access" --vpc-id vpc-xxxxxxxx

# 특정 IP만 SSH 허용
aws ec2 authorize-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 22 --cidr 203.0.113.0/32

# HTTPS(443) 공용 허용
aws ec2 authorize-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 443 --cidr 0.0.0.0/0

2. 네트워크 ACL(NACL)

  • 서브넷(Subnet) 단위로 적용
  • Stateless
  • Allow / Deny 규칙 모두 설정 가능
  • 우선순위 기반 룰 평가 (룰 번호가 낮을수록 우선)

NACL 설정 예시

# SSH(22) 외부 차단
aws ec2 create-network-acl-entry --network-acl-id acl-xxxxxxxx --rule-number 100 \
  --protocol tcp --port-range From=22,To=22 --egress false --cidr-block 0.0.0.0/0 --rule-action deny

# RDP(3389) 외부 차단
aws ec2 create-network-acl-entry --network-acl-id acl-xxxxxxxx --rule-number 110 \
  --protocol tcp --port-range From=3389,To=3389 --egress false --cidr-block 0.0.0.0/0 --rule-action deny

IAM 기반 접근 통제

AWS IAM을 통해 사용자의 접근 권한을 세분화하고, EC2, S3, RDS 등 서비스별 최소 권한을 부여해야 합니다.

IAM 역할(Role) 생성 예시

aws iam create-role --role-name SecureEC2Role --assume-role-policy-document file://trust-policy.json

IAM 정책(Policy) 부여 예시 (S3 Read Only)

aws iam put-role-policy --role-name SecureEC2Role --policy-name S3ReadOnly --policy-document file://s3-read-policy.json

root 계정 사용을 지양하고, 다중 인증(MFA)을 활성화하며, 개별 사용자 계정을 사용해 접근을 제어합니다.

추가 보안 정책 (로깅, 모니터링, DDoS 방어 등)

1. AWS CloudTrail

  • 모든 API 호출 기록
  • EC2, RDS, IAM, VPC 등 리소스 변경 추적 가능
aws cloudtrail create-trail --name SecurityTrail --s3-bucket-name my-security-logs
aws cloudtrail start-logging --name SecurityTrail

2. AWS VPC Flow Logs

  • VPC 내부 트래픽 로깅
  • 비정상적인 IP 접근, 포트 스캔 등을 파악 가능
aws ec2 create-flow-logs --resource-type VPC --resource-id vpc-xxxxxxxx --traffic-type ALL --log-group-name VPCFlowLogs

3. AWS WAF & Shield

  • WAF: 특정 IP, 패턴 기반 차단
  • AWS Shield Advanced: DDoS 방어 강화
# WAF IP 차단 예시
aws waf create-ip-set --name "BlockedIPSet" --scope "REGIONAL" --addresses "203.0.113.0/24"

접근 통제 점검 및 정책 유지보수

  • 월 1회 이상 정기 점검 권장
  • CloudTrail, VPC Flow Logs 모니터링을 통한 이상 징후 탐지
  • AWS Config 등을 이용해 보안 그룹, NACL 변경 이력 확인
  • 비인가 접근(Unauthorized Access) 발생 시 알림(SNS, Lambda) 설정

예시: 비인가 접근 감지 알림

aws sns create-topic --name UnauthorizedAccessAlerts
aws lambda create-function --function-name UnauthorizedAccessMonitor \
  --runtime python3.8 --role arn:aws:iam::xxxxxxxx:role/lambda-role \
  --handler lambda_function.lambda_handler --code S3Bucket=my-bucket,S3Key=lambda-code.zip

AWS 환경에서 Security Group, Network ACL, IAM, 로깅/모니터링을 종합적으로 활용하여 최소 권한 원칙(Least Privilege)에 기반한 강력한 접근 통제를 수행해야 합니다.

AWS 보안 그룹 점검 자동화

AWS 내 특정 보안 그룹(Security Group)이 어떤 리소스에 연결되어 있는지, 그리고 위험한 인바운드 규칙(0.0.0.0/0, ::/0 등)이 존재하는지를 자동으로 점검합니다.

1. 주요 기능

  1. 보안 그룹 정보 조회
  2. EC2, RDS, ELB, ALB, ElastiCache, Lambda, ECS 등 리소스에서 사용 중인지 확인
  3. 보안 그룹의 인바운드 규칙 분석 (0.0.0.0/0, ::/0 같은 광범위 허용 확인)
  4. 위험 규칙 발견 시 경고

2. Python 스크립트 예시 (aws_sg_checker.py)

import boto3
import argparse
import sys

# AWS 클라이언트 초기화
ec2 = boto3.client("ec2")
rds = boto3.client("rds")
elb = boto3.client("elb")
elbv2 = boto3.client("elbv2")
elasticache = boto3.client("elasticache")
lambda_client = boto3.client("lambda")
ecs = boto3.client("ecs")

def get_security_group_id(sg_name):
    """보안 그룹 이름으로 ID 조회"""
    response = ec2.describe_security_groups(Filters=[{"Name": "group-name", "Values": [sg_name]}])
    if response["SecurityGroups"]:
        return response["SecurityGroups"][0]["GroupId"]
    return None

def check_sg_rules(sg_id):
    """보안 그룹 규칙 점검"""
    print(f"\n🔍 보안 그룹 규칙 점검: {sg_id}")
    response = ec2.describe_security_groups(GroupIds=[sg_id])

    for sg in response["SecurityGroups"]:
        for rule in sg.get("IpPermissions", []):
            from_port = rule.get("FromPort", "All")
            to_port = rule.get("ToPort", "All")
            ip_ranges = rule.get("IpRanges", [])
            ipv6_ranges = rule.get("Ipv6Ranges", [])

            for ip in ip_ranges + ipv6_ranges:
                cidr = ip.get("CidrIp") or ip.get("CidrIpv6")
                if cidr in ["0.0.0.0/0", "::/0"]:
                    print(f"⚠️ [경고] 포트 {from_port}-{to_port}이 {cidr}에 대해 개방됨 (대외 노출 가능)")

def check_usage(sg_id):
    """보안 그룹 적용 리소스 확인"""
    print(f"\n🔎 사용 중인 리소스 탐색: {sg_id}")

    # EC2
    ec2_instances = ec2.describe_instances(Filters=[{"Name": "instance.group-id", "Values": [sg_id]}])
    instances = [inst["InstanceId"] for res in ec2_instances["Reservations"] for inst in res["Instances"]]
    print(f"✅ EC2 인스턴스: {instances if instances else '없음'}")

    # RDS
    rds_instances = rds.describe_db_instances()
    rds_results = [
        db["DBInstanceIdentifier"] for db in rds_instances["DBInstances"]
        if any(sg["VpcSecurityGroupId"] == sg_id for sg in db["VpcSecurityGroups"])
    ]
    print(f"✅ RDS 인스턴스: {rds_results if rds_results else '없음'}")

    # Classic ELB
    elb_results = [
        lb["LoadBalancerName"]
        for lb in elb.describe_load_balancers()["LoadBalancerDescriptions"]
        if sg_id in lb["SecurityGroups"]
    ]
    print(f"✅ Classic ELB: {elb_results if elb_results else '없음'}")

    # ALB / NLB
    alb_results = [
        lb["LoadBalancerName"]
        for lb in elbv2.describe_load_balancers()["LoadBalancers"]
        if sg_id in lb["SecurityGroups"]
    ]
    print(f"✅ ALB / NLB: {alb_results if alb_results else '없음'}")

    # ElastiCache
    elasticache_clusters = elasticache.describe_cache_clusters()
    elasticache_results = [
        cache["CacheClusterId"]
        for cache in elasticache_clusters["CacheClusters"]
        if any(sg["SecurityGroupId"] == sg_id for sg in cache["SecurityGroups"])
    ]
    print(f"✅ ElastiCache 클러스터: {elasticache_results if elasticache_results else '없음'}")

    # Lambda
    lambda_functions = lambda_client.list_functions()["Functions"]
    lambda_results = [
        func["FunctionName"]
        for func in lambda_functions
        if "VpcConfig" in func and sg_id in func["VpcConfig"].get("SecurityGroupIds", [])
    ]
    print(f"✅ Lambda 함수: {lambda_results if lambda_results else '없음'}")

    # ECS
    ecs_clusters = ecs.list_clusters()["clusterArns"]
    ecs_results = []
    for cluster in ecs_clusters:
        ecs_instances = ecs.list_container_instances(cluster=cluster)["containerInstanceArns"]
        if ecs_instances:
            resp = ecs.describe_container_instances(cluster=cluster, containerInstances=ecs_instances)
            for inst in resp["containerInstances"]:
                if inst.get("ec2InstanceId"):
                    ecs_results.append(inst["ec2InstanceId"])
    print(f"✅ ECS 서비스(EC2 Instance ID): {ecs_results if ecs_results else '없음'}")

def main():
    parser = argparse.ArgumentParser(description="AWS 보안 그룹 사용 내역 조회 및 보안 점검 스크립트")
    parser.add_argument("-n", "--name", help="보안 그룹 이름 (예: ec2-sbs-web-test)")
    parser.add_argument("-i", "--id", help="보안 그룹 ID (예: sg-xxxxxxxx)")
    args = parser.parse_args()

    sg_id = args.id

    # 보안 그룹 이름으로 ID 조회
    if args.name and not sg_id:
        print(f"🔍 보안 그룹 이름 {args.name} 조회 중...")
        sg_id = get_security_group_id(args.name)
        if not sg_id:
            print("❌ 해당 이름의 보안 그룹이 없습니다.")
            sys.exit(1)

    if not sg_id:
        print("❌ 보안 그룹 ID 또는 이름을 입력해야 합니다.")
        sys.exit(1)

    print(f"\n📌 보안 그룹 ID: {sg_id}")
    check_sg_rules(sg_id)
    check_usage(sg_id)

if __name__ == "__main__":
    main()

3. 활용 방법

  1. 사전 준비
    • Python, boto3 라이브러리 설치
      pip install boto3
      aws configure  # AWS 자격(Access Key, Secret Key) 설정
  2. 스크립트 실행
    • 보안 그룹 이름으로 조회
      python aws_sg_checker.py -n my-security-group
    • 보안 그룹 ID로 조회
      python aws_sg_checker.py -i sg-xxxxxxxx
    • 결과 파일 저장
      python aws_sg_checker.py -i sg-xxxxxxxx > sg_report.txt
  3. 점검 결과 확인
    • 0.0.0.0/0, ::/0와 같은 외부 노출 규칙이 있는지 확인
    • 해당 보안 그룹을 어떤 리소스(EC2, RDS, ELB 등)에서 사용 중인지 파악
    • 불필요한 보안 그룹은 제거 또는 수정

운영자와 보안 담당자의 역할별 활용 방안

1. 운영자(Administrator)

  • 보안 그룹 설정최소 권한만 허용
  • SSH(22), RDP(3389) 등은 공용 서브넷에 직접 노출하지 않기
  • 사용하지 않는 보안 그룹은 즉시 삭제
  • AWS CloudTrail을 통해 보안 그룹 변경 이력 모니터링

2. 보안 담당자(Security Auditor)

  • 주기적으로 보안 그룹 점검 (스크립트 또는 AWS Config 활용)
  • 0.0.0.0/0 또는 ::/0로 열려 있는 규칙이 있는지 확인
  • IAM 정책과 연계하여 역할(Role) 권한이 과도하지 않은지 점검
  • VPC Flow Logs 등으로 포트 스캐닝, 비인가 접근 시도 모니터링

종합 결론 및 체크리스트

AWS 환경에서 Security Group과 NACL을 적절히 구성하고 IAM, CloudTrail, VPC Flow Logs 등과 연동하여 체계적인 보안 관리를 수행하면, 온프레미스에서 iptables를 운영하던 수준 이상의 보안성을 달성할 수 있습니다.

  • Public Subnet에는 필요한 서비스(예: 웹 서비스 포트)만 최소로 열고,
  • Private Subnet을 통해 내부 트래픽만 접근하도록 설계하며,
  • 주기적으로 접근 통제 설정(SG, NACL, IAM 정책)을 감사(Audit) 하여 위험 요소를 최소화해야 합니다.

자동화 점검 및 최적화 전체 코드

보안 그룹(Security Group) 최적화/점검을 위한 Python 스크립트이며, 각 함수와 로직에 대한 설명입니다.

import boto3

# AWS 서비스별 클라이언트 초기화
ec2_client = boto3.client('ec2')
elb_v2_client = boto3.client('elbv2')
elb_classic_client = boto3.client('elb')
rds_client = boto3.client('rds')
lambda_client = boto3.client('lambda')
autoscaling_client = boto3.client('autoscaling')
elasticache_client = boto3.client('elasticache')
ecs_client = boto3.client('ecs')

def collect_in_use_security_groups():
    """
    AWS 각 서비스(ENI, ALB/NLB, Classic ELB, RDS, Lambda, Auto Scaling, ElastiCache, ECS)에 연결된
    Security Group ID들을 수집하여 set 형태로 반환합니다.

    Returns:
        set: 사용(Attached) 중인 보안 그룹의 GroupId를 모두 합친 set.
    """
    in_use_sg_ids = set()

    # 1) ENI(Elastic Network Interface)
    # ENI에 연결된 보안 그룹 식별
    network_interfaces = ec2_client.describe_network_interfaces()['NetworkInterfaces']
    for eni in network_interfaces:
        for group in eni['Groups']:
            in_use_sg_ids.add(group['GroupId'])

    # 2) ALB/NLB
    # describe_load_balancers() 호출 결과의 SecurityGroups를 확인
    load_balancers = elb_v2_client.describe_load_balancers()['LoadBalancers']
    for lb in load_balancers:
        for sg_id in lb.get('SecurityGroups', []):
            in_use_sg_ids.add(sg_id)

    # 3) Classic ELB
    # Classic ELB에는 describe_load_balancers()가 다른 client(elb_classic_client)를 통해 호출됨
    classic_load_balancers = elb_classic_client.describe_load_balancers()['LoadBalancerDescriptions']
    for clb in classic_load_balancers:
        for sg_id in clb.get('SecurityGroups', []):
            in_use_sg_ids.add(sg_id)

    # 4) RDS
    # RDS 인스턴스의 VpcSecurityGroups 필드에서 사용 중인 보안 그룹을 추출
    db_instances = rds_client.describe_db_instances()['DBInstances']
    for db in db_instances:
        for vpc_sg in db.get('VpcSecurityGroups', []):
            in_use_sg_ids.add(vpc_sg['VpcSecurityGroupId'])

    # 5) Lambda
    # VPC 구성된 Lambda 함수의 보안 그룹 확인 (VpcConfig.SecurityGroupIds)
    lambda_functions = lambda_client.list_functions()['Functions']
    for func in lambda_functions:
        vpc_config = func.get('VpcConfig', {})
        sg_list = vpc_config.get('SecurityGroupIds', [])
        for sg_id in sg_list:
            in_use_sg_ids.add(sg_id)

    # 6) Auto Scaling
    # Auto Scaling Group의 Launch Configuration / Launch Template 등을 통해 설정된 SG를 확인
    asg_paginator = autoscaling_client.get_paginator('describe_auto_scaling_groups')
    for page in asg_paginator.paginate():
        asg_list = page['AutoScalingGroups']
        for asg in asg_list:
            # (1) Launch Configuration이 있는 경우
            if 'LaunchConfigurationName' in asg:
                lc_name = asg['LaunchConfigurationName']
                lc_resp = autoscaling_client.describe_launch_configurations(LaunchConfigurationNames=[lc_name])
                if lc_resp['LaunchConfigurations']:
                    lc_sg_list = lc_resp['LaunchConfigurations'][0].get('SecurityGroups', [])
                    for sg_id in lc_sg_list:
                        in_use_sg_ids.add(sg_id)

            # (2) MixedInstancesPolicy가 있고 Launch Template가 포함된 경우
            if 'MixedInstancesPolicy' in asg and 'LaunchTemplate' in asg['MixedInstancesPolicy']:
                lt_spec = asg['MixedInstancesPolicy']['LaunchTemplate']['LaunchTemplateSpecification']
                lt_id = lt_spec['LaunchTemplateId']
                lt_ver = lt_spec['Version']
                lt_resp = ec2_client.describe_launch_template_versions(LaunchTemplateId=lt_id, Versions=[lt_ver])
                lt_data = lt_resp['LaunchTemplateVersions'][0]['LaunchTemplateData']
                for sg_id in lt_data.get('SecurityGroupIds', []):
                    in_use_sg_ids.add(sg_id)

            # (3) 단일 Launch Template를 사용하는 경우
            if 'LaunchTemplate' in asg:
                lt_id = asg['LaunchTemplate']['LaunchTemplateId']
                lt_ver = asg['LaunchTemplate']['Version']
                lt_resp = ec2_client.describe_launch_template_versions(LaunchTemplateId=lt_id, Versions=[lt_ver])
                lt_data = lt_resp['LaunchTemplateVersions'][0]['LaunchTemplateData']
                for sg_id in lt_data.get('SecurityGroupIds', []):
                    in_use_sg_ids.add(sg_id)

    # 7) ElastiCache
    # ElastiCache 클러스터(ReplicationGroup가 아닌 단일 CacheCluster)에서 사용 중인 보안 그룹 확인
    try:
        cache_clusters = elasticache_client.describe_cache_clusters()['CacheClusters']
        for cluster in cache_clusters:
            for sg in cluster.get('SecurityGroups', []):
                in_use_sg_ids.add(sg['SecurityGroupId'])
    except Exception:
        # IAM 권한이 없는 경우 예외 발생 가능
        pass

    # 8) ECS
    # ECS의 서비스 중, 네트워크 모드가 awsvpc인 경우 securityGroups 사용
    try:
        cluster_arns = ecs_client.list_clusters()['clusterArns']
        for cluster_arn in cluster_arns:
            service_arns = ecs_client.list_services(cluster=cluster_arn)['serviceArns']
            if not service_arns:
                continue
            svc_desc = ecs_client.describe_services(cluster=cluster_arn, services=service_arns)
            services = svc_desc.get('services', [])
            for svc in services:
                if 'networkConfiguration' in svc:
                    awsvpc_config = svc['networkConfiguration'].get('awsvpcConfiguration', {})
                    for sg_id in awsvpc_config.get('securityGroups', []):
                        in_use_sg_ids.add(sg_id)
    except Exception:
        # 권한이 없거나, ECS를 사용하지 않는 계정일 경우 예외
        pass

    return in_use_sg_ids

def main():
    """
    메인 함수:
      1. 모든 보안 그룹 목록을 조회
      2. 실제 사용 중인 보안 그룹 수집
      3. 미사용(Unattached) 보안 그룹 식별 및 출력
      4. (선택) 보안 그룹 규칙을 확인하여 위험도(0.0.0.0/0, ::/0 오픈 등) 분석
    """
    # 1) 모든 보안 그룹 목록 가져오기
    all_sg_response = ec2_client.describe_security_groups()
    all_security_groups = all_sg_response['SecurityGroups']
    all_sg_ids = {sg['GroupId'] for sg in all_security_groups}

    # 2) 실제 사용 중인 보안 그룹 식별
    in_use_sg_ids = collect_in_use_security_groups()

    # 3) 미사용 보안 그룹 식별
    unused_sg_ids = all_sg_ids - in_use_sg_ids

    # 4) 미사용 보안 그룹에 대한 정보 출력
    print("[미사용(Unattached) 보안 그룹 목록]")
    for sg_id in unused_sg_ids:
        sg_info = next((item for item in all_security_groups if item['GroupId'] == sg_id), None)
        if sg_info:
            sg_name = sg_info['GroupName']
            print(f"  - GroupId: {sg_id}, GroupName: {sg_name}")

    # 5) (선택) 보안 그룹 규칙 위험도 점검
    print("\n[보안 그룹 규칙 위험도 점검: 0.0.0.0/0, ::/0]")
    for sg_info in all_security_groups:
        group_id = sg_info['GroupId']
        group_name = sg_info['GroupName']
        for ip_permission in sg_info.get('IpPermissions', []):
            from_port = ip_permission.get('FromPort', 'All')
            to_port = ip_permission.get('ToPort', 'All')
            ip_ranges = ip_permission.get('IpRanges', []) + ip_permission.get('Ipv6Ranges', [])
            for ip_range in ip_ranges:
                cidr = ip_range.get('CidrIp') or ip_range.get('CidrIpv6')
                if cidr in ['0.0.0.0/0', '::/0']:
                    print(f"  ⚠️ 위험 규칙 감지 → SG: {group_id}({group_name}), "
                          f"포트: {from_port}-{to_port}, CIDR: {cidr}")

if __name__ == "__main__":
    main()

A. 전역 클라이언트 설정

ec2_client = boto3.client('ec2')
elb_v2_client = boto3.client('elbv2')
elb_classic_client = boto3.client('elb')
rds_client = boto3.client('rds')
lambda_client = boto3.client('lambda')
autoscaling_client = boto3.client('autoscaling')
elasticache_client = boto3.client('elasticache')
ecs_client = boto3.client('ecs')
  • AWS SDK(Boto3)를 통해 각 서비스별로 API 호출이 가능하도록 클라이언트를 생성합니다.
  • 예: ec2_client → VPC, Security Group, ENI 등 EC2 관련 리소스 관리에 사용

B. collect_in_use_security_groups() 함수

def collect_in_use_security_groups():
    # ...
    return in_use_sg_ids
  1. in_use_sg_ids: 현재 사용(Attached) 중인 보안 그룹의 GroupId를 담는 set입니다.
  2. ENI(Elastic Network Interface)
    network_interfaces = ec2_client.describe_network_interfaces()['NetworkInterfaces']
    for eni in network_interfaces:
        for group in eni['Groups']:
            in_use_sg_ids.add(group['GroupId'])
    • EC2 인스턴스, Load Balancer 등의 내부 요소인 네트워크 인터페이스에 연결된 SG를 확인합니다.
    • 보안 그룹은 ENI 단위로도 적용되므로, 여기서도 꼭 체크해야 놓치지 않습니다.
  3. ALB / NLB (엘라스틱 로드 밸런서 v2)
    load_balancers = elb_v2_client.describe_load_balancers()['LoadBalancers']
    for lb in load_balancers:
        for sg_id in lb.get('SecurityGroups', []):
            in_use_sg_ids.add(sg_id)
    • ALB/NLB에 연결된 SG를 SecurityGroups 필드에서 추출합니다.
  4. Classic ELB
    classic_load_balancers = elb_classic_client.describe_load_balancers()['LoadBalancerDescriptions']
    for clb in classic_load_balancers:
        for sg_id in clb.get('SecurityGroups', []):
            in_use_sg_ids.add(sg_id)
    • 클래식 ELB(API: elb)에도 SecurityGroups 항목이 있으며, 이를 확인합니다.
  5. RDS
    db_instances = rds_client.describe_db_instances()['DBInstances']
    for db in db_instances:
        for vpc_sg in db.get('VpcSecurityGroups', []):
            in_use_sg_ids.add(vpc_sg['VpcSecurityGroupId'])
    • RDS(DB) 인스턴스에는 VpcSecurityGroups가 있으며, SG ID를 수집합니다.
  6. Lambda
    lambda_functions = lambda_client.list_functions()['Functions']
    for func in lambda_functions:
        vpc_config = func.get('VpcConfig', {})
        sg_list = vpc_config.get('SecurityGroupIds', [])
        for sg_id in sg_list:
            in_use_sg_ids.add(sg_id)
    • VPC에 연결된 Lambda 함수는 VpcConfig 내에 SecurityGroupIds가 설정됩니다.
  7. Auto Scaling
    • Launch Configuration / Launch Template / Mixed Instances Policy에 속한 보안 그룹 식별
    • Auto Scaling 그룹에서 EC2를 실행할 때 사용되는 SG가 여기에 포함됨
      asg_paginator = autoscaling_client.get_paginator('describe_auto_scaling_groups')
      for page in asg_paginator.paginate():
        for asg in page['AutoScalingGroups']:
            # LaunchConfigurationName, MixedInstancesPolicy, LaunchTemplate를 하나씩 확인
  8. ElastiCache(선택)
    cache_clusters = elasticache_client.describe_cache_clusters()['CacheClusters']
    for cluster in cache_clusters:
        for sg in cluster.get('SecurityGroups', []):
            in_use_sg_ids.add(sg['SecurityGroupId'])
    • ElastiCache 클러스터에서 VPC 모드 시에 SG가 존재합니다.
  9. ECS(선택)
    cluster_arns = ecs_client.list_clusters()['clusterArns']
    for cluster_arn in cluster_arns:
        service_arns = ecs_client.list_services(cluster=cluster_arn)['serviceArns']
        # ...
        awsvpc_config = svc['networkConfiguration'].get('awsvpcConfiguration', {})
        for sg_id in awsvpc_config.get('securityGroups', []):
            in_use_sg_ids.add(sg_id)
    • ECS 서비스 중 awsvpc 모드에서는 보안 그룹을 서비스 수준에서 연결합니다.

모든 리소스에서 수집된 SG ID들은 중복 방지를 위해 set(in_use_sg_ids)에 추가됩니다.

C. 메인 로직 main()

  1. 전체 보안 그룹 목록 조회
    all_sg_response = ec2_client.describe_security_groups()
    all_security_groups = all_sg_response['SecurityGroups']
    all_sg_ids = {sg['GroupId'] for sg in all_security_groups}
    • 현존하는 모든 보안 그룹을 가져오고, ID만 추려서 all_sg_ids 집합을 만듭니다.
  2. 실제 사용 중인 보안 그룹 식별
    in_use_sg_ids = collect_in_use_security_groups()
    • 앞서 정의한 함수로 각 리소스에 연결되어 있는 SG를 모두 수집합니다.
  3. 미사용 보안 그룹(Unattached SG) 추출
    unused_sg_ids = all_sg_ids - in_use_sg_ids
    • 차집합 연산을 통해 어떤 리소스에도 연결되지 않은 SG를 찾습니다.
  4. 미사용 SG 출력
    for sg_id in unused_sg_ids:
        sg_info = next((item for item in all_security_groups if item['GroupId'] == sg_id), None)
        if sg_info:
            sg_name = sg_info['GroupName']
            print(f"  - GroupId: {sg_id}, GroupName: {sg_name}")
    • SG 이름, ID 등 정보를 표시하고, 필요하면 삭제를 진행할 수 있습니다(코드 예시상 출력만 함).
  5. 보안 그룹 규칙 위험도 점검
    for sg_info in all_security_groups:
        for ip_permission in sg_info.get('IpPermissions', []):
            # 0.0.0.0/0 또는 ::/0 허용 여부 확인
            ip_ranges = ip_permission.get('IpRanges', []) + ip_permission.get('Ipv6Ranges', [])
            ...
    • FromPort, ToPort, CidrIp(또는 CidrIpv6) 중 0.0.0.0/0, ::/0가 있으면 경고 출력

활용 포인트

  1. 미사용 보안 그룹 정리
    • 매번 코드 실행 시, 사용되지 않는(Unattached) SG를 빠르게 식별 → 삭제하여 관리 효율화
    • 정체 모를 SG가 남아있는 경우, 의도치 않은 보안 취약점이 될 수 있으므로 적극 정리
  2. 사용 중인 보안 그룹(Attached) 확인
    • EC2, RDS, ALB, Lambda, Auto Scaling, ElastiCache, ECS 등 다양한 경로를 통해 SG가 사용됨
    • “EC2만 확인하면 되겠지”라고 생각하다가 놓치는 부분이 많으므로 본 스크립트를 통해 누락 없이 확인
  3. 0.0.0.0/0, ::/0 광범위 허용
    • SSH(22)나 RDP(3389), DB 포트(3306, 5432 등)가 공용(IP 전역)에 열려있으면 Brute Force 등 보안 위협이 커짐
    • 본 스크립트 마지막 부분에서 해당 규칙이 있는 보안 그룹을 경고해 주므로, 즉시 설정 수정 가능
  4. 자동화 & 통합 모니터링
    • 스크립트를 주기적으로 실행 → 결과를 Slack, Email, SNS 등으로 알림
    • CloudWatch Event, Lambda로 스케줄링하여 정기 보안 감사 체계 구축

필요에 따라 특정 서비스를 제외하거나, 또는 더 많은 서비스(EFS, MSK 등)를 추가해 보안 그룹을 더 폭넓게 추적할 수도 있습니다.

최종 체크리스트

  1. Security Group
    • 0.0.0.0/0 또는 ::/0 인바운드 허용 규칙이 있는가?
    • SSH(22), RDP(3389), DB 포트 등이 과도하게 공개되어 있는가?
    • 불필요한 보안 그룹은 없는가?
  2. Network ACL
    • 서브넷 단위로 공용 인터넷 접근이 허용되어 있는가?
    • Deny 규칙 우선순위가 올바르게 설정되어 있는가?
  3. IAM
    • EC2, Lambda, RDS, S3 등에 필요한 권한만 부여했는가?
    • root 계정 사용 금지, MFA 활성화, 개별 사용자 계정 사용 여부?
  4. 로깅·모니터링
    • AWS CloudTrail이 활성화되어 있는가?
    • VPC Flow Logs로 네트워크 트래픽을 모니터링하고 있는가?
    • GuardDuty, AWS Config, WAF 등 보안 서비스와 연계되어 있는가?
  5. 정기 감사 프로세스
    • 월 1회 이상 점검 진행 여부
    • 접근 통제 정책 변경 시 보안팀 승인 프로세스 적용 여부

위 가이드를 통해 다음을 모두 달성할 수 있습니다.

  1. AWS에서 로컬 방화벽(iptables) 대신 Security Group과 NACL로 보안 관리
  2. 공용 액세스(퍼블릭 접근)를 최소화하는 방법
  3. IAM, CloudTrail, VPC Flow Logs 등과 연계하여 종합적인 보안 운영 체계 구축
  4. Python 스크립트를 활용한 보안 그룹 자동 점검으로 운영·보안 효율성 강화
  5. 운영자와 보안 담당자가 협업하여 주기적 점검, 설정 개선, 예외 승인 프로세스를 통해 보안 수준을 지속적으로 높일 수 있음

이 모든 과정을 통해 AWS 클라우드 환경에서 발생할 수 있는 네트워크 및 접근 통제 관련 보안 위험을 최대한 줄이고, 안전하고 효율적인 클라우드 운영이 가능합니다. 필요한 경우 위 체크리스트와 스크립트를 활용하여 정기적인 보안 감사를 수행하고, 최신 보안 모범 사례(AWS Best Practices)에 부합하도록 시스템을 지속적으로 개선해 나가시기 바랍니다.

728x90

댓글