Chapter 2

보안 완화와 설계 패턴

  • 2.1 위협 처리
  • 2.2 구조적 완화 전략
  • 2.3 접근 정책과 접근 제어
  • 2.4 인터페이스
  • 2.5 통신
  • 2.6 스토리지
  • 2.7 설계 속성
  • 2.8 노출 최소화
  • 2.9 강력한 집행
  • 2.10 중복성
  • 2.11 신뢰와 책임
  • 2.12 안티패턴

위협을 찾았으면 어떻게 대응할지 결정해야 하고, 개별 땜질보다 구조 자체를 안전하게 만드는 게 훨씬 효과적이야. 보안 완화와 설계 패턴을 함께 보자.

위협에 대한 선택지는 네 가지야 — 완화(가능성/영향을 줄이기), 제거(기능 자체를 없애기), 전가(외부 서비스에 맡기기), 수용(알고도 감수하기). 여기서 중요한 건 "수용"과 "무시"는 다르다는 거야. 수용은 인지하고 문서화한 뒤 의사결정자가 승인한 것이고, 무시는 그냥 모르쇠지.

근데 개별 위협을 하나씩 땜질하는 것보다 구조 자체를 안전하게 만드는 게 훨씬 효과적이야. 컴포넌트를 격리해서 하나가 뚫려도 나머지는 안전하게(K8s에서 NetworkPolicy로 서비스 간 통신을 제한하는 것처럼), 최소 권한을 적용해서 DB 접근이 필요 없는 서비스에는 커넥션을 안 주고, 계층화로 각 레이어에서 보안 검사를 수행하면 — 한 겹이 뚫려도 다음 겹이 막아줘. 이게 심층 방어의 실제 모습이야.

데이터가 다뤄지는 세 지점을 기억해야 해. 인터페이스(데이터가 들어오는 곳), 통신(데이터가 이동하는 곳), 스토리지(데이터가 머무는 곳). 인터페이스에서는 모든 외부 입력을 의심하고 화이트리스트로 검증해야 해 — SQL 인젝션은 Prepared Statement, XSS는 출력 인코딩, CSRF는 토큰 검증으로 막고. 통신에서는 TLS가 표준이야. 개발 환경에서 verify=False 쓴 코드가 프로덕션에 배포되는 순간 중간자 공격에 무방비가 돼. Istio 같은 서비스 메시에서 mTLS를 기본 적용하면 서비스 간 통신이 자동으로 암호화되고 상호 인증까지 돼.

스토리지에서는 유휴 데이터 암호화가 기본이고, 비밀번호는 bcrypt/Argon2 같은 느린 해시 함수로 저장해야 해. 그런데 암호화만큼 중요한 게 키 관리야. 암호화 키를 소스 코드에 하드코딩하면 암호화를 아무리 잘 해도 소용없어. Vault나 AWS Secrets Manager 같은 시크릿 관리 시스템에서 관리하고, 정기적으로 로테이션 해야 하지. 로그에 비밀번호나 토큰이 찍히지 않는지도 꼭 확인하고.

접근 제어 모델은 DAC(소유자가 결정), MAC(시스템이 강제), RBAC(역할에 권한 매핑) 중 실무에서는 RBAC가 가장 흔해. K8s RBAC도 이 원리를 따르지. 중요한 건 모델 선택이 아니라, 접근 정책을 먼저 정의하고 그에 맞게 구현하는 순서야. 정책 없이 코드부터 짜면 일관성이 무너져.

이런 완화 전략이 효과를 보려면 설계 자체가 뒷받침해야 해. 좋은 보안 설계의 첫 번째 속성은 단순성이야. 복잡한 시스템은 이해하기 어렵고, 이해하기 어려운 시스템은 보안 분석이 불가능해. 여기에 투명성(설계가 공개되어도 안전해야 진짜 안전한 거지), 검증 가능성(테스트하고 감사할 수 있어야 해), 모듈화(보안 기능을 독립 모듈로 분리)가 더해져.

이 속성들이 갖춰졌다면, 다음은 노출을 최소화하는 거야. 안 쓰는 포트는 닫고, 불필요한 API 엔드포인트는 제거하고, 에러 메시지에 스택 트레이스를 그대로 보여주면 공격자한테 시스템 구조를 알려주는 꼴이야. "혹시 나중에 쓸까?"하고 열어두는 모든 것이 공격 표면이야. DB 서버가 퍼블릭 네트워크에서 접근 가능하면 말이 안 되잖아 — 프라이빗 서브넷에 넣고 앱 서버를 통해서만 접근하게 해야지.

노출을 줄였으면 남은 건 강제로 집행하는 거야. 보안 정책을 정의만 해놓으면 아무 소용없어. 인증/인가를 각 서비스마다 따로 구현하면 일관성이 깨지니까, API 게이트웨이나 미들웨어에서 중앙화해서 처리하고. 시스템이 실패하면 "일단 통과"가 아니라 접근 차단이 기본이어야 해. 감사 로그는 **추가 전용(Append-Only)**으로 만들어서 변조를 원천 차단하고. CI/CD에 SAST, 의존성 스캔, 이미지 스캔을 넣어서 사람이 실수해도 파이프라인이 막아주게 자동화해야 해.

그리고 하나의 방어선만으로는 부족해. WAF가 1차, 애플리케이션 입력 검증이 2차, DB 접근 제어가 3차 — 이렇게 여러 겹을 두면 한 겹이 뚫려도 다음이 막아줘. 백업도 마찬가지야. 랜섬웨어 맞았는데 백업 없으면 끝이고, 백업은 있는데 복구 테스트 안 해봤으면 없는 거나 마찬가지지.

마지막으로 절대 하면 안 되는 것들. "아무도 이 URL 모를 거야"는 보안이 아니라 운에 기대는 거야(모호함에 의한 보안). 소스 코드에 API 키 넣는 건 GitHub에 푸시하는 순간 게임 오버(하드코딩된 시크릿). 자체 암호화 알고리즘 만드는 건 안전할 확률이 거의 제로야(직접 만든 암호화). "내부에서만 쓰니까 괜찮아"가 가장 많은 보안 사고를 일으키는 말 중 하나야(과도한 신뢰). 그리고 기능 다 만들고 "이제 보안 추가하자"는 최악이야 — 보안은 나중에 얹는 게 아니라 처음부터 설계에 녹이는 거야.


정리

2장 읽고 기억할 거 세 가지:

  1. 개별 땜질보다 구조가 먼저야. 격리, 계층화, 최소 권한을 아키텍처 수준에서 잡고, 데이터의 세 지점(인터페이스, 통신, 스토리지) 모두에서 방어해야 해.
  2. 보안 정책은 자동화로 강제 집행해야 해. 중앙화된 인증/인가, Fail Secure 기본값, CI/CD에 보안 스캔 — 사람 의지에 의존하면 뚫려.
  3. 안티패턴을 외워둬. 하드코딩된 시크릿, 자체 암호화, "내부니까 괜찮아", 모호함에 의한 보안 — 이거 하나라도 있으면 구조를 아무리 잘 짜도 무너져.