icon

안동민 개발노트

10장 : HTTPS와 네트워크 보안

인증서 실무


TLS와 인증서의 원리를 이해했으니, 이제 실무에서 인증서를 어떻게 발급하고 관리하는지 살펴보겠습니다.


Let's Encrypt와 자동 갱신

Let's Encrypt는 무료로 TLS 인증서를 발급하는 비영리 CA입니다. 2015년 출시 이후, 전 세계 HTTPS 보급률을 극적으로 높인 프로젝트입니다.

Let's Encrypt ACME 프로토콜 동작 흐름
Certbot (클라이언트)                    Let's Encrypt (CA 서버)
    │                                          │
    │── 1. 인증서 발급 요청 ──────────────────→│
    │   "example.com 인증서를 주세요"          │
    │                                          │
    │←─ 2. Challenge 발급 ─────────────────────│
    │   "이 도메인을 소유하고 있다면           │
    │    다음 중 하나를 증명하세요"            │
    │   - HTTP-01: /.well-known/acme-challenge/│
    │   - DNS-01: TXT 레코드 추가              │
    │                                          │
    │── 3. Challenge 응답 ────────────────────→│
    │   (HTTP: 특정 경로에 토큰 배치)          │
    │   (DNS: _acme-challenge.example.com TXT) │
    │                                          │
    │    [CA가 실제 도메인에 접근하여 검증]    │
    │                                          │
    │←─ 4. 인증서 발급 ────────────────────────│
    │   * 서버 인증서 (PEM)                    │
    │   * 중간 CA 인증서                       │
    │   * 유효 기간: 90일                      │
    │                                          │
    │   [certbot이 웹 서버에 인증서 설치]      │
    │   [cron/systemd로 60일마다 자동 갱신]    │
특징설명
무료비용 부담 없이 누구나 HTTPS 적용
자동화ACME 프로토콜로 발급/갱신 자동화
유효 기간 90일키 유출 영향 최소화, 자동 갱신 강제
DV 인증서만도메인 소유 확인만 (기업 신원 확인 없음)
와일드카드DNS-01로 *.example.com 발급 가능
certbot_commands.sh
# Certbot으로 Let's Encrypt 인증서 발급 (Nginx)
sudo certbot --nginx -d example.com -d www.example.com

# 인증서 갱신 테스트 (실제 갱신하지 않음)
sudo certbot renew --dry-run

# 자동 갱신 cron 설정 (60일마다)
echo "0 3 */60 * * certbot renew --quiet" | sudo crontab -

# 발급된 인증서 확인
sudo certbot certificates
# Certificate Name: example.com
#   Domains: example.com www.example.com
#   Expiry Date: 2024-04-15 (VALID: 89 days)

# DNS-01 Challenge로 와일드카드 인증서 발급
sudo certbot certonly --manual --preferred-challenges dns \
  -d "*.example.com" -d "example.com"
인증서 유형 비교
DV (Domain Validation)
  검증 대상: 도메인 소유권만
  발급 시간: 수 분
  비용: 무료 ~ 저렴
  용도: 개인 사이트, 소규모 서비스
  → Let's Encrypt가 이 유형

OV (Organization Validation)
  검증 대상: 도메인 + 조직 실재 확인
  발급 시간: 수 일
  비용: 유료
  용도: 기업 사이트

EV (Extended Validation)
  검증 대상: 도메인 + 조직 + 법적 실재
  발급 시간: 수 주
  비용: 고가
  용도: 금융, 전자상거래
  (과거: 녹색 주소창, 현재: 브라우저가 구분 안 함)

대부분의 클라우드 서비스(AWS ACM, Cloudflare, Vercel)는 인증서를 자동으로 발급하고 갱신해 주므로, 개발자가 직접 Certbot을 실행할 일은 점점 줄어들고 있습니다.


자체 서명 인증서

개발 환경에서 HTTPS를 테스트하려면 인증서가 필요한데, 매번 Let's Encrypt를 사용하기는 번거롭습니다. 이때 자체 서명 인증서(Self-Signed Certificate)를 만들어 사용합니다.

자체 서명 vs CA 서명 비교
CA 서명 인증서
  Root CA → 중간 CA → 서버 인증서
                        ↑ CA가 서명
  → 브라우저가 Root CA를 신뢰 → 체인 검증 성공 → 자물쇠 ✓

자체 서명 인증서
  서버 인증서
    ↑ 자기 자신이 서명
  → 브라우저 신뢰 저장소에 없음 → 검증 실패 → 경고! ✗
self_signed_cert.sh
# OpenSSL로 자체 서명 인증서 생성
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout server.key -out server.crt -days 365 \
  -subj "/CN=localhost"

# 생성된 인증서 내용 확인
openssl x509 -in server.crt -noout -text | head -20

# mkcert 사용 (로컬 CA 설치로 경고 없이 개발)
# 1. 로컬 CA 설치 (최초 1회)
mkcert -install

# 2. localhost용 인증서 생성
mkcert localhost 127.0.0.1 ::1
# → localhost+2.pem (인증서)
# → localhost+2-key.pem (개인키)

# 3. Node.js에서 사용
# const https = require('https');
# const options = {
#   cert: fs.readFileSync('localhost+2.pem'),
#   key: fs.readFileSync('localhost+2-key.pem')
# };

자체 서명 인증서는 절대로 프로덕션 환경에서 사용해서는 안 됩니다.


SNI

SNI(Server Name Indication)는 TLS 핸드셰이크에서 클라이언트가 접속하려는 호스트명을 서버에 알려주는 확장입니다.

SNI가 필요한 이유
하나의 서버(IP: 93.184.216.34)에서 여러 도메인 호스팅
  - example.com
  - shop.example.com
  - blog.example.com

SNI 없이 (과거)
  Client → TLS 연결 → 서버: "어떤 인증서를 보내야 하지?"
  → IP당 하나의 인증서만 가능
  → 도메인마다 별도 IP 필요 (비용 증가)

SNI 있음 (현재)
  Client Hello에 "shop.example.com" 포함
  → 서버: "shop.example.com 인증서를 보내야겠군"
  → 하나의 IP로 여러 도메인의 인증서 제공 가능

┌────────────────────────────────────────────┐
│ Client Hello                               │
│ ┌──────────────────────────────────────┐   │
│ │ TLS Version: 1.3                     │   │
│ │ Cipher Suites: [...]                 │   │
│ │ SNI: shop.example.com  ← 평문 노출!  │   │
│ │ Key Share: [...]                     │   │
│ └──────────────────────────────────────┘   │
└────────────────────────────────────────────┘
         ↑ 도청자가 접속 대상 도메인을 알 수 있음

SNI의 가장 큰 이슈는, 호스트명이 평문으로 전송된다는 것입니다. TLS가 아직 수립되기 전이기 때문입니다.

SNI 프라이버시 진화
                                        프라이버시 수준
SNI (평문)         ▓░░░░░░░░░░  낮음     도메인 노출
ESNI (암호화)      ▓▓▓▓▓▓░░░░░  중간     CloudFlare 등 지원
ECH (전체 암호화)  ▓▓▓▓▓▓▓▓▓▓  높음      Client Hello 전체 암호화
                                         (Inner/Outer 구조)

ECH(Encrypted Client Hello)는 Client Hello 자체를 암호화하여, 도청자가 접속 대상을 알 수 없게 합니다. DNS HTTPS 레코드에서 서버의 공개키를 가져와 Client Hello를 암호화합니다.


mTLS (상호 인증)

일반적인 TLS에서는 서버만 인증서를 제시합니다. mTLS(Mutual TLS)는 서버뿐 아니라 클라이언트도 인증서를 제시하는 양방향 인증입니다.

TLS vs mTLS 비교
일반 TLS (단방향)
  Client ───────→ Server
    "서버가 진짜인지 확인"
    서버만 인증서 제시

  사용자 인증: 별도 (ID/PW, 토큰 등)

mTLS (양방향)
  Client ←──────→ Server
    서버도 인증서 제시
    클라이언트도 인증서 제시
    "양쪽 모두 신원 확인"

  과정
  1. Server → Client: 서버 인증서 제시 (일반 TLS와 동일)
  2. Server → Client: "Certificate Request" (너도 인증서 보내)
  3. Client → Server: 클라이언트 인증서 제시
  4. Server: 클라이언트 인증서 검증
  5. 양방향 신뢰 확립
사용 시나리오설명예시
마이크로서비스서비스 간 상호 인증Istio, Linkerd 서비스 메시
금융 API핀테크 서비스 신원 증명Open Banking API
IoT기기 인증스마트홈, 의료 기기
Zero Trust네트워크 접근 제어BeyondCorp, Cloudflare Access
내부 시스템관리 도구 접근 제한DB 관리 콘솔
mtls_setup.sh
# 1. CA 개인키와 인증서 생성
openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout ca.key -out ca.crt -days 3650 \
  -subj "/CN=My Internal CA"

# 2. 클라이언트 개인키와 CSR 생성
openssl req -newkey rsa:2048 -nodes \
  -keyout client.key -out client.csr \
  -subj "/CN=service-a"

# 3. CA로 클라이언트 인증서 서명
openssl x509 -req -in client.csr \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out client.crt -days 365

# 4. mTLS로 서버 접속 테스트
curl --cert client.crt --key client.key \
  --cacert ca.crt https://internal-api.company.com/health
인증서 관리 자동화 도구 비교
도구             대상              특징
───────────────────────────────────────────────
cert-manager    Kubernetes       자동 발급/갱신, Let's Encrypt 연동
Vault PKI       전체 인프라      HashiCorp, 동적 인증서 발급
SPIFFE/SPIRE    마이크로서비스   서비스 ID 기반, mTLS 자동화
AWS ACM         AWS 서비스       무료, ALB/CloudFront 자동 적용

다음 절에서는 네트워크에서 발생하는 보안 위협과 방어 전략을 살펴보겠습니다.

목차