본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 20. · 5 Views
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
목차
1. 보안 요구사항 정의
스타트업 개발자 김개발 씨는 새로운 서비스를 런칭하게 되었습니다. CTO가 물었습니다.
"보안은 어떻게 설계할 건가요?" 김개발 씨는 막막했습니다. 어디서부터 시작해야 할까요?
보안 요구사항 정의는 프로젝트에 필요한 보안 수준을 결정하는 첫 단계입니다. 마치 집을 짓기 전에 어떤 자물쇠와 경비 시스템이 필요한지 계획하는 것과 같습니다.
데이터 민감도, 규정 준수, 위협 모델을 분석하여 구체적인 보안 목표를 수립합니다.
다음 코드를 살펴봅시다.
# 보안 요구사항 체크리스트
security_requirements = {
# 데이터 분류: 민감도에 따른 보호 수준
"data_classification": {
"pii": "high", # 개인정보는 높은 수준 보호
"financial": "critical", # 금융 데이터는 최고 수준
"public": "low" # 공개 데이터는 기본 수준
},
# 규정 준수: 법적 요구사항
"compliance": ["GDPR", "ISMS-P", "PCI-DSS"],
# 보안 목표: CIA 트라이어드
"security_goals": {
"confidentiality": True, # 기밀성: 데이터 암호화
"integrity": True, # 무결성: 데이터 변조 방지
"availability": 99.9 # 가용성: 99.9% 업타임
}
}
김개발 씨는 신입 개발자 시절, 보안을 "나중에 추가하면 되는 것"이라고 생각했습니다. 하지만 첫 프로젝트에서 개인정보가 유출되는 사고를 겪은 후, 생각이 완전히 바뀌었습니다.
보안은 처음부터 설계되어야 합니다. 선배 개발자 박시니어 씨가 조언했습니다.
"보안은 집을 짓는 것과 같아요. 완공 후에 벽을 더 튼튼하게 만들 수는 없습니다.
설계 단계에서 결정해야 합니다." 보안 요구사항 정의란 무엇일까요? 쉽게 비유하자면, 보안 요구사항은 마치 건축 설계도와 같습니다.
어떤 문을 달고, 어떤 자물쇠를 쓰고, CCTV는 어디에 설치할지 미리 계획하는 것입니다. 프로젝트를 시작하기 전에 "무엇을 보호해야 하는가"와 "어떻게 보호할 것인가"를 명확히 정의하는 과정입니다.
보안 요구사항 없이 개발하면 어떻게 될까요? 개발자들은 각자 생각하는 대로 보안을 구현합니다.
어떤 개발자는 비밀번호를 평문으로 저장하고, 어떤 개발자는 SHA-256으로 해싱합니다. 일관성이 없어지고, 보안 취약점이 생깁니다.
더 큰 문제는 나중에 규정 준수 감사를 받을 때 "무엇을 어떻게 보호하고 있는지" 설명할 수 없다는 것입니다. 바로 이런 문제를 해결하기 위해 보안 요구사항 정의가 필요합니다.
보안 요구사항을 정의하면 일관된 보안 정책을 수립할 수 있습니다. 또한 규정 준수를 자연스럽게 만족할 수 있습니다.
무엇보다 개발 초기에 보안 비용이 낮다는 큰 이점이 있습니다. 보안 요구사항은 크게 세 가지로 나뉩니다.
첫째, 데이터 분류입니다. 모든 데이터가 같은 수준의 보호를 필요로 하지 않습니다.
개인정보는 높은 수준으로, 공개 데이터는 기본 수준으로 분류합니다. 위의 코드에서 data_classification 딕셔너리가 이를 보여줍니다.
둘째, 규정 준수입니다. 금융 서비스라면 PCI-DSS를, 유럽 사용자가 있다면 GDPR을 준수해야 합니다.
이런 규정들이 요구하는 보안 통제를 미리 파악해야 합니다. 셋째, CIA 트라이어드입니다.
정보 보안의 3대 원칙인 기밀성(Confidentiality), 무결성(Integrity), 가용성(Availability)을 정의합니다. 예를 들어 의료 서비스는 기밀성이 최우선이고, 결제 서비스는 무결성이 중요합니다.
실제 현업에서는 어떻게 활용할까요? 핀테크 스타트업을 예로 들어봅시다.
사용자의 계좌 정보를 다루기 때문에 금융 데이터는 critical 등급으로 분류합니다. PCI-DSS 규정을 준수해야 하고, 데이터는 반드시 암호화되어야 합니다.
가용성은 99.9% 이상이어야 합니다. 이런 요구사항을 미리 정의하면, 개발팀은 AWS KMS로 암호화하고, Multi-AZ로 고가용성을 구성하는 등 구체적인 설계를 할 수 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수는 "모든 것을 최고 수준으로 보호하려는 것"입니다.
공개 블로그 데이터까지 암호화하면 성능이 떨어지고 비용이 증가합니다. 따라서 데이터의 실제 가치와 위험을 평가하여 적절한 수준을 선택해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언을 들은 김개발 씨는 체크리스트를 만들었습니다.
"우리 서비스는 개인정보를 다루니 high 등급, ISMS-P 인증이 필요하니 관련 통제를 구현하자." 보안 요구사항을 제대로 정의하면 나중에 "이거 왜 이렇게 했어요?"라는 질문에 명확히 답할 수 있습니다. 여러분도 다음 프로젝트를 시작하기 전에 보안 요구사항부터 정의해 보세요.
실전 팁
💡 - 데이터 흐름도(DFD)를 그려서 어떤 데이터가 어디로 흐르는지 시각화하세요
- STRIDE 모델을 사용해 위협을 체계적으로 분석하세요
- 규정 준수는 법무팀과 협업하여 정확한 요구사항을 파악하세요
2. VPC 네트워크 구성
김개발 씨는 AWS에 서버를 띄우려다가 깨달았습니다. "EC2를 인터넷에 바로 노출시켜도 되나?" 박시니어 씨가 고개를 저었습니다.
"VPC부터 제대로 설계해야 합니다."
VPC 네트워크 구성은 AWS 클라우드 내에 격리된 가상 네트워크를 만드는 것입니다. 마치 아파트 단지에 담을 세우고 출입구를 만드는 것처럼, 퍼블릭과 프라이빗 서브넷을 나누고 인터넷 게이트웨이와 NAT 게이트웨이를 설정하여 네트워크 보안을 강화합니다.
다음 코드를 살펴봅시다.
# Terraform으로 VPC 구성하기
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16" # 65,536개 IP 주소 공간
enable_dns_hostnames = true
tags = {
Name = "production-vpc"
}
}
# 퍼블릭 서브넷: 웹 서버용 (인터넷 접근 가능)
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24" # 256개 IP
availability_zone = "ap-northeast-2a"
map_public_ip_on_launch = true # 자동 퍼블릭 IP 할당
}
# 프라이빗 서브넷: 데이터베이스용 (인터넷 차단)
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-2a"
}
김개발 씨의 첫 프로젝트는 간단했습니다. EC2 하나에 웹 서버와 데이터베이스를 모두 설치했습니다.
그런데 어느 날 보안 감사를 받게 되었습니다. 감사관이 물었습니다.
"데이터베이스가 인터넷에 직접 노출되어 있네요?" 김개발 씨는 식은땀을 흘렸습니다. 데이터베이스는 외부에서 접근할 수 없어야 하는데, 자신의 구성은 누구나 접속을 시도할 수 있었습니다.
VPC 네트워크 구성이란 무엇일까요? VPC는 Virtual Private Cloud의 약자로, 쉽게 말해 AWS 클라우드 안에 여러분만의 독립된 네트워크를 만드는 것입니다.
마치 아파트 단지를 생각해보세요. 담장으로 외부와 구분되고, 정문과 후문이 있으며, 동마다 출입 통제가 다릅니다.
VPC도 이와 같습니다. VPC 없이 리소스를 배포하면 어떻게 될까요?
모든 서버가 인터넷에 직접 노출됩니다. 웹 서버는 괜찮지만, 데이터베이스나 내부 API 서버까지 외부에서 접근 가능해집니다.
해커들은 포트 스캔으로 이런 서버들을 찾아내고 공격을 시도합니다. 실제로 2019년 한 회사는 MongoDB를 인터넷에 노출시켜 수백만 건의 고객 데이터가 유출되었습니다.
바로 이런 문제를 해결하기 위해 VPC 네트워크 구성이 필수적입니다. VPC를 사용하면 네트워크 격리가 가능해집니다.
또한 서브넷 단위로 접근 제어를 할 수 있습니다. 무엇보다 방화벽 규칙을 세밀하게 설정할 수 있다는 큰 이점이 있습니다.
VPC의 핵심 개념을 살펴보겠습니다. 첫 번째는 CIDR 블록입니다.
위 코드에서 10.0.0.0/16은 10.0.0.0부터 10.0.255.255까지 약 65,000개의 IP 주소를 의미합니다. 충분히 큰 주소 공간을 확보하면 나중에 서브넷을 추가하기 쉽습니다.
두 번째는 퍼블릭 서브넷입니다. 10.0.1.0/24는 256개의 IP를 가지며, map_public_ip_on_launch = true 설정으로 이 서브넷의 인스턴스들은 자동으로 퍼블릭 IP를 받습니다.
웹 서버, 로드 밸런서처럼 인터넷과 통신해야 하는 리소스를 배치합니다. 세 번째는 프라이빗 서브넷입니다.
10.0.2.0/24는 퍼블릭 IP가 없어 인터넷에서 직접 접근할 수 없습니다. 데이터베이스, 내부 API 서버 등 외부 노출이 불필요한 리소스를 배치합니다.
실제 현업에서는 어떻게 활용할까요? 이커머스 서비스를 예로 들어봅시다.
웹 프론트엔드는 퍼블릭 서브넷에 배치하여 사용자가 접속할 수 있게 합니다. API 서버는 프라이빗 서브넷에 두고, Application Load Balancer를 통해서만 접근하게 합니다.
RDS 데이터베이스는 더 깊은 프라이빗 서브넷에 두어 API 서버만 접근 가능하게 합니다. 이런 다층 방어(Defense in Depth) 구조가 표준입니다.
또한 Multi-AZ 구성도 중요합니다. 위 코드에서는 하나의 가용 영역만 사용했지만, 실제로는 ap-northeast-2a와 ap-northeast-2c에 각각 서브넷을 만들어 장애 대비를 해야 합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수는 "서브넷을 너무 작게 나누는 것"입니다.
/28 (16개 IP)로 만들면 나중에 오토 스케일링할 때 IP가 부족해집니다. AWS는 각 서브넷에서 5개 IP를 예약하므로, 여유 있게 /24 (256개) 이상을 권장합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언을 들은 김개발 씨는 VPC를 다시 설계했습니다.
퍼블릭 서브넷에는 웹 서버만, 프라이빗 서브넷에는 RDS만 배치했습니다. 보안 감사에서 합격점을 받았습니다.
VPC를 제대로 구성하면 네트워크 수준에서 보안이 시작됩니다. 여러분도 다음 프로젝트에서 VPC 설계부터 시작해 보세요.
실전 팁
💡 - 프로덕션 환경은 최소 3개 가용 영역에 서브넷을 배치하세요
- CIDR 블록은 온프레미스 네트워크와 겹치지 않게 계획하세요
- VPC Flow Logs를 활성화해 네트워크 트래픽을 모니터링하세요
3. WAF와 CloudFront 적용
서비스가 인기를 얻자, 이상한 트래픽이 증가하기 시작했습니다. SQL 인젝션 시도, 봇 공격, DDoS까지.
김개발 씨는 밤잠을 설쳤습니다. "이걸 어떻게 막지?" 박시니어 씨가 말했습니다.
"WAF를 적용할 시간입니다."
WAF는 웹 애플리케이션 방화벽으로, SQL 인젝션, XSS 같은 웹 공격을 필터링합니다. CloudFront는 CDN으로 전 세계 엣지에서 콘텐츠를 제공하며 DDoS 방어도 제공합니다.
둘을 함께 사용하면 오리진 서버를 보호하면서 성능도 향상시킬 수 있습니다.
다음 코드를 살펴봅시다.
# Terraform으로 WAF 규칙 설정하기
resource "aws_wafv2_web_acl" "main" {
name = "production-waf"
scope = "CLOUDFRONT" # CloudFront에 연결
default_action {
allow {} # 기본적으로 허용, 규칙에 매치되면 차단
}
# 규칙 1: SQL 인젝션 차단
rule {
name = "block-sql-injection"
priority = 1
statement {
managed_rule_group_statement {
vendor_name = "AWS"
name = "AWSManagedRulesSQLiRuleSet"
}
}
action {
block {} # SQL 인젝션 시도는 즉시 차단
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "SQLiBlocked"
sampled_requests_enabled = true
}
}
# 규칙 2: 잘 알려진 봇 차단
rule {
name = "block-bad-bots"
priority = 2
statement {
managed_rule_group_statement {
vendor_name = "AWS"
name = "AWSManagedRulesKnownBadInputsRuleSet"
}
}
action {
block {}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "BadBotsBlocked"
sampled_requests_enabled = true
}
}
}
김개발 씨의 서비스는 론칭 3개월 만에 사용자 1만 명을 돌파했습니다. 기쁨도 잠시, CloudWatch 알람이 울리기 시작했습니다.
데이터베이스 CPU가 100%에 도달했습니다. 로그를 확인해보니 수상한 쿼리가 가득했습니다.
"SELECT * FROM users WHERE id=1 OR 1=1--" SQL 인젝션 공격이었습니다. 누군가 데이터베이스의 모든 정보를 빼내려 시도하고 있었습니다.
WAF란 무엇일까요? WAF는 Web Application Firewall의 약자입니다.
마치 공항 보안 검색대와 같습니다. 모든 승객(HTTP 요청)이 비행기(웹 서버)에 타기 전에 위험 물품(악의적인 페이로드)을 가지고 있는지 검사합니다.
SQL 인젝션, XSS, 파일 포함 공격 등 OWASP Top 10에 포함된 웹 공격들을 자동으로 탐지하고 차단합니다. WAF 없이 서비스를 운영하면 어떻게 될까요?
모든 공격이 애플리케이션까지 도달합니다. 개발자가 아무리 조심해도, 하나의 취약점이라도 있으면 시스템 전체가 위험해집니다.
2017년 Equifax 해킹 사건에서는 Apache Struts의 알려진 취약점을 패치하지 않아 1억 4천만 명의 개인정보가 유출되었습니다. WAF가 있었다면 공격을 초기에 차단할 수 있었습니다.
바로 이런 문제를 해결하기 위해 WAF가 필수적입니다. WAF를 사용하면 제로데이 공격 방어가 가능해집니다.
AWS 관리형 규칙은 새로운 취약점이 발견되면 자동으로 업데이트됩니다. 또한 가시성 확보도 할 수 있습니다.
CloudWatch 메트릭으로 어떤 공격이 얼마나 차단되었는지 실시간으로 볼 수 있습니다. 위의 코드를 단계별로 살펴보겠습니다.
먼저 aws_wafv2_web_acl 리소스를 생성합니다. scope = "CLOUDFRONT"는 이 WAF가 CloudFront 배포에 연결된다는 의미입니다.
default_action { allow {} }는 규칙에 매치되지 않은 요청은 기본적으로 허용한다는 뜻입니다. 첫 번째 규칙은 SQL 인젝션 차단입니다.
AWSManagedRulesSQLiRuleSet는 AWS가 관리하는 규칙 세트로, SQL 인젝션 패턴을 자동으로 탐지합니다. action { block {}는 매치되면 즉시 403 Forbidden을 반환합니다.
두 번째 규칙은 악성 봇 차단입니다. AWSManagedRulesKnownBadInputsRuleSet는 잘 알려진 악의적인 입력 패턴을 차단합니다.
visibility_config에서 cloudwatch_metrics_enabled = true로 설정하면 CloudWatch에서 차단 통계를 확인할 수 있습니다. 이제 CloudFront를 함께 활용해봅시다.
CloudFront는 CDN이지만, 보안 기능도 강력합니다. 첫째, 오리진 서버의 IP를 숨깁니다.
사용자는 CloudFront의 엣지 로케이션에만 접속하므로, 오리진 서버에 직접 공격할 수 없습니다. 둘째, AWS Shield Standard가 자동으로 활성화되어 DDoS 공격을 방어합니다.
셋째, WAF를 CloudFront에 연결하면 공격이 엣지에서 차단되어 오리진 서버의 부하가 줄어듭니다. 실제 현업에서는 어떻게 활용할까요?
쇼핑몰 서비스를 예로 들어봅시다. 블랙프라이데이 세일 기간에 트래픽이 10배 증가합니다.
동시에 경쟁사가 고용한 봇들이 재고를 확인하려 수천 개의 요청을 보냅니다. WAF의 Rate Limiting 규칙으로 IP당 분당 100개 요청으로 제한합니다.
CloudFront는 정적 리소스를 캐싱하여 오리진 서버의 부하를 80% 줄입니다. 또한 Geo Blocking도 유용합니다.
한국 사용자만 대상으로 하는 서비스라면, 다른 국가의 요청을 WAF에서 차단하여 불필요한 트래픽을 줄일 수 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수는 "WAF 규칙을 너무 엄격하게 설정하는 것"입니다. 정상적인 사용자의 요청까지 차단되면 서비스 이용에 불편을 겪습니다.
따라서 처음에는 count 모드로 설정하여 어떤 요청이 차단될지 확인한 후, block 모드로 전환하는 것이 안전합니다. 또한 비용도 고려해야 합니다.
WAF는 규칙당, 요청당 과금됩니다. 수백만 요청이 오는 서비스에서는 월 수십만 원의 비용이 발생할 수 있습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 도움으로 WAF를 적용한 김개발 씨는 CloudWatch 대시보드를 보며 놀랐습니다.
"하루에 5천 건의 공격이 차단되고 있었네요!" 모르는 사이 WAF가 서비스를 지키고 있었습니다. WAF와 CloudFront를 제대로 구성하면 웹 공격과 DDoS로부터 안전하게 서비스를 보호할 수 있습니다.
여러분도 다음 배포에서 WAF를 적용해 보세요.
실전 팁
💡 - 처음에는 count 모드로 오탐을 확인한 후 block 모드로 전환하세요
- CloudWatch Logs Insights로 차단된 요청을 분석하여 규칙을 튜닝하세요
- AWS Managed Rules는 자동 업데이트되므로 커스텀 규칙보다 우선 사용하세요
4. 암호화 전략 구현
김개발 씨는 데이터베이스에 사용자 비밀번호를 저장하려다가 고민에 빠졌습니다. "평문으로 저장하면 안 되는 건 알겠는데, 어떻게 암호화해야 하지?" 박시니어 씨가 화이트보드를 가리켰습니다.
"암호화에는 전송 중 암호화와 저장 시 암호화가 있습니다."
암호화 전략은 데이터를 보호하기 위해 전송 중(TLS/SSL)과 저장 시(KMS) 암호화를 구현하는 것입니다. 마치 소중한 물건을 금고에 넣고, 운반할 때는 보안 차량을 이용하는 것처럼, 데이터가 이동할 때와 저장될 때 모두 암호화하여 유출 위험을 최소화합니다.
다음 코드를 살펴봅시다.
# Python으로 AWS KMS 사용하기
import boto3
import base64
# KMS 클라이언트 초기화
kms_client = boto3.client('kms', region_name='ap-northeast-2')
# 데이터 암호화 함수
def encrypt_sensitive_data(plaintext, key_id):
"""
민감한 데이터를 KMS로 암호화합니다.
plaintext: 암호화할 평문 데이터
key_id: KMS 마스터 키 ID
"""
response = kms_client.encrypt(
KeyId=key_id,
Plaintext=plaintext.encode('utf-8') # 바이트로 변환
)
# CiphertextBlob은 바이너리이므로 Base64로 인코딩
encrypted = base64.b64encode(response['CiphertextBlob'])
return encrypted.decode('utf-8')
# 데이터 복호화 함수
def decrypt_sensitive_data(encrypted_data):
"""
암호화된 데이터를 복호화합니다.
KMS가 자동으로 올바른 키를 찾아 복호화합니다.
"""
ciphertext = base64.b64decode(encrypted_data)
response = kms_client.decrypt(
CiphertextBlob=ciphertext
)
return response['Plaintext'].decode('utf-8')
# 실제 사용 예제
user_ssn = "123-45-6789" # 주민등록번호
kms_key_id = "arn:aws:kms:ap-northeast-2:123456789012:key/abc-def"
# 암호화하여 DB에 저장
encrypted_ssn = encrypt_sensitive_data(user_ssn, kms_key_id)
print(f"암호화됨: {encrypted_ssn[:50]}...") # 긴 문자열 일부만 출력
# 필요할 때 복호화
decrypted_ssn = decrypt_sensitive_data(encrypted_ssn)
print(f"복호화됨: {decrypted_ssn}")
김개발 씨는 신입 시절, 데이터베이스에 비밀번호를 평문으로 저장했습니다. "어차피 데이터베이스는 프라이빗 서브넷에 있으니까 괜찮겠지?" 하지만 6개월 후, 퇴사한 개발자가 백업 파일을 가져갔다는 소문이 돌았습니다.
모든 사용자의 비밀번호가 그대로 노출된 상태였습니다. CTO가 긴급 회의를 소집했습니다.
"앞으로 모든 민감 데이터는 암호화해서 저장합니다. 예외 없이." 암호화 전략이란 무엇일까요?
암호화는 평문 데이터를 읽을 수 없는 형태로 변환하는 기술입니다. 쉽게 비유하자면, 암호화는 마치 보물을 금고에 넣는 것과 같습니다.
금고를 열 수 있는 열쇠(암호화 키)가 없으면 내용물을 볼 수 없습니다. 데이터가 이동할 때(전송 중)와 저장될 때(저장 시) 모두 암호화하는 것이 보안의 기본입니다.
암호화 없이 데이터를 다루면 어떻게 될까요? 2013년 타겟(Target) 해킹 사건에서는 4천만 개의 신용카드 정보가 유출되었습니다.
결제 데이터가 암호화되지 않은 상태로 전송되어, 중간에 탈취당했습니다. 회사는 2억 9천만 달러의 합의금을 지불했습니다.
암호화가 되어 있었다면, 데이터를 훔쳐도 읽을 수 없었을 것입니다. 바로 이런 문제를 해결하기 위해 암호화 전략이 필수적입니다.
암호화를 사용하면 데이터 유출 시에도 안전합니다. 암호화된 데이터는 키 없이는 의미 없는 랜덤 문자열입니다.
또한 규정 준수를 만족할 수 있습니다. GDPR, HIPAA 등은 개인정보 암호화를 요구합니다.
무엇보다 고객 신뢰를 얻을 수 있다는 큰 이점이 있습니다. 암호화는 크게 두 가지로 나뉩니다.
첫째, 전송 중 암호화(Encryption in Transit)입니다. 데이터가 네트워크를 통해 이동할 때 암호화합니다.
HTTPS(TLS/SSL)가 대표적입니다. 사용자의 브라우저와 서버 사이, 서버와 데이터베이스 사이 모두 TLS를 사용해야 합니다.
AWS에서는 ALB에서 TLS 인증서를 설정하고, RDS 연결 시 ssl=true 옵션을 사용합니다. 둘째, 저장 시 암호화(Encryption at Rest)입니다.
데이터베이스, S3 버킷, EBS 볼륨에 저장될 때 암호화합니다. AWS KMS(Key Management Service)를 사용하면 키 관리를 AWS가 대신 해줍니다.
위의 코드를 단계별로 살펴보겠습니다. kms_client.encrypt() 함수는 평문 데이터를 KMS 마스터 키로 암호화합니다.
KeyId는 KMS 키의 ARN입니다. 암호화된 결과인 CiphertextBlob은 바이너리 데이터이므로, Base64로 인코딩하여 문자열로 변환합니다.
이렇게 하면 데이터베이스의 TEXT 컬럼에 저장할 수 있습니다. kms_client.decrypt() 함수는 암호화된 데이터를 복호화합니다.
중요한 점은 KeyId를 지정하지 않아도 된다는 것입니다. KMS가 암호화할 때 사용한 키 정보를 CiphertextBlob에 포함시키므로, 자동으로 올바른 키를 찾아 복호화합니다.
실제 현업에서는 어떻게 활용할까요? 의료 서비스를 예로 들어봅시다.
환자의 진료 기록은 HIPAA 규정에 따라 반드시 암호화되어야 합니다. RDS 데이터베이스를 생성할 때 storage_encrypted = true로 설정하면, 디스크에 저장되는 모든 데이터가 자동으로 암호화됩니다.
또한 특정 필드(주민등록번호, 신용카드 번호)는 애플리케이션 레벨에서 KMS로 추가 암호화합니다. 이를 다층 암호화(Defense in Depth)라고 합니다.
또한 키 로테이션도 중요합니다. KMS 키는 1년마다 자동으로 로테이션할 수 있습니다.
키가 유출되더라도 피해 범위를 최소화할 수 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수는 "비밀번호를 암호화하는 것"입니다. 비밀번호는 해싱해야지 암호화하면 안 됩니다.
암호화는 복호화가 가능하지만, 해싱은 일방향입니다. 비밀번호는 절대 복호화할 일이 없으므로, bcrypt나 Argon2로 해싱해야 합니다.
또 다른 실수는 "암호화 키를 코드에 하드코딩하는 것"입니다. GitHub에 키를 올리면 몇 분 안에 봇이 스캔하여 탈취합니다.
반드시 AWS Secrets Manager나 KMS를 사용하세요. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 가이드를 따라 김개발 씨는 모든 민감 데이터를 KMS로 암호화했습니다. RDS는 저장 시 암호화를 활성화하고, ALB에는 TLS 인증서를 적용했습니다.
이제 안심하고 잠들 수 있습니다. 암호화 전략을 제대로 구현하면 데이터 유출 사고가 나도 피해를 최소화할 수 있습니다.
여러분도 다음 프로젝트에서 KMS를 적용해 보세요.
실전 팁
💡 - S3 버킷은 기본 암호화(Default Encryption)를 활성화하세요
- RDS 스냅샷도 암호화되는지 확인하세요 (소스 DB가 암호화되어 있어야 함)
- CloudTrail로 KMS 키 사용 이력을 모니터링하여 이상 접근을 탐지하세요
5. 접근 제어 설정
김개발 씨는 새로운 인턴에게 AWS 콘솔 접근 권한을 주려다가 멈칫했습니다. "모든 권한을 줘도 되나?
실수로 프로덕션 DB를 삭제하면?" 박시니어 씨가 말했습니다. "최소 권한 원칙을 따라야 합니다."
접근 제어는 IAM 정책으로 사용자와 서비스에 필요한 최소한의 권한만 부여하는 것입니다. 마치 회사에서 직급별로 출입할 수 있는 구역이 다른 것처럼, 개발자는 개발 환경만, 운영팀은 프로덕션 읽기만 허용하여 실수와 악의적인 행위를 방지합니다.
다음 코드를 살펴봅시다.
# Terraform으로 IAM 정책 설정하기
# 읽기 전용 개발자 역할
resource "aws_iam_role" "developer_readonly" {
name = "DeveloperReadOnly"
# 이 역할을 누가 사용할 수 있는지 정의 (Trust Policy)
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::123456789012:root"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"sts:ExternalId" = "unique-external-id"
}
}
}]
})
}
# S3 읽기 전용 정책
resource "aws_iam_policy" "s3_readonly" {
name = "S3ReadOnlyAccess"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject", # 객체 읽기만 허용
"s3:ListBucket" # 버킷 목록 조회만 허용
]
Resource = [
"arn:aws:s3:::my-app-logs/*", # 로그 버킷의 모든 객체
"arn:aws:s3:::my-app-logs" # 버킷 자체
]
},
{
Effect = "Deny"
Action = [
"s3:DeleteObject", # 삭제 명시적 거부
"s3:PutObject" # 쓰기 명시적 거부
]
Resource = "*"
}
]
})
}
# 역할에 정책 연결
resource "aws_iam_role_policy_attachment" "developer_s3" {
role = aws_iam_role.developer_readonly.name
policy_arn = aws_iam_policy.s3_readonly.arn
}
김개발 씨의 팀에 새로운 인턴 이주니어 씨가 합류했습니다. 팀장이 말했습니다.
"AWS 콘솔 권한을 줘서 로그를 볼 수 있게 해주세요." 김개발 씨는 간단하게 생각했습니다. "그냥 AdministratorAccess를 주면 되겠지?" 2주 후, 끔찍한 일이 벌어졌습니다.
이주니어 씨가 실수로 프로덕션 RDS 인스턴스를 삭제했습니다. "테스트 DB인 줄 알았어요!" 다행히 자동 백업이 있었지만, 4시간의 데이터가 손실되었습니다.
접근 제어란 무엇일까요? 접근 제어는 "누가 무엇을 할 수 있는가"를 정의하는 것입니다.
쉽게 비유하자면, 호텔 키카드와 같습니다. 객실 키카드로는 자기 방만 열 수 있고, 직원 키카드는 모든 방을 열 수 있지만 금고는 못 엽니다.
지배인 키카드는 모든 곳에 접근할 수 있습니다. IAM도 이처럼 역할에 따라 권한을 세밀하게 조정합니다.
접근 제어 없이 시스템을 운영하면 어떻게 될까요? 모든 사람이 관리자 권한을 가지면, 누구나 실수로 혹은 의도적으로 시스템을 망칠 수 있습니다.
2017년 AWS S3 장애는 엔지니어가 실수로 프로덕션 서버를 삭제한 것이 원인이었습니다. 만약 그 엔지니어가 읽기 전용 권한만 있었다면, 사고는 일어나지 않았을 것입니다.
바로 이런 문제를 해결하기 위해 최소 권한 원칙(Principle of Least Privilege)이 필수적입니다. 접근 제어를 사용하면 실수 방지가 가능해집니다.
삭제 권한이 없으면 실수로 삭제할 수 없습니다. 또한 내부자 위협 감소도 할 수 있습니다.
퇴사 예정 직원이 데이터를 빼가는 것을 방지합니다. 무엇보다 감사 추적이 쉬워진다는 큰 이점이 있습니다.
CloudTrail로 누가 언제 무엇을 했는지 모두 기록됩니다. IAM 정책의 구조를 살펴보겠습니다.
첫째, Trust Policy입니다. assume_role_policy는 이 역할을 누가 사용할 수 있는지 정의합니다.
위 코드에서는 특정 AWS 계정만 이 역할을 assume할 수 있고, ExternalId로 추가 검증을 합니다. 둘째, Permission Policy입니다.
aws_iam_policy에서 실제 권한을 정의합니다. Effect: "Allow"는 허용, Effect: "Deny"는 거부입니다.
중요한 점은 명시적 거부가 최우선이라는 것입니다. 어떤 정책이 허용해도, 하나의 정책이 거부하면 최종 결과는 거부입니다.
셋째, Actions과 Resources입니다. s3:GetObject는 S3 객체를 읽는 작업입니다.
Resource: "arn:aws:s3:::my-app-logs/*"는 특정 버킷의 모든 객체를 의미합니다. 이처럼 세밀하게 제어할 수 있습니다.
실제 현업에서는 어떻게 활용할까요? 대기업 조직을 예로 들어봅시다.
개발팀은 개발 계정에서 모든 권한을 가지지만, 프로덕션 계정에서는 읽기만 가능합니다. 운영팀은 프로덕션 계정에서 배포 권한은 있지만, 데이터베이스 스키마 변경은 DBA만 가능합니다.
이런 역할 기반 접근 제어(RBAC)가 표준입니다. 또한 조건부 정책도 유용합니다.
예를 들어 회사 IP에서만 접속할 때만 권한을 부여하거나, MFA(다중 인증)를 사용한 경우에만 민감한 작업을 허용할 수 있습니다. 서비스 간 통신에서는 IAM Role을 사용합니다.
EC2 인스턴스에 IAM Role을 부여하면, 애플리케이션이 액세스 키 없이 S3나 RDS에 접근할 수 있습니다. 액세스 키를 코드에 하드코딩하는 위험을 없앨 수 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수는 "와일드카드 남용"입니다.
Resource: "*"는 모든 리소스를 의미하므로, 의도치 않은 권한을 부여할 수 있습니다. 반드시 특정 ARN으로 제한하세요.
또 다른 실수는 "루트 계정 사용"입니다. AWS 루트 계정은 모든 권한을 가지므로, 일상적인 작업에는 절대 사용하면 안 됩니다.
루트 계정은 MFA를 활성화하고 금고에 보관하세요. 정기적인 권한 검토도 중요합니다.
AWS IAM Access Analyzer를 사용하면 사용하지 않는 권한을 자동으로 찾아줍니다. 퇴사자의 권한은 즉시 제거하고, 프로젝트가 끝난 임시 권한도 삭제해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 사고 이후, 김개발 씨는 IAM 정책을 재설계했습니다.
개발자는 읽기 전용, 시니어 개발자는 개발 환경 쓰기, 테크 리드만 프로덕션 쓰기 권한을 가지도록 변경했습니다. 이주니어 씨는 이제 로그만 볼 수 있습니다.
접근 제어를 제대로 설정하면 실수와 악의적인 행위를 모두 예방할 수 있습니다. 여러분도 다음 프로젝트에서 최소 권한 원칙을 적용해 보세요.
실전 팁
💡 - IAM Policy Simulator로 정책을 테스트한 후 적용하세요
- 그룹 기반 권한 관리를 사용하여 사용자 개별 정책을 최소화하세요
- AWS Organizations의 SCP(Service Control Policy)로 계정 레벨에서 제한하세요
6. 보안 모니터링
김개발 씨는 새벽 3시에 전화를 받았습니다. "서버에서 비트코인 마이닝이 돌아가고 있습니다!" 누군가 서버를 해킹했습니다.
하지만 언제, 어떻게 침입했는지 알 수 없었습니다. 로그가 없었기 때문입니다.
박시니어 씨가 한숨을 쉬었습니다. "보안 모니터링을 해야 한다고 했잖아요."
보안 모니터링은 CloudTrail, GuardDuty, Security Hub로 모든 API 호출을 기록하고 이상 행위를 탐지하는 것입니다. 마치 건물에 CCTV를 설치하고 경비원이 실시간으로 감시하는 것처럼, AWS 리소스에 대한 모든 접근을 추적하여 침해 사고를 조기에 발견합니다.
다음 코드를 살펴봅시다.
# Terraform으로 CloudTrail과 GuardDuty 설정하기
# S3 버킷: CloudTrail 로그 저장소
resource "aws_s3_bucket" "cloudtrail_logs" {
bucket = "my-company-cloudtrail-logs"
# 실수로 삭제 방지
lifecycle {
prevent_destroy = true
}
}
# CloudTrail: 모든 API 호출 기록
resource "aws_cloudtrail" "main" {
name = "production-trail"
s3_bucket_name = aws_s3_bucket.cloudtrail_logs.id
# 모든 리전의 이벤트 기록
is_multi_region_trail = true
# 관리 이벤트와 데이터 이벤트 모두 기록
event_selector {
read_write_type = "All"
include_management_events = true
# S3 객체 레벨 로깅 (누가 어떤 파일을 읽었는지)
data_resource {
type = "AWS::S3::Object"
values = ["arn:aws:s3:::my-sensitive-bucket/*"]
}
}
# 로그 파일 무결성 검증 활성화
enable_log_file_validation = true
# CloudWatch Logs로 실시간 전송
cloud_watch_logs_group_arn = aws_cloudwatch_log_group.cloudtrail.arn
cloud_watch_logs_role_arn = aws_iam_role.cloudtrail_cloudwatch.arn
}
# GuardDuty: 위협 탐지 서비스
resource "aws_guardduty_detector" "main" {
enable = true
# 6시간마다 결과 업데이트
finding_publishing_frequency = "SIX_HOURS"
# S3 보호 활성화
datasources {
s3_logs {
enable = true
}
}
}
# SNS 토픽: GuardDuty 알림
resource "aws_sns_topic" "security_alerts" {
name = "security-alerts"
}
# EventBridge 규칙: 높은 심각도 알림만
resource "aws_cloudwatch_event_rule" "guardduty_high" {
name = "guardduty-high-severity"
event_pattern = jsonencode({
source = ["aws.guardduty"]
detail = {
severity = [7, 8, 9] # High, Critical만
}
})
}
김개발 씨의 서비스는 1년 동안 순조롭게 운영되었습니다. 어느 날 CloudWatch 알람이 울렸습니다.
CPU가 100%에 달했습니다. 서버에 접속해보니 낯선 프로세스가 실행 중이었습니다.
비트코인 마이닝 프로그램이었습니다. 누군가 서버를 해킹했습니다.
하지만 가장 큰 문제는 "언제, 어떻게 침입했는지" 알 수 없다는 것이었습니다. 로그가 없었기 때문입니다.
보안 모니터링이란 무엇일까요? 보안 모니터링은 시스템에서 일어나는 모든 일을 기록하고 분석하는 것입니다.
쉽게 비유하자면, 은행의 CCTV와 같습니다. CCTV는 24시간 녹화하고, 경비원이 실시간으로 감시합니다.
이상한 행동을 하는 사람이 있으면 즉시 알람이 울립니다. AWS에서는 CloudTrail이 CCTV, GuardDuty가 경비원 역할을 합니다.
보안 모니터링 없이 시스템을 운영하면 어떻게 될까요? 침해 사고가 발생해도 모릅니다.
평균적으로 해커는 시스템에 침입한 후 200일 동안 탐지되지 않는다는 연구 결과가 있습니다. 이 기간 동안 데이터를 조금씩 빼가거나, 백도어를 설치합니다.
로그가 없으면 사고 원인 분석도 불가능합니다. "언제부터 해킹당했는지, 어떤 데이터가 유출되었는지" 전혀 알 수 없습니다.
바로 이런 문제를 해결하기 위해 보안 모니터링이 필수적입니다. 보안 모니터링을 사용하면 실시간 위협 탐지가 가능해집니다.
이상한 API 호출이 있으면 즉시 알람이 옵니다. 또한 사고 대응이 빨라집니다.
로그를 분석하여 공격 경로를 파악하고, 피해 범위를 확인할 수 있습니다. 무엇보다 규정 준수를 만족할 수 있다는 큰 이점이 있습니다.
대부분의 보안 규정은 감사 로그 보관을 요구합니다. AWS 보안 모니터링의 핵심 서비스를 살펴보겠습니다.
첫째, CloudTrail입니다. 모든 AWS API 호출을 기록합니다.
누가 언제 어떤 EC2를 시작했는지, 누가 S3 버킷을 삭제했는지 모두 기록됩니다. 위 코드에서 is_multi_region_trail = true로 설정하면 모든 리전의 이벤트를 하나의 트레일로 기록합니다.
enable_log_file_validation = true는 로그 파일이 변조되지 않았음을 암호학적으로 검증합니다. 둘째, GuardDuty입니다.
CloudTrail, VPC Flow Logs, DNS 로그를 분석하여 이상 행위를 자동으로 탐지합니다. 예를 들어 북한 IP에서 접속 시도가 있으면 알려줍니다.
EC2에서 비트코인 마이닝 프로그램이 실행되면 탐지합니다. 기계 학습으로 정상 패턴을 학습하여, 비정상 행위를 찾아냅니다.
셋째, Security Hub입니다. GuardDuty, Inspector, Macie 등 여러 보안 서비스의 결과를 한곳에 모아 보여줍니다.
보안 점수를 매기고, 우선순위가 높은 문제를 먼저 해결하도록 가이드합니다. 위의 코드를 단계별로 살펴보겠습니다.
먼저 S3 버킷을 만들어 CloudTrail 로그를 저장합니다. lifecycle { prevent_destroy = true }는 실수로 버킷을 삭제하는 것을 방지합니다.
로그는 법적으로 보관해야 하는 경우가 많으므로, 삭제 방지는 필수입니다. CloudTrail 설정에서 event_selector는 어떤 이벤트를 기록할지 정의합니다.
include_management_events = true는 EC2 시작, IAM 사용자 생성 같은 관리 이벤트를 기록합니다. data_resource는 S3 객체 읽기/쓰기 같은 데이터 이벤트를 기록합니다.
데이터 이벤트는 양이 많아 비용이 증가하므로, 민감한 버킷만 선택적으로 활성화합니다. GuardDuty는 enable = true만 하면 자동으로 작동합니다.
별도 에이전트 설치가 필요 없습니다. s3_logs { enable = true }는 S3 접근 로그도 분석하여, 데이터 유출 시도를 탐지합니다.
EventBridge 규칙으로 GuardDuty의 높은 심각도 알림만 필터링합니다. severity = [7, 8, 9]는 High, Critical 등급만 SNS로 전송합니다.
Low 심각도까지 알림받으면 너무 많아서 중요한 것을 놓칠 수 있습니다. 실제 현업에서는 어떻게 활용할까요?
금융 회사를 예로 들어봅시다. CloudTrail로 모든 API 호출을 기록하고, 로그는 90일간 S3에 보관합니다.
GuardDuty가 "IAM 사용자가 비정상적으로 많은 API 호출을 했다"고 알립니다. 로그를 분석하니, 탈취된 액세스 키로 고객 데이터를 다운로드하려는 시도였습니다.
즉시 해당 키를 비활성화하고, 피해를 막았습니다. 또한 자동 대응도 구현할 수 있습니다.
Lambda 함수로 EventBridge 이벤트를 받아, 의심스러운 IP를 자동으로 차단하거나, 손상된 EC2를 격리할 수 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수는 "로그를 모으기만 하고 분석하지 않는 것"입니다. 로그는 쌓이지만, 아무도 보지 않으면 무용지물입니다.
CloudWatch Insights로 정기적으로 분석하거나, GuardDuty 알림을 Slack으로 전송하여 즉시 확인하세요. 또 다른 실수는 "로그를 퍼블릭 S3에 저장하는 것"입니다.
CloudTrail 로그에는 민감한 정보가 포함될 수 있습니다. 반드시 암호화하고, 접근 권한을 최소화하세요.
로그 보관 기간도 고려해야 합니다. 규정에 따라 1년, 3년, 7년 보관이 요구될 수 있습니다.
S3 Lifecycle 정책으로 오래된 로그는 Glacier로 이동하여 비용을 절감하세요. 다시 김개발 씨의 이야기로 돌아가 봅시다.
사고 이후, 김개발 씨는 CloudTrail과 GuardDuty를 활성화했습니다. 2주 후, GuardDuty가 알렸습니다.
"중국 IP에서 SSH 브루트포스 공격이 있습니다." 즉시 Security Group을 수정하여 차단했습니다. 이제 서버는 안전합니다.
보안 모니터링을 제대로 구현하면 침해 사고를 조기에 발견하고 대응할 수 있습니다. 여러분도 지금 당장 CloudTrail을 활성화해 보세요.
실전 팁
💡 - CloudTrail 로그는 SIEM(Splunk, ELK)과 연동하여 고급 분석을 하세요
- AWS Config로 리소스 구성 변경 이력을 추적하세요
- GuardDuty의 신뢰할 수 있는 IP 목록으로 오탐을 줄이세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
Spring Cloud Config 서버 구성 완벽 가이드
마이크로서비스 환경에서 설정을 중앙화하여 관리하는 Spring Cloud Config 서버 구성 방법을 단계별로 학습합니다. Git 백엔드부터 암호화까지 실무에 필요한 모든 내용을 다룹니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.