이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • TLS termination 위치가 앱의 scheme, cookie, redirect 판단에 어떤 영향을 주는가?
  • edge termination, re-encryption, end-to-end TLS, mTLS를 언제 선택하는가?
  • mTLS를 도입할 때 인증서 운영과 장애 대응에서 무엇을 준비해야 하는가?

개요

TLS termination은 암호화된 연결이 어디에서 복호화되는지 정하는 설계다. 많은 서비스는 CDN, cloud load balancer, ingress, reverse proxy 중 한 곳에서 client TLS를 종료하고, 그 뒤의 app으로 HTTP 또는 다시 TLS로 전달한다. 백엔드 개발자는 이 위치를 알아야 secure cookie, redirect URL, client IP, scheme 판단을 올바르게 구현할 수 있다.

  • edge termination은 운영이 단순하고 인증서 관리를 중앙화하기 좋다.
  • re-encryption은 LB에서 TLS를 풀고 backend로 다시 TLS를 맺는다.
  • pass-through는 TLS를 app까지 그대로 전달하지만 L7 routing과 관찰성이 제한될 수 있다.
  • mTLS는 서버 인증에 더해 client 인증서까지 검증해 호출자를 강하게 식별한다.

원리

대표적인 배치는 세 가지다.

Client --TLS--> CDN/LB --HTTP--> App
Client --TLS--> CDN/LB --TLS--> App
Client --TLS passthrough--> App

첫 번째는 단순하지만 내부 구간이 평문이다. 두 번째는 내부 구간도 암호화하지만 LB와 app 사이 인증서, truststore, SNI까지 운영해야 한다. 세 번째는 app이 직접 TLS를 처리하므로 인증서 배포와 reload 책임이 app 운영에 가까워진다.

중요한 판단 기준은 “어디서 암호화를 푸는가”가 아니라 “그 지점 이후를 어떤 신뢰 경계로 볼 것인가”다.

Forwarded Header와 앱 판단

LB에서 TLS가 종료되면 app은 실제 client가 HTTPS로 접속했는지 직접 알 수 없다. 이때 X-Forwarded-Proto, X-Forwarded-Host, Forwarded, X-Forwarded-For 같은 header를 보고 판단한다.

Spring Boot에서는 proxy 환경에 맞게 forwarded header 처리가 필요하다.

server:
  forward-headers-strategy: framework

단, 모든 client가 임의로 보낸 forwarded header를 믿으면 보안 문제가 된다. app은 신뢰 가능한 proxy에서 들어온 header만 사용해야 하고, edge proxy는 외부에서 들어온 forwarding header를 정리하거나 덮어써야 한다.

Secure Cookie와 Redirect

termination 위치를 잘못 이해하면 다음 문제가 생긴다.

  • 앱은 HTTP로 들어왔다고 판단해 Secure cookie를 만들지 않는다.
  • 로그인 후 redirect URL이 http://로 만들어진다.
  • HSTS와 HTTPS redirect가 proxy와 app 양쪽에서 중복되어 redirect loop가 생긴다.
  • OAuth callback URL의 scheme/host가 실제 외부 URL과 달라 인증이 실패한다.

이 문제는 앱 코드만 봐서는 잘 보이지 않는다. 외부 URL, LB listener, proxy header, framework 설정을 같이 봐야 한다.

mTLS의 역할

mTLS는 서버가 client 인증서를 검증하는 방식이다. 내부 API, B2B 연동, admin endpoint, service mesh에서 많이 쓰인다. 장점은 토큰 탈취만으로는 호출하기 어렵고, 네트워크 경계 안에서도 호출 주체를 인증서 identity로 구분할 수 있다는 점이다.

하지만 mTLS는 운영 부담이 크다.

  • client 인증서 발급과 배포가 필요하다.
  • 만료 전 회전 절차가 필요하다.
  • 폐기된 인증서를 막는 정책이 필요하다.
  • 인증서 subject/SAN을 애플리케이션 권한 모델과 어떻게 매핑할지 정해야 한다.

코드와 설정으로 확인하기

Nginx에서 backend로 proxy하면서 forwarding header를 명시한다.

location /api/ {
    proxy_pass http://app_upstream;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

mTLS를 검증하는 server block 예시다.

server {
    listen 443 ssl;
    server_name partner-api.example.com;
 
    ssl_certificate /etc/nginx/certs/server.crt;
    ssl_certificate_key /etc/nginx/certs/server.key;
    ssl_client_certificate /etc/nginx/certs/partner-ca.crt;
    ssl_verify_client on;
 
    location / {
        proxy_set_header X-Client-Cert-Subject $ssl_client_s_dn;
        proxy_pass http://app_upstream;
    }
}

클라이언트 인증서를 넣어 호출한다.

curl -v https://partner-api.example.com/orders \
  --cert ./client.crt \
  --key ./client.key \
  --cacert ./server-ca.crt

인프라 협업 포인트

  • termination 위치별로 “누가 인증서를 소유하고, 누가 갱신하고, 누가 reload하는지”를 표로 정한다.
  • app 팀은 외부 URL, 내부 upstream URL, trusted proxy 대역, forwarded header 처리 방식을 인프라 팀에 알려야 한다.
  • 보안 팀과는 mTLS client identity가 권한 부여와 감사 로그에서 어떻게 쓰이는지 합의한다.
  • 장애 대응 문서에는 client-to-LB TLS와 LB-to-App TLS를 각각 검증하는 명령을 둔다.

실전 팁

  • 내부 구간이 private network라고 해서 무조건 평문이 괜찮은 것은 아니다. 규제, 멀티테넌시, 운영자 접근 범위, 패킷 캡처 가능성을 보고 결정한다.
  • mTLS를 처음 도입할 때는 모든 endpoint에 한 번에 켜기보다 파트너 API나 admin API처럼 가치가 큰 경계부터 적용한다.
  • service mesh mTLS는 앱 코드를 줄여 주지만, 인증서 만료와 sidecar 장애는 여전히 운영 이슈다.
  • 개인 프로젝트에서는 managed LB termination으로 시작하고, admin endpoint는 VPN, IP allowlist, OAuth, mTLS 중 현실적인 조합을 고른다.
  • proxy가 client certificate 정보를 header로 넘길 때는 외부 client가 같은 header를 위조하지 못하도록 edge에서 삭제 후 재주입해야 한다.

위험 신호!

  • 앱이 X-Forwarded-Proto를 믿지만 trusted proxy 설정이 없다.
  • mTLS 인증서 만료 알림이 client 소유 팀에 전달되지 않는다.
  • 내부 TLS를 켰지만 app의 truststore 배포 방식이 수동이다.
  • redirect loop를 앱 코드 문제로만 보고 LB listener와 HTTPS redirect 정책을 확인하지 않는다.

확인 질문

확인 질문

  • LB에서 TLS를 종료하면 앱이 HTTPS 여부를 어떻게 알 수 있는가?
    • 신뢰 가능한 proxy가 넣은 forwarded header를 framework가 해석하도록 설정해야 한다.
  • mTLS가 일반 TLS와 다른 핵심은 무엇인가?
    • 서버 인증뿐 아니라 서버가 client 인증서도 검증해 호출자 신원을 확인한다는 점이다.
  • forwarded header를 무조건 믿으면 왜 위험한가?
    • 외부 사용자가 header를 위조해 scheme, host, client IP 판단을 속일 수 있기 때문이다.

참고 문서