보안 그룹으로 애플리케이션 접근 제어하기

AWSBeginner
지금 연습하기

소개

배송 애플리케이션의 퍼블릭 경로는 이미 작동하지만, 제공된 접근 규칙은 모든 IPv4 소스의 HTTP를 허용합니다. 애플리케이션에 별도의 보안 그룹을 지정해 의도한 클라이언트와 포트만 허용합니다. 실제 요청을 통해 소스 제한, 포트 제한, 상태 저장 응답이 접근에 미치는 영향을 확인합니다.

먼저 “퍼블릭 서브넷을 인터넷에 연결하기”를 완료하세요. 이 새 환경은 자체 네트워크, 퍼블릭 주소, 경로와 애플리케이션을 제공하며 이전 VM을 재사용하지 않습니다. CLI는 이미 구성되어 있습니다. 제공된 리소스와 연습과 관계없는 참조 네트워크를 보존하세요. 삭제할 연습 리소스는 새로 만드는 보안 그룹뿐입니다.

자격증 연관성

이 실습은 다음 시험 주제의 실무 연습을 제공합니다.

빈 연습용 보안 그룹 연결하기

이 단계에서는 작동 중인 애플리케이션을 확인하고 광범위한 접근 그룹을 자신의 빈 그룹으로 교체합니다.

명령은 Terminal에서 실행하고 옆의 AWS View를 클릭하세요. 뷰는 CLI와 같은 리소스 상태를 읽습니다. application-network, 퍼블릭·프라이빗 서브넷, 프라이빗 주소 10.20.1.10의 애플리케이션이 표시됩니다. 퍼블릭 주소와 인터넷 게이트웨이 경로는 이미 제공되므로 변경하지 마세요.

cd /home/labex/project

Name 태그로 VPC를 선택합니다. --filters는 서버 결과를 제한하고, --query는 ID를 선택하며, $(...)는 결과를 셸 변수에 저장합니다.

VPC_ID=$(aws ec2 describe-vpcs \
  --filters Name=tag:Name,Values=application-network \
  --query 'Vpcs[0].VpcId' \
  --output text)

해당 VPC에서 제공된 애플리케이션 인터페이스를 선택합니다.

ENI_ID=$(aws ec2 describe-network-interfaces \
  --filters "Name=vpc-id,Values=$VPC_ID" Name=tag:Name,Values=application-interface \
  --query 'NetworkInterfaces[0].NetworkInterfaceId' \
  --output text)

보안 그룹은 연결된 리소스의 네트워크 인터페이스에서 허용할 트래픽을 제어합니다. 인바운드 규칙은 애플리케이션으로 도착하는 트래픽을, 아웃바운드 규칙은 애플리케이션이 시작하는 트래픽을 허용합니다. 정리할 때 복원할 수 있도록 유일한 제공 그룹의 ID를 저장합니다. [0]은 그룹 하나로 구성된 목록의 첫 항목을 선택합니다.

SUPPLIED_GROUP_ID=$(aws ec2 describe-network-interfaces \
  --network-interface-ids "$ENI_ID" \
  --query 'NetworkInterfaces[0].Groups[0].GroupId' \
  --output text)

규칙을 변경하지 않고 읽습니다.

aws ec2 describe-security-groups \
  --group-ids "$SUPPLIED_GROUP_ID" \
  --query 'SecurityGroups[].{ID:GroupId,Inbound:IpPermissions,Outbound:IpPermissionsEgress}' \
  --output json

제공된 그룹은 0.0.0.0/0, 즉 모든 IPv4 소스에서 오는 인바운드 TCP 포트 80과 모든 아웃바운드 트래픽을 허용합니다. HTTP는 요청과 응답을 사용하고, 포트는 수신 서비스를 식별합니다. 애플리케이션은 TCP 포트 80과 8081에서 HTTP를 제공하지만 제공 규칙은 80만 허용합니다.

AWS View에서 Request application · client A, 이어서 Request application · client B를 클릭합니다. 둘 다 포트 80에서 Success와 Application online을 반환합니다. A는 198.51.100.10, B는 198.51.100.20을 사용합니다. Request port 8081을 클릭하면 해당 포트가 허용되지 않아 A의 요청이 실패합니다.

같은 VPC에 별도 그룹을 만듭니다. --group-name은 이름을, --description은 목적을 지정합니다. 따옴표로 묶은 태그 명세는 소유권 태그를 하나의 인수로 추가합니다. 새 ID를 저장합니다.

GROUP_ID=$(aws ec2 create-security-group \
  --group-name parcel-web \
  --description "Parcel HTTP access" \
  --vpc-id "$VPC_ID" \
  --tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=parcel-web},{Key=Project,Value=parcel}]' \
  --query 'GroupId' \
  --output text)

새 그룹에는 인바운드 허용이 없고 모든 아웃바운드 트래픽은 허용됩니다. 보안 그룹에는 허용 규칙이 있으며 명시적인 거부 규칙은 없습니다. 일치하는 허용 규칙이 없는 트래픽은 차단됩니다.

--groups는 인터페이스의 그룹 목록을 교체합니다. 새 그룹만 연결하세요. 광범위한 그룹도 남기면 두 그룹의 권한이 합쳐져 두 클라이언트 모두 계속 허용됩니다.

aws ec2 modify-network-interface-attribute \
  --network-interface-id "$ENI_ID" \
  --groups "$GROUP_ID"

변경 성공 시 출력은 없습니다. 인터페이스에 자신의 그룹만 연결되었는지 확인합니다.

aws ec2 describe-network-interfaces \
  --network-interface-ids "$ENI_ID" \
  --query 'NetworkInterfaces[].{ID:NetworkInterfaceId,Groups:Groups}' \
  --output json

AWS View에 parcel-web과 Inbound · none이 표시됩니다. A와 B에서 새 요청을 보내면 둘 다 Connection failed가 됩니다. 퍼블릭 주소와 경로는 남아 있지만 빈 그룹은 인바운드 요청을 허용하지 않습니다. 저장한 ID를 유지하도록 이 Terminal을 열어 두세요.

의도한 HTTP 클라이언트만 허용하기

이 단계에서는 클라이언트 A에 포트 80 접근을 허용하고 다른 소스와 포트는 계속 차단되는지 확인합니다.

인바운드 규칙은 프로토콜, 포트 범위, 소스를 지정합니다. --protocol tcp는 TCP를, --port 80은 단일 포트를, --cidr 198.51.100.10/32는 A의 IPv4 주소만 선택합니다. /32는 IPv4 주소 하나를 포함하므로 0.0.0.0/0보다 범위가 좁습니다.

제공 그룹이 아닌 자신의 그룹에 규칙을 추가합니다.

aws ec2 authorize-security-group-ingress \
  --group-id "$GROUP_ID" \
  --protocol tcp \
  --port 80 \
  --cidr 198.51.100.10/32

응답은 성공을 나타내며 새 규칙 ID가 포함될 수 있습니다. 전체 인바운드 규칙을 읽습니다.

aws ec2 describe-security-groups \
  --group-ids "$GROUP_ID" \
  --query 'SecurityGroups[].IpPermissions' \
  --output json

TCP 규칙 하나가 있으며 FromPort와 ToPort는 모두 80, 소스는 198.51.100.10/32입니다. AWS View에도 같은 인바운드 소스와 포트가 표시됩니다.

Request application · client A를 클릭합니다. 결과는 Success, 소스 198.51.100.10, 대상 포트 80, 본문 Application online입니다. 이는 제공 애플리케이션의 실제 응답입니다.

이제 Request application · client B와 Request port 8081을 클릭합니다. 둘 다 Connection failed를 반환합니다. 첫 요청은 소스가 다르고, 둘째는 A를 사용하지만 포트가 다릅니다. 퍼블릭 주소와 경로가 통신 경로를 제공하고 보안 그룹은 그 경로를 이용할 트래픽을 결정합니다.

임시 포트 허용을 테스트하고 제거하기

이 단계에서는 두 번째 수신 포트가 자체 규칙이 있을 때만 접근 가능함을 확인한 뒤 임시 허용을 제거합니다.

제공 애플리케이션은 포트 8081에서도 HTTP를 제공합니다. 허용 소스는 유지하고 이 포트의 임시 규칙을 추가합니다.

aws ec2 authorize-security-group-ingress \
  --group-id "$GROUP_ID" \
  --protocol tcp \
  --port 8081 \
  --cidr 198.51.100.10/32

규칙을 다시 읽습니다.

aws ec2 describe-security-groups \
  --group-ids "$GROUP_ID" \
  --query 'SecurityGroups[].IpPermissions' \
  --output json

이제 TCP 포트 80과 8081의 두 규칙이 있으며 둘 다 A로 제한됩니다. AWS View에 두 규칙이 표시됩니다. Request port 8081을 클릭하면 Success, 대상 포트 8081, 본문 Application online으로 바뀝니다. 두 번째 서비스가 실행 중이며 앞선 실패는 접근 규칙의 제한 때문임을 확인할 수 있습니다.

임시 TCP 8081 규칙이 클라이언트 A의 실제 요청을 허용함

AWS View 예시: 두 개의 좁은 인바운드 규칙이 있고 A가 포트 8081에서 애플리케이션 응답을 받습니다. 생성되는 리소스 ID는 환경마다 다릅니다.

연습에는 포트 80만 필요합니다. 규칙의 취소는 해당 허용을 제거합니다. 동일한 프로토콜, 포트, 소스를 지정해 임시 규칙만 제거합니다.

aws ec2 revoke-security-group-ingress \
  --group-id "$GROUP_ID" \
  --protocol tcp \
  --port 8081 \
  --cidr 198.51.100.10/32

응답은 성공을 나타냅니다. 변경 후 규칙을 조회합니다.

aws ec2 describe-security-groups \
  --group-ids "$GROUP_ID" \
  --query 'SecurityGroups[].IpPermissions' \
  --output json

A의 포트 80 규칙만 남습니다. AWS View에서 8081 규칙이 사라지면 Request port 8081을 다시 클릭하세요. 이제 실패합니다. A의 80은 계속 성공하고 B의 80은 계속 실패합니다. 애플리케이션이나 경로를 교체하지 않고 한 포트 허용만 변경했습니다.

상태 저장 HTTP 응답 관찰하기

이 단계에서는 자신의 그룹에서 기본 아웃바운드 규칙을 제거하고 허용된 인바운드 HTTP에 대한 응답이 여전히 작동하는지 확인합니다.

보안 그룹은 상태를 저장합니다. 허용된 인바운드 요청에 대한 응답은 새 연결을 허용하는 아웃바운드 규칙이 없어도 애플리케이션에서 나갈 수 있습니다. 아웃바운드 규칙은 애플리케이션이 시작하는 트래픽을 제어하며 이 HTTP 응답을 허용하는 데 필요하지 않습니다.

현재 아웃바운드 권한을 읽습니다.

aws ec2 describe-security-groups \
  --group-ids "$GROUP_ID" \
  --query 'SecurityGroups[].IpPermissionsEgress' \
  --output json

새 그룹에는 모든 트래픽의 기본 대상 0.0.0.0/0이 있습니다. 다음 명령의 --ip-permissions는 규칙 명세의 JSON 목록을 따옴표로 묶은 하나의 인수로 받습니다. IpProtocol 값 -1은 모든 프로토콜이며 IpRanges는 아웃바운드 규칙의 대상을 나타냅니다. 이 기본 규칙을 정확히 제거합니다.

aws ec2 revoke-security-group-egress \
  --group-id "$GROUP_ID" \
  --ip-permissions '[{"IpProtocol":"-1","IpRanges":[{"CidrIp":"0.0.0.0/0"}]}]'

응답은 성공을 나타냅니다. 양방향 규칙을 읽습니다.

aws ec2 describe-security-groups \
  --group-ids "$GROUP_ID" \
  --query 'SecurityGroups[].{Inbound:IpPermissions,Outbound:IpPermissionsEgress}' \
  --output json

인바운드는 여전히 A의 포트 80만 허용하고 아웃바운드는 비어 있습니다. AWS View는 Outbound · none을 표시합니다. Request application · client A를 다시 클릭하면 Success와 Application online이 반환됩니다. 응답이 허용된 인바운드 연결에 속하기 때문입니다.

Request application · client B와 Request port 8081을 다시 확인합니다. 둘 다 계속 실패합니다. 상태 저장 응답은 다른 소스나 포트에 새로운 인바운드 접근을 부여하지 않습니다. 이 테스트는 응답 동작을 확인하며, 애플리케이션이 시작하는 새 아웃바운드 연결을 테스트하지는 않습니다.

아웃바운드 허용 없이도 허용된 인바운드 HTTP 요청에 응답이 반환됨

AWS View 예시: 인바운드는 A의 포트 80만 허용하고 아웃바운드는 비어 있지만 실제 HTTP 응답은 성공합니다. 생성되는 리소스 ID는 환경마다 다릅니다.

제공 그룹을 복원하고 자신의 그룹 삭제하기

이 단계에서는 애플리케이션의 원래 연결을 복원하고 자신의 연습용 보안 그룹만 삭제합니다.

네트워크 인터페이스에 연결된 그룹은 삭제할 수 없습니다. 먼저 자신의 그룹을 저장해 둔 제공 그룹으로 교체하세요. 제공 그룹을 삭제하거나 수정하지 마세요.

aws ec2 modify-network-interface-attribute \
  --network-interface-id "$ENI_ID" \
  --groups "$SUPPLIED_GROUP_ID"

연결을 확인합니다.

aws ec2 describe-network-interfaces \
  --network-interface-ids "$ENI_ID" \
  --query 'NetworkInterfaces[].{ID:NetworkInterfaceId,Groups:Groups}' \
  --output json

원래의 supplied-application 그룹만 연결되어 있습니다. 이제 사용하지 않는 자신의 그룹을 삭제합니다.

aws ec2 delete-security-group --group-id "$GROUP_ID"

삭제 성공 시 출력은 없습니다. 제거할 수 있는 태그와 무관하게 리소스가 없음을 확인하도록 전체 그룹 목록을 조회합니다.

aws ec2 describe-security-groups \
  --query 'SecurityGroups[].{ID:GroupId,Name:GroupName,VPC:VpcId}' \
  --output table

parcel-web이 없습니다. 제공 그룹과 기본 그룹은 물론 VPC, 서브넷, 애플리케이션 인터페이스, 퍼블릭 주소와 경로도 남아 있습니다. 목록 조회 실패는 삭제 증거가 아닙니다.

AWS View에 다시 supplied-application이 표시됩니다. A와 B 요청 버튼을 클릭하면 두 포트 80 요청 모두 초기 상태처럼 Success를 반환합니다. 변경하지 않은 제공 그룹은 80만 허용하므로 Request port 8081은 실패합니다.

이 단계의 완료 검사를 실행하세요.

요약

별도의 보안 그룹을 만들고 연결해 A의 TCP 포트 80만 허용했습니다. 두 번째 포트의 임시 허용을 테스트하고 취소했으며 아웃바운드 허용 없이도 상태 저장 HTTP 응답이 작동함을 확인했습니다. 실제 요청으로 허용·차단 트래픽을 구분하고, 마지막으로 제공 그룹을 복원한 뒤 자신의 연습 그룹만 삭제했습니다.

“프라이빗 서브넷에 아웃바운드 접근 제공하기”를 이어서 학습해 요청하지 않은 외부 접근을 허용하지 않고 프라이빗 애플리케이션의 아웃바운드 경로를 구성하세요.