본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 28. · 4 Views
AWS 보안 그룹 참조로 계층별 보안 구성 완벽 가이드
AWS 보안 그룹 참조를 활용하여 3티어 아키텍처의 각 계층을 안전하게 연결하는 방법을 알아봅니다. IP 주소 대신 보안 그룹 ID를 참조하여 유연하고 관리하기 쉬운 네트워크 보안을 구현하는 실무 패턴을 다룹니다.
목차
1. 보안 그룹 참조의 개념과 장점
어느 날 김개발 씨가 AWS 콘솔에서 보안 그룹 설정을 하다가 이상한 것을 발견했습니다. 인바운드 규칙의 소스 부분에 IP 주소가 아닌 "sg-0a1b2c3d4e5f"라는 이상한 문자열이 들어있었습니다.
"이게 뭐지? IP 주소도 아닌데..."
보안 그룹 참조란 인바운드 또는 아웃바운드 규칙에서 IP 주소 대신 다른 보안 그룹의 ID를 소스나 대상으로 지정하는 방식입니다. 마치 "신분증을 가진 사람만 입장 가능"이라고 규칙을 정하는 것과 같습니다.
이 방식을 사용하면 IP 주소가 바뀌어도 보안 규칙을 수정할 필요가 없어집니다.
다음 코드를 살펴봅시다.
// CDK로 보안 그룹 참조 설정하기
import * as ec2 from 'aws-cdk-lib/aws-ec2';
// 웹 서버용 보안 그룹 생성
const webSg = new ec2.SecurityGroup(this, 'WebSG', {
vpc,
description: 'Security group for web servers'
});
// WAS용 보안 그룹 생성 - 웹 서버 보안 그룹을 참조
const wasSg = new ec2.SecurityGroup(this, 'WasSG', {
vpc,
description: 'Security group for application servers'
});
// 핵심: IP가 아닌 보안 그룹 ID로 트래픽 허용
wasSg.addIngressRule(
webSg, // 소스로 보안 그룹 객체를 직접 전달
ec2.Port.tcp(8080),
'Allow traffic from web servers only'
);
김개발 씨는 입사 6개월 차 클라우드 엔지니어입니다. 오늘은 3티어 아키텍처의 보안 그룹을 설정하는 업무를 맡았습니다.
그런데 기존 설정을 보니 뭔가 이상했습니다. 분명 IP 주소가 들어가야 할 자리에 "sg-"로 시작하는 문자열이 있었기 때문입니다.
선배 개발자 박시니어 씨가 다가와 설명해 주었습니다. "그건 보안 그룹 참조라고 해요.
IP 주소 대신 다른 보안 그룹을 참조하는 거죠." 그렇다면 보안 그룹 참조란 정확히 무엇일까요? 쉽게 비유하자면, 보안 그룹 참조는 마치 회사 출입 시스템과 같습니다.
"개발팀 사원증을 가진 사람만 서버실 출입 가능"이라는 규칙을 생각해 보세요. 개발팀원이 바뀌더라도 규칙 자체는 수정할 필요가 없습니다.
새 직원이 개발팀 사원증을 받으면 자동으로 서버실에 들어갈 수 있게 됩니다. IP 주소를 직접 지정하던 시절에는 어땠을까요?
EC2 인스턴스가 교체되거나 Auto Scaling으로 새 인스턴스가 생성될 때마다 문제가 발생했습니다. 새 인스턴스는 새로운 IP 주소를 받기 때문에, 보안 그룹 규칙도 매번 수정해야 했습니다.
인스턴스가 10개, 100개로 늘어나면 관리는 악몽이 됩니다. 더 큰 문제는 휴먼 에러였습니다.
급하게 IP를 추가하다가 실수로 잘못된 IP 범위를 열어버리는 사고가 종종 발생했습니다. 바로 이런 문제를 해결하기 위해 보안 그룹 참조가 등장했습니다.
보안 그룹 참조를 사용하면 "이 보안 그룹에 속한 모든 리소스의 트래픽을 허용한다"라고 선언할 수 있습니다. 인스턴스가 추가되든 교체되든, 해당 보안 그룹에 연결되어 있기만 하면 자동으로 통신이 허용됩니다.
위의 코드를 살펴보겠습니다. 먼저 웹 서버용 보안 그룹 webSg를 생성합니다.
그 다음 WAS용 보안 그룹 wasSg를 생성합니다. 핵심은 addIngressRule 메서드의 첫 번째 인자입니다.
IP 주소 문자열이 아닌 webSg 보안 그룹 객체를 직접 전달하고 있습니다. 이렇게 설정하면 webSg 보안 그룹에 연결된 모든 리소스에서 오는 8080 포트 트래픽만 허용됩니다.
실제 현업에서는 어떻게 활용할까요? 대표적인 사례가 3티어 아키텍처입니다.
웹 서버, WAS, 데이터베이스가 각각 다른 계층에 위치하고, 각 계층은 바로 아래 계층만 접근할 수 있어야 합니다. 보안 그룹 참조를 사용하면 이런 계층 구조를 깔끔하게 표현할 수 있습니다.
하지만 주의할 점도 있습니다. 보안 그룹 참조는 같은 VPC 내에서만 동작합니다.
다른 VPC의 보안 그룹은 참조할 수 없습니다. VPC 피어링을 통해 연결된 경우에도 보안 그룹 참조는 불가능하며, 이 경우에는 IP 주소나 CIDR 블록을 사용해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 Auto Scaling 환경에서도 보안 규칙을 수정할 필요가 없었군요!" 보안 그룹 참조를 제대로 이해하면 동적인 클라우드 환경에서도 안정적인 보안 정책을 유지할 수 있습니다.
실전 팁
💡 - 보안 그룹 참조는 같은 VPC 내에서만 가능합니다
- Auto Scaling 환경에서는 IP 대신 반드시 보안 그룹 참조를 사용하세요
- 보안 그룹 이름에 역할을 명확히 표시하면 참조 시 실수를 줄일 수 있습니다
2. 타이트 커플링 vs 루즈 커플링
김개발 씨가 레거시 인프라 문서를 검토하던 중 이상한 점을 발견했습니다. 보안 그룹 규칙에 "10.0.1.45/32", "10.0.1.46/32"...
이런 식으로 개별 IP가 수십 개나 나열되어 있었습니다. "이걸 어떻게 관리하지?"
타이트 커플링은 IP 주소를 직접 지정하여 보안 그룹과 리소스가 강하게 결합된 상태입니다. 반면 루즈 커플링은 보안 그룹 참조를 사용하여 느슨하게 연결된 상태입니다.
마치 전화번호를 직접 외우는 것과 연락처 앱에 저장해두는 것의 차이와 같습니다.
다음 코드를 살펴봅시다.
// 타이트 커플링 - IP 직접 지정 (권장하지 않음)
const tightCoupledSg = new ec2.SecurityGroup(this, 'TightSG', { vpc });
tightCoupledSg.addIngressRule(
ec2.Peer.ipv4('10.0.1.45/32'), // 특정 IP에 강하게 결합
ec2.Port.tcp(3306),
'Allow specific IP'
);
// IP가 변경되면 규칙도 수정해야 함
// 루즈 커플링 - 보안 그룹 참조 (권장)
const looseCoupledSg = new ec2.SecurityGroup(this, 'LooseSG', { vpc });
looseCoupledSg.addIngressRule(
wasSg, // 보안 그룹으로 느슨하게 연결
ec2.Port.tcp(3306),
'Allow from WAS security group'
);
// WAS 인스턴스가 변경되어도 규칙 수정 불필요
김개발 씨는 레거시 시스템 마이그레이션 프로젝트에 투입되었습니다. 기존 인프라 문서를 열어보니 한숨이 절로 나왔습니다.
보안 그룹 하나에 수십 개의 IP 주소가 빼곡히 적혀 있었기 때문입니다. "이 IP들이 다 뭐예요?" 김개발 씨가 물었습니다.
박시니어 씨가 답했습니다. "저게 바로 타이트 커플링의 폐해예요.
서버 IP가 바뀔 때마다 하나씩 추가한 거죠. 지금은 어떤 IP가 어떤 서버인지도 모르는 상태예요." 타이트 커플링과 루즈 커플링의 차이를 이해해 봅시다.
타이트 커플링은 마치 친구 전화번호를 머릿속에 외우는 것과 같습니다. 친구가 번호를 바꾸면 새 번호를 다시 외워야 합니다.
친구가 10명이면 10개의 번호를 관리해야 하고, 누군가 번호를 바꿀 때마다 기억을 업데이트해야 합니다. 루즈 커플링은 연락처 앱에 이름으로 저장해두는 것과 같습니다.
"개발팀 김철수"라고 저장해두면, 김철수 씨가 번호를 바꾸더라도 연락처만 업데이트하면 됩니다. 전화를 걸 때는 그냥 "김철수"를 찾으면 되죠.
클라우드 환경에서 타이트 커플링이 왜 문제일까요? 첫째, Auto Scaling 환경에서는 인스턴스가 수시로 생성되고 삭제됩니다.
새 인스턴스마다 새 IP가 할당되므로 보안 규칙을 계속 수정해야 합니다. 둘째, 스팟 인스턴스를 사용하면 인스턴스가 예고 없이 종료되고 새로 시작될 수 있습니다.
셋째, 장애 복구 시 인스턴스를 교체하면 IP가 바뀝니다. 반면 루즈 커플링에서는 이런 걱정이 없습니다.
보안 그룹 참조를 사용하면 "WAS 보안 그룹에 속한 모든 것"이라고 정의합니다. 인스턴스가 100개든 1000개든, 새로 생성되든 교체되든, WAS 보안 그룹에만 연결되어 있으면 자동으로 통신이 허용됩니다.
위 코드에서 두 방식의 차이를 명확히 볼 수 있습니다. 타이트 커플링 방식에서는 **ec2.Peer.ipv4('10.0.1.45/32')**로 특정 IP를 직접 지정합니다.
이 IP의 서버가 교체되면 규칙도 수정해야 합니다. 루즈 커플링 방식에서는 wasSg라는 보안 그룹 객체를 참조합니다.
WAS 서버가 어떻게 바뀌든 규칙은 그대로입니다. 실무에서 루즈 커플링을 적용하는 전략을 알아봅시다.
기존에 IP로 설정된 규칙이 있다면, 먼저 해당 IP들이 어떤 역할의 서버인지 파악합니다. 같은 역할의 서버들을 하나의 보안 그룹으로 묶습니다.
그 다음 IP 기반 규칙을 보안 그룹 참조 규칙으로 교체합니다. 주의할 점은 마이그레이션 순서입니다.
새 규칙을 먼저 추가하고, 테스트 후에 기존 IP 규칙을 삭제해야 합니다. 순서를 바꾸면 서비스 장애가 발생할 수 있습니다.
다시 김개발 씨의 상황으로 돌아가 봅시다. "그러면 이 수십 개의 IP를 전부 보안 그룹 참조로 바꿔야 하는 건가요?" 김개발 씨가 물었습니다.
박시니어 씨가 답했습니다. "네, 하지만 한 번만 고생하면 앞으로는 유지보수가 훨씬 쉬워져요.
루즈 커플링이야말로 클라우드 네이티브한 설계의 핵심이니까요."
실전 팁
💡 - 신규 인프라는 처음부터 보안 그룹 참조로 설계하세요
- 레거시 IP 규칙은 단계적으로 마이그레이션하되, 기존 규칙을 먼저 삭제하지 마세요
- 보안 그룹 이름에 역할을 명시하면 어떤 그룹을 참조해야 할지 명확해집니다
3. 내부 로드 밸런서 구성
김개발 씨가 아키텍처 다이어그램을 보다가 의문이 생겼습니다. 외부 로드 밸런서는 인터넷에서 들어오는 트래픽을 받는데, 내부 로드 밸런서는 왜 필요한 걸까요?
그리고 보안 그룹은 어떻게 설정해야 할까요?
**내부 로드 밸런서(Internal ALB)**는 VPC 내부 통신을 위한 로드 밸런서로, 프라이빗 서브넷에 배치됩니다. 웹 서버에서 WAS로 가는 트래픽을 분산하거나, 마이크로서비스 간 통신에 사용됩니다.
보안 그룹 참조를 통해 특정 계층에서만 접근하도록 제한할 수 있습니다.
다음 코드를 살펴봅시다.
// 내부 ALB용 보안 그룹 생성
const internalAlbSg = new ec2.SecurityGroup(this, 'InternalAlbSG', {
vpc,
description: 'Security group for internal ALB'
});
// 웹 서버 보안 그룹에서만 접근 허용
internalAlbSg.addIngressRule(
webServerSg, // 웹 서버 보안 그룹 참조
ec2.Port.tcp(80),
'Allow HTTP from web servers'
);
// 내부 ALB 생성 - scheme을 internal로 설정
const internalAlb = new elbv2.ApplicationLoadBalancer(this, 'InternalALB', {
vpc,
internetFacing: false, // 핵심: 내부용으로 설정
securityGroup: internalAlbSg,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }
});
김개발 씨는 3티어 아키텍처를 설계하던 중 고민에 빠졌습니다. 웹 서버에서 WAS로 요청을 보낼 때, WAS 인스턴스가 여러 개라면 어떻게 해야 할까요?
"WAS 인스턴스 IP를 직접 지정하면... 아, 타이트 커플링이 되어버리네." 김개발 씨는 이전에 배운 내용을 떠올렸습니다.
박시니어 씨가 해결책을 제시했습니다. "내부 로드 밸런서를 사용하면 돼요.
WAS 앞에 내부 ALB를 두는 거죠." 내부 로드 밸런서란 무엇일까요? 일반적으로 우리가 아는 로드 밸런서는 인터넷에서 들어오는 트래픽을 받습니다.
이것을 **외부 로드 밸런서(Internet-facing ALB)**라고 합니다. 반면 **내부 로드 밸런서(Internal ALB)**는 VPC 내부에서만 접근 가능합니다.
인터넷에서는 아예 접근할 수 없습니다. 마치 회사 건물에 비유하면, 외부 로드 밸런서는 정문 안내데스크입니다.
외부 손님을 맞이하고 적절한 곳으로 안내합니다. 내부 로드 밸런서는 각 층에 있는 내부 안내데스크입니다.
이미 건물 안에 있는 직원들만 이용할 수 있습니다. 왜 내부 로드 밸런서가 필요할까요?
첫째, 부하 분산입니다. WAS 인스턴스가 여러 개일 때 트래픽을 골고루 분배합니다.
둘째, 헬스 체크입니다. 문제가 있는 WAS 인스턴스를 자동으로 제외합니다.
셋째, 느슨한 결합입니다. 웹 서버는 내부 ALB의 DNS 이름만 알면 되고, 개별 WAS IP를 알 필요가 없습니다.
위 코드를 살펴보겠습니다. 가장 중요한 부분은 internetFacing: false 설정입니다.
이것이 내부 로드 밸런서를 만드는 핵심입니다. true로 설정하면 인터넷에서 접근 가능한 외부 로드 밸런서가 됩니다.
다음으로 vpcSubnets 설정을 보면 PRIVATE_WITH_EGRESS 서브넷을 지정했습니다. 내부 ALB는 프라이빗 서브넷에 배치해야 합니다.
보안 그룹 설정도 중요합니다. internalAlbSg.addIngressRule에서 webServerSg를 참조하고 있습니다.
이렇게 하면 웹 서버 보안 그룹에 속한 인스턴스에서만 내부 ALB에 접근할 수 있습니다. 실무에서 내부 ALB는 어떻게 활용될까요?
대표적인 패턴이 웹-WAS 분리입니다. 웹 서버(Nginx)는 정적 파일 서빙과 리버스 프록시 역할을 하고, WAS(Tomcat, Node.js)는 비즈니스 로직을 처리합니다.
웹 서버는 내부 ALB를 통해 WAS에 요청을 전달합니다. 또 다른 활용 사례는 마이크로서비스 아키텍처입니다.
각 마이크로서비스 앞에 내부 ALB를 두면, 서비스 간 통신이 유연해집니다. 서비스 A가 서비스 B의 내부 ALB DNS를 호출하면 됩니다.
주의할 점이 있습니다. 내부 ALB의 DNS 이름은 프라이빗 IP로 resolve됩니다.
VPC 외부에서는 이 DNS를 조회해도 접근할 수 없습니다. 또한 내부 ALB라도 보안 그룹을 0.0.0.0/0으로 열면 안 됩니다.
반드시 필요한 소스만 허용해야 합니다. 김개발 씨가 정리했습니다.
"내부 ALB를 사용하면 WAS IP를 웹 서버에서 직접 관리할 필요가 없군요. 게다가 보안 그룹 참조로 웹 서버에서만 접근하도록 제한하면 보안도 확보되고요." 박시니어 씨가 덧붙였습니다.
"맞아요. 이게 바로 계층별 보안의 핵심이에요."
실전 팁
💡 - 내부 ALB는 반드시 프라이빗 서브넷에 배치하세요
- 내부 ALB도 최소 2개의 가용 영역에 걸쳐 배치해야 고가용성이 확보됩니다
- Route 53 프라이빗 호스팅 영역과 연동하면 더 깔끔한 DNS 이름을 사용할 수 있습니다
4. WAS에서 DB로 보안 그룹 참조 설정
김개발 씨가 데이터베이스 보안 설정을 하려고 합니다. "DB는 정말 중요한데, 아무나 접근하면 안 되잖아요.
어떻게 해야 WAS에서만 접근하도록 막을 수 있을까요?"
데이터베이스는 가장 민감한 계층이므로 보안 그룹 참조를 통해 WAS 계층에서만 접근하도록 엄격하게 제한해야 합니다. RDS 보안 그룹의 인바운드 규칙에 WAS 보안 그룹을 소스로 지정하면, WAS 인스턴스만 DB에 연결할 수 있습니다.
다음 코드를 살펴봅시다.
// WAS용 보안 그룹
const wasSg = new ec2.SecurityGroup(this, 'WasSG', {
vpc,
description: 'Security group for WAS instances'
});
// RDS용 보안 그룹 - 핵심 보안 설정
const rdsSg = new ec2.SecurityGroup(this, 'RdsSG', {
vpc,
description: 'Security group for RDS database'
});
// WAS 보안 그룹에서만 MySQL 접근 허용
rdsSg.addIngressRule(
wasSg, // WAS 보안 그룹만 허용
ec2.Port.tcp(3306),
'Allow MySQL access from WAS only'
);
// RDS 인스턴스 생성
const rdsInstance = new rds.DatabaseInstance(this, 'Database', {
engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0 }),
vpc,
securityGroups: [rdsSg],
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }
});
"데이터베이스가 털리면 회사가 망해요." 박시니어 씨의 말에 김개발 씨는 긴장했습니다. 고객 정보, 결제 정보, 모든 중요한 데이터가 데이터베이스에 있기 때문입니다.
그렇다면 데이터베이스를 어떻게 보호해야 할까요? 가장 기본적인 원칙은 필요한 곳에서만 접근 가능하게 하는 것입니다.
데이터베이스에 직접 접근해야 하는 것은 WAS뿐입니다. 웹 서버도, 인터넷도, 심지어 개발자 PC도 직접 접근할 필요가 없습니다.
마치 은행 금고와 같습니다. 금고에는 금고 담당자만 들어갈 수 있습니다.
은행 창구 직원도, 고객도 금고에 직접 들어가지 않습니다. 필요한 것이 있으면 금고 담당자를 통해 가져옵니다.
위 코드에서 이 원칙이 어떻게 구현되는지 살펴봅시다. 먼저 WAS용 보안 그룹 wasSg를 생성합니다.
그 다음 RDS용 보안 그룹 rdsSg를 생성합니다. 핵심은 rdsSg.addIngressRule입니다.
소스로 wasSg만 지정했습니다. 이렇게 설정하면 wasSg에 연결된 인스턴스에서만 MySQL 포트(3306)로 접근할 수 있습니다.
다른 어떤 곳에서도 접근이 불가능합니다. PRIVATE_ISOLATED 서브넷도 중요합니다.
AWS에는 여러 종류의 서브넷이 있습니다. PUBLIC 서브넷은 인터넷 게이트웨이를 통해 인터넷과 직접 통신합니다.
PRIVATE_WITH_EGRESS 서브넷은 NAT 게이트웨이를 통해 아웃바운드만 가능합니다. PRIVATE_ISOLATED 서브넷은 인터넷과 완전히 격리됩니다.
데이터베이스는 PRIVATE_ISOLATED 서브넷에 배치해야 합니다. 인터넷에서 들어오는 인바운드도, 인터넷으로 나가는 아웃바운드도 필요 없기 때문입니다.
DB가 해야 할 일은 VPC 내부의 WAS 요청에 응답하는 것뿐입니다. 실무에서 흔히 하는 실수를 알아봅시다.
첫 번째 실수는 개발 편의를 위해 0.0.0.0/0을 여는 것입니다. "개발 중이니까 잠깐만..." 이런 마음으로 열었다가 그대로 운영에 배포되는 경우가 있습니다.
절대 하면 안 됩니다. 두 번째 실수는 Bastion Host 보안 그룹을 DB에 추가하는 것입니다.
관리자가 DB에 직접 쿼리를 날리고 싶을 때 이렇게 합니다. 하지만 이것도 위험합니다.
Bastion이 털리면 DB도 위험해집니다. 더 안전한 방법이 있습니다.
AWS Systems Manager의 Session Manager를 사용하면 Bastion Host 없이도 프라이빗 인스턴스에 접속할 수 있습니다. 또는 WAS 인스턴스에서 DB 클라이언트를 실행하는 방법도 있습니다.
어느 쪽이든 DB 보안 그룹에 추가 규칙을 넣지 않아도 됩니다. 김개발 씨가 물었습니다.
"그런데 RDS 생성할 때 publiclyAccessible 옵션이 있던데요?" 박시니어 씨가 답했습니다. "그건 반드시 false로 설정해야 해요.
true로 하면 RDS에 퍼블릭 IP가 할당되고, 보안 그룹만 열려 있으면 인터넷에서 접근 가능해져요. 정말 위험합니다." 데이터베이스 보안은 아무리 강조해도 지나치지 않습니다.
실전 팁
💡 - RDS의 publiclyAccessible은 반드시 false로 설정하세요
- DB 보안 그룹에는 0.0.0.0/0을 절대 추가하지 마세요
- DB 관리가 필요하면 Session Manager나 VPN을 통해 접근하세요
5. 보안 그룹 체이닝 패턴
김개발 씨가 지금까지 배운 내용을 정리하다가 패턴을 발견했습니다. "웹 서버 → 내부 ALB → WAS → DB...
각 단계마다 앞 단계의 보안 그룹만 참조하네요?" 박시니어 씨가 미소 지었습니다. "그게 바로 보안 그룹 체이닝이에요."
보안 그룹 체이닝은 각 계층의 보안 그룹이 바로 앞 계층의 보안 그룹만 참조하는 패턴입니다. ALB → Web → Internal ALB → WAS → DB 순으로 체인처럼 연결됩니다.
이 패턴을 사용하면 계층 간 통신 경로가 명확해지고, 보안 위반 시 영향 범위를 최소화할 수 있습니다.
다음 코드를 살펴봅시다.
// 보안 그룹 체이닝 - 전체 3티어 구성
const albSg = new ec2.SecurityGroup(this, 'AlbSG', { vpc });
const webSg = new ec2.SecurityGroup(this, 'WebSG', { vpc });
const internalAlbSg = new ec2.SecurityGroup(this, 'InternalAlbSG', { vpc });
const wasSg = new ec2.SecurityGroup(this, 'WasSG', { vpc });
const dbSg = new ec2.SecurityGroup(this, 'DbSG', { vpc });
// 체인 1: 인터넷 → ALB (HTTPS만 허용)
albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'HTTPS from internet');
// 체인 2: ALB → Web
webSg.addIngressRule(albSg, ec2.Port.tcp(80), 'From ALB');
// 체인 3: Web → Internal ALB
internalAlbSg.addIngressRule(webSg, ec2.Port.tcp(80), 'From Web');
// 체인 4: Internal ALB → WAS
wasSg.addIngressRule(internalAlbSg, ec2.Port.tcp(8080), 'From Internal ALB');
// 체인 5: WAS → DB
dbSg.addIngressRule(wasSg, ec2.Port.tcp(3306), 'From WAS only');
김개발 씨는 화이트보드에 아키텍처를 그려보았습니다. 인터넷에서 시작해서 데이터베이스까지, 각 단계의 보안 그룹이 체인처럼 연결되어 있었습니다.
"이게 바로 보안 그룹 체이닝 패턴이에요." 박시니어 씨가 설명했습니다. "각 계층은 바로 앞 계층만 신뢰하는 거죠." 보안 그룹 체이닝은 마치 릴레이 경주와 같습니다.
첫 번째 주자(ALB)가 바톤을 받아 두 번째 주자(Web)에게 넘깁니다. 두 번째 주자는 세 번째 주자(Internal ALB)에게만 바톤을 넘길 수 있습니다.
아무나 중간에 끼어들 수 없습니다. 각 주자는 바로 앞 주자에게서만 바톤을 받을 수 있습니다.
위 코드에서 체인의 각 연결을 살펴봅시다. 첫 번째 체인은 인터넷에서 ALB로 들어오는 구간입니다.
여기서만 **ec2.Peer.anyIpv4()**를 사용합니다. 외부 로드 밸런서는 인터넷의 모든 사용자가 접근해야 하기 때문입니다.
단, HTTPS(443)만 허용합니다. 두 번째 체인부터는 모두 보안 그룹 참조입니다.
웹 서버는 albSg에서만 트래픽을 받습니다. 즉, 외부 ALB를 거치지 않고는 웹 서버에 접근할 수 없습니다.
세 번째 체인에서 내부 ALB는 webSg만 참조합니다. 웹 서버가 아닌 곳에서는 내부 ALB에 접근할 수 없습니다.
네 번째 체인에서 WAS는 internalAlbSg만 참조합니다. 내부 ALB를 통해서만 WAS에 도달할 수 있습니다.
다섯 번째 체인에서 DB는 wasSg만 참조합니다. WAS만 DB에 접근할 수 있습니다.
이 패턴의 장점은 무엇일까요? 첫째, 공격 표면 최소화입니다.
각 계층은 바로 앞 계층에서만 접근 가능하므로, 공격자가 한 계층을 뚫어도 다음 계층으로 바로 넘어갈 수 없습니다. 둘째, 영향 범위 제한입니다.
웹 서버가 침해되어도 공격자는 내부 ALB까지만 접근할 수 있습니다. WAS나 DB에 직접 접근하려면 추가 단계를 뚫어야 합니다.
셋째, 문제 추적 용이입니다. 각 체인이 명확하므로 어디서 문제가 발생했는지 쉽게 파악할 수 있습니다.
VPC Flow Logs와 함께 사용하면 더욱 효과적입니다. 실무에서 자주 하는 실수가 있습니다.
"편의상" 웹 서버에서 DB로 직접 연결하는 경우입니다. 이렇게 하면 체인이 끊어집니다.
웹 서버 → Internal ALB → WAS → DB 경로를 지켜야 합니다. 설령 웹 서버에서 간단한 쿼리가 필요하더라도 WAS를 통해 API로 호출해야 합니다.
또 다른 실수는 관리용 접근을 위해 체인을 우회하는 것입니다. 예를 들어 모니터링 서버에서 모든 계층에 직접 접근하도록 설정하면 체인의 의미가 없어집니다.
모니터링도 가능하면 에이전트 방식(아웃바운드)을 사용하거나, 별도의 관리용 체인을 구성해야 합니다. 김개발 씨가 감탄했습니다.
"체인처럼 연결하니까 전체 구조가 한눈에 들어오네요. 그리고 각 계층의 책임도 명확해지고요." 박시니어 씨가 덧붙였습니다.
"이 패턴을 **심층 방어(Defense in Depth)**라고도 해요. 여러 겹의 보안 계층을 두는 거죠."
실전 팁
💡 - 체인을 그림으로 그려서 문서화하면 팀원 모두가 이해하기 쉽습니다
- VPC Flow Logs를 활성화하면 체인의 각 단계에서 트래픽 흐름을 추적할 수 있습니다
- 모니터링이나 관리 접근도 가능하면 체인 구조를 유지하세요
6. 최소 권한 원칙 적용 사례
김개발 씨가 보안 그룹 설정을 마무리하며 한 가지 더 궁금해졌습니다. "포트는 어떻게 설정해야 하나요?
그냥 다 열어도 되나요?" 박시니어 씨가 단호하게 말했습니다. "절대 안 돼요.
최소 권한 원칙을 꼭 지켜야 해요."
**최소 권한 원칙(Principle of Least Privilege)**은 필요한 최소한의 권한만 부여하는 보안 원칙입니다. 보안 그룹에서는 필요한 포트만, 필요한 소스에서만 접근을 허용해야 합니다.
"일단 열어두고 나중에 제한"이 아니라 "기본적으로 모두 차단하고 필요한 것만 허용"해야 합니다.
다음 코드를 살펴봅시다.
// 잘못된 예시 - 과도한 권한
const badSg = new ec2.SecurityGroup(this, 'BadSG', { vpc });
badSg.addIngressRule(
ec2.Peer.anyIpv4(), // 모든 IP에서
ec2.Port.allTraffic(), // 모든 포트 허용 - 위험!
'Allow all - DANGEROUS'
);
// 올바른 예시 - 최소 권한 적용
const goodSg = new ec2.SecurityGroup(this, 'GoodSG', { vpc });
// 필요한 소스에서, 필요한 포트만
goodSg.addIngressRule(
webSg, // 특정 보안 그룹에서만
ec2.Port.tcp(8080), // 필요한 포트만
'Allow port 8080 from web servers'
);
// 헬스 체크용 별도 포트도 명시적으로 추가
goodSg.addIngressRule(
albSg,
ec2.Port.tcp(8081), // 헬스 체크 전용 포트
'Health check from ALB'
);
"왜 모든 포트를 열면 안 되나요? 어차피 보안 그룹 참조로 소스를 제한했잖아요." 김개발 씨가 물었습니다.
박시니어 씨가 설명을 시작했습니다. "좋은 질문이에요.
하지만 심층 방어 관점에서 생각해 봐요." 최소 권한 원칙은 마치 호텔 방 열쇠와 같습니다. 호텔에 투숙하면 내 방 열쇠만 받습니다.
다른 방에는 들어갈 수 없습니다. 만약 "편의상" 모든 방 마스터키를 손님에게 준다면?
내 짐만 찾으면 되는데 모든 방에 접근할 수 있으니 보안 위험이 커집니다. 보안 그룹도 마찬가지입니다.
WAS에서 필요한 것은 MySQL 포트(3306)뿐인데 모든 포트를 열면? 공격자가 WAS를 침해했을 때 더 많은 선택지를 갖게 됩니다.
위 코드에서 두 예시의 차이를 봅시다. 잘못된 예시에서는 **ec2.Port.allTraffic()**을 사용했습니다.
이렇게 하면 1번 포트부터 65535번 포트까지 모든 포트가 열립니다. 공격자가 원하는 어떤 포트로든 접근 시도가 가능합니다.
올바른 예시에서는 **ec2.Port.tcp(8080)**처럼 정확한 포트만 지정했습니다. 애플리케이션이 8080 포트에서 실행되니까 8080만 열면 됩니다.
헬스 체크가 8081 포트를 사용한다면 그것도 별도로 추가합니다. 실무에서 최소 권한 원칙을 적용하는 방법을 알아봅시다.
첫째, 기본 거부입니다. AWS 보안 그룹은 기본적으로 모든 인바운드를 차단합니다.
이 기본값을 유지하고, 필요한 것만 명시적으로 허용합니다. 둘째, 포트별 문서화입니다.
왜 이 포트가 필요한지 주석이나 설명에 기록합니다. 나중에 "이 포트는 왜 열려 있지?"라는 의문이 생기지 않도록 합니다.
셋째, 정기적인 검토입니다. 더 이상 사용하지 않는 포트가 있는지 주기적으로 확인합니다.
레거시 시스템을 교체했는데 관련 포트 규칙은 그대로 남아있는 경우가 많습니다. 흔한 실수 사례를 살펴봅시다.
"개발 중에는 편하게"라며 모든 포트를 열어두는 경우가 있습니다. 문제는 이 상태로 운영 환경에 배포되는 것입니다.
IaC(Infrastructure as Code)를 사용하면 이런 실수를 줄일 수 있습니다. 코드 리뷰에서 **Port.allTraffic()**이나 0.0.0.0/0을 발견하면 바로 지적할 수 있기 때문입니다.
또 다른 실수는 "SSH(22)는 당연히 필요하지"라며 모든 서버에 SSH 포트를 여는 것입니다. 하지만 AWS Systems Manager Session Manager를 사용하면 SSH 포트 없이도 서버에 접속할 수 있습니다.
SSH 포트가 열려 있으면 무차별 대입 공격의 대상이 됩니다. 보안 그룹 참조를 사용해도 포트 제한은 필요합니다.
"어차피 웹 서버 보안 그룹에서만 접근 가능한데, 포트까지 제한할 필요가 있나요?" 네, 필요합니다. 웹 서버가 침해되었을 때를 가정해야 합니다.
공격자가 웹 서버를 장악했다면, 웹 서버에서 갈 수 있는 모든 곳이 위험해집니다. 포트를 제한해 두면 공격자가 할 수 있는 일이 제한됩니다.
8080 포트로만 WAS에 접근할 수 있다면, 공격자도 8080 포트를 통해서만 공격해야 합니다. 다른 포트로 우회하는 것이 불가능합니다.
김개발 씨가 고개를 끄덕였습니다. "이제 이해했어요.
보안 그룹 참조로 누가 접근할 수 있는지 정하고, 포트 제한으로 어떻게 접근할 수 있는지 정하는 거군요. 둘 다 해야 제대로 된 보안이 되는 거네요."
실전 팁
💡 - Port.allTraffic()이나 0.0.0.0/0은 코드 리뷰에서 반드시 지적하세요
- SSH 대신 Session Manager 사용을 검토하세요
- 규칙 설명에 왜 이 포트가 필요한지 기록해 두면 나중에 정리하기 쉽습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
AWS Systems Manager Session Manager로 안전한 서버 접속 완벽 가이드
AWS Systems Manager의 Session Manager를 활용하여 SSH 키 없이 프라이빗 인스턴스에 안전하게 접속하는 방법을 알아봅니다. Bastion 서버 없이도 보안을 강화하고, 모든 세션을 로깅하여 감사까지 할 수 있는 현대적인 서버 접속 방식을 배워봅니다.
NAT 게이트웨이로 프라이빗 서브넷 인터넷 연결 완벽 가이드
AWS VPC의 프라이빗 서브넷에서 인터넷에 접근하는 방법을 NAT 게이트웨이를 통해 배웁니다. 보안을 유지하면서 외부 API 호출, 패키지 업데이트 등을 수행하는 핵심 네트워크 아키텍처를 쉽게 이해할 수 있습니다.
인터넷 게이트웨이와 퍼블릭 라우팅 설정 완벽 가이드
AWS VPC에서 인터넷과 통신하기 위한 핵심 구성요소인 인터넷 게이트웨이와 퍼블릭 라우팅 설정을 단계별로 학습합니다. 초급 개발자도 쉽게 따라할 수 있도록 실무 예제와 함께 설명합니다.
LLM-as-Judge TypeScript 실전 구현 가이드
프로덕션급 LLM 평가 시스템을 TypeScript로 구현하는 방법을 다룹니다. 19개의 테스트로 검증된 Direct Scoring, Pairwise Comparison, Rubric Generation 패턴을 실습하며, Eugene Yan과 Vercel AI SDK 6의 연구를 실제 코드에 적용합니다.
실전 예제 X-to-Book System 분석 완벽 가이드
Claude Agent SDK의 핵심 스킬들이 실제 프로젝트에서 어떻게 조합되는지 X-to-Book System을 통해 분석합니다. 멀티 에이전트 패턴부터 메모리 시스템까지, 실전 아키텍처의 비밀을 파헤쳐 봅니다.