mTLS Overview
· 약 12분
mTLS는 mutual TLS의 약자로, 서버와 클라이언트가 서로를 인증하는 TLS 통신 방식입니다.
일반 HTTPS보다 더 높은 수준의 보안 통신이 요구되는 핀테크, 헬스케어, 마이크로서비스 환경에서 널리 사용됩니다.
✅ TLS vs mTLS 비교
| 항목 | TLS (일반 HTTPS) | mTLS (mutual TLS) |
|---|---|---|
| 인증 방향 | 서버만 인증 | 서버 ↔ 클라이언트 상호 인증 |
| 사용 사례 | 웹사이트 접속 | 금융 API, 서비스 간 통신 |
| 클라이언트 인증서 | 없음 | 필수 |
| 보안 수준 | 기본 보안 | 강력한 보안 (양방향 신뢰) |
🔐 mTLS 통신 흐름
Client Server
│ │
│ —— Client Hello ———→ │ ← TLS 시작
│ ←— Server Certificate —— │ ← 서버 인증서 검증
│ —— Client Certificate —→ │ ← 클라이언트 인증서 제공
│ ←— 인증 완료 후 암호화 세션 │ ← 세션 키 협상 완료
│ ←—— Encrypted Data ——→ │ ← 양방향 암호화 통신
📦 구성 요소
| 구성 요소 | 설명 |
|---|---|
| CA (Certificate Authority) | 인증서 발급자 |
| Server Certificate | 서버 인증서 |
| Client Certificate | 클라이언트 인증서 |
| Private Key | 인증서에 대응하는 개인 키 |
| Root / Intermediate CA | 신뢰 체계 구성 |
🎯 mTLS를 사용하는 이유
🔐 보안 강화
- 중간자 공격(MITM) 방지
- 데이터 위/변조 방지
🧩 마이크로서비스 보안
- Kubernetes + Istio 환경에서 Pod 간 mTLS 통신
- 서비스 간 ID 기반 신뢰 모델 제공
🏦 금융/핀테크 API
- PG사, 은행, KYC 연동 시 mTLS 필수
- 인증 기관 요구 조건 충족
🧪 실전 예시
Istio에서 mTLS 활성화 예
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: my-service
spec:
mtls:
mode: STRICT
- 해당 네임스페이스 내 서비스 간 통신을 mTLS로 강제함
Spring web-client mTLS 구성 예
MtlsHttpClientConfig
import io.netty.handler.ssl.SslContextBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import reactor.netty.http.client.HttpClient
import java.io.FileInputStream
import java.security.KeyStore
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import java.security.SecureRandom
@Configuration
class MtlsHttpClientConfig {
@Bean
fun mtlsHttpClient(): HttpClient {
val keyStorePath = "client.p12"
val keyStorePassword = "changeit".toCharArray()
// Load client certificate
val keyStore = KeyStore.getInstance("PKCS12")
FileInputStream(keyStorePath).use {
keyStore.load(it, keyStorePassword)
}
val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
kmf.init(keyStore, keyStorePassword)
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
tmf.init(keyStore)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(kmf.keyManagers, tmf.trustManagers, SecureRandom())
return HttpClient.create().secure { spec ->
spec.sslContext(sslContext)
}
}
}
WebClientConfig
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.web.reactive.function.client.WebClient
import reactor.netty.http.client.HttpClient
@Configuration
class WebClientConfig {
@Bean
fun mtlsWebClient(mtlsHttpClient: HttpClient): WebClient {
return WebClient.builder()
.baseUrl("https://secure.api.example.com")
.clientConnector(ReactorClientHttpConnector(mtlsHttpClient))
.build()
}
}
web-client 사용 예시
MtlsSecureService
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
import java.time.Duration
@Service
class MtlsSecureService(
private val mtlsWebClient: WebClient
) {
fun fetchSecureData(): Mono<String> {
return mtlsWebClient.get()
.uri("/secure-data")
.retrieve()
.bodyToMono(String::class.java)
.timeout(Duration.ofSeconds(5))
}
}
web-client 사용 예시 (coroutine version)
Dependencies
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
}
사용 예시
import kotlinx.coroutines.reactor.awaitSingle
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
@Service
class MtlsSecureService(
private val mtlsWebClient: WebClient
) {
suspend fun fetchSecureData(): String {
return mtlsWebClient.get()
.uri("/secure-data")
.retrieve()
.bodyToMono(String::class.java)
.awaitSingle() // 코루틴으로 변환
}
}
Node.js 클라이언트 mTLS 구성 예
const fs = require('fs');
const https = require('https');
const options = {
hostname: 'secure.api.example',
port: 443,
path: '/',
method: 'GET',
key: fs.readFileSync('client-key.pem'),
cert: fs.readFileSync('client-cert.pem'),
ca: fs.readFileSync('ca-cert.pem'),
};
const req = https.request(options, res => {
console.log('status:', res.statusCode);
});
req.end();
⚠️ 도입 시 유의사항
| 항목 | 유의사항 |
|---|---|
| 인증서 만료 관리 | cert-manager, Vault 등을 통한 자동 갱신 필요 |
| 클라이언트 키 관리 | 인증서 유출 위험 대비 필요 (e.g. HSM, Vault 사용) |
| 성능 이슈 | 최초 TLS Handshake 비용 발생, 세션 재사용 고려 |
| 디버깅 난이도 | 인증 오류 시 로그 확인 및 추적이 어려울 수 있음 |
🔧 관련 도구
| 목적 | 도구 |
|---|---|
| 인증서 발급/갱신 | cert-manager, Let's Encrypt, HashiCorp Vault |
| 서비스 간 인증 | Istio, Linkerd (Service Mesh) |
| 클라이언트 인증 관리 | PKCS#11, AWS IoT X.509 인증 |
📌 요약
mTLS는 TLS 기반의 양방향 인증 방식으로, 클라이언트와 서버가 모두 자신을 증명해야 통신이 가능함- 마이크로서비스, 핀테크, 인증 API 환경에서 보안 강화에 탁월
- 인증서 관리 자동화와 운영 안정성이 관건
🔐 Kubernetes 내 mTLS 사용 여부와 성능 영향
Kubernetes 클러스터 내 서비스 간 통신에서 **mTLS(Mutual TLS)**를 사용할지 여부는 보안성과 성능의 균형을 고려해 결정해야 합니다. 아래는 그에 대한 상세 설명입니다.
✅ 1. mTLS 도입 시 성능 손실은 있는가?
예, 있습니다. 주요 원인은 다음과 같습니다:
| 성능 영향 요소 | 설명 |
|---|---|
| TLS 핸드셰이크 비용 | 연결마다 RSA/ECDSA 기반 핸드셰이크 발생 → CPU 부하 |
| 암호화/복호화 연산 | 대칭키 기반 암복호화도 일부 CPU 부담 유발 |
| 인증서 검증 비용 | 클라이언트/서버 모두 인증서 체인 검 증 |
| 네트워크 지연 | 1-RTT 추가 (TLS 핸드셰이크 지연) |
📌 일반적인 K8s 환경에서는 5~15% 수준의 latency overhead,
CPU 사용량은 mTLS 사용 시 최대 30%까지 증가할 수 있음 (서비스에 따라 다름)
✅ 2. 그렇다면 내부 DNS로 호출하면 mTLS 없이도 안전한가?
그렇지 않습니다. 이유는 다음과 같습니다:
| 위험 요소 | 설명 |
|---|---|
| Flat Network 구조 | 기본적으로 모든 Pod 간 통신 허용 (네트워크 정책 없음) |
| 악성 코드 유입 가능성 | 하나의 compromised pod가 다른 pod로 접근 가능 |
| 네임스페이스 격리 불충분 | 네트워크 단위 격리는 별도 정책 필요 |
| DNS 스푸핑 가능성 | Pod DNS를 악의적으로 조작할 수 있음 |
즉, "같은 클러스터 안이라서 안전하다"는 가정은 위험합니다.
✅ 3. 보통 어떻게 설계하나?
| 구분 | 권장 방안 |
|---|---|
| 민감 서비스 (인증/결제 등) | ✅ mTLS 적용 (STRICT 모드) |
| 내부 비중요 서비스 | ❌ mTLS 선택적 적용 또는 PERMISSIVE |
| Sidecar 기반 Istio 사용 시 | ✅ PeerAuthentication으로 namespace 단위 적용 |
| 고성능 서비스 | ✅ gRPC + HTTP/2 + connection reuse + mTLS 적용 |
| 성능 최적화 | ✅ TLS 세션 재사용, Keep-Alive, Envoy 튜닝 등 고려 |
✅ 4. Istio 기반 구성 예시
PERMISSIVE (선택적 mTLS 허용)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: internal-namespace
spec:
mtls:
mode: PERMISSIVE
STRICT (강제 mTLS)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: secure-services
namespace: payment
spec:
mtls:
mode: STRICT
✅ 결론 요약
| 항목 | 요약 |
|---|---|
| 🔐 mTLS 사용 시 | 보안 강력해지지만 성능 손실 있음 (CPU/Latency ↑) |
| 🌐 내부 DNS 호출만으로는 | DNS Spoofing/Pod 침해에 취약 |
| 🛠️ 권장 전략 | 중요 서비스만 mTLS 적용 + NetworkPolicy 병행 |
| 💡 대안 기술 | eBPF 기반 Cilium, SPIRE, mTLS offloading 등 고려 가능 |
📌 참고
- 클러스터 보안은 네트워크 정책, 서비스 인증, 접근 통제 등이 복합적으로 구성되어야 안전합니다.
- mTLS는 중요한 보안 레이어 중 하나이며, 반드시 전체 성능과 운영 전략 속에서 판단해야 합니다.
🔐 mTLS Offloading 구성 가이드 (Kubernetes + Service Mesh)
mTLS Offloading은 mTLS 통신의 암복호화 및 인증을 애플리케이션이 아닌 별도의 계층(프록시/사이드카/Gateway)에서 수행하도록 하는 아키텍처입니다. 이 구성은 성능 최적화와 보안 관리를 동시에 만족시킬 수 있습니다.
✅ mTLS Offloading이란?
TLS 통신의 인증서 검증, 핸드셰이크, 암복호화 작업을 애플리케이션 외부에서 처리하는 구조
[Client] ⇄ [Sidecar / Proxy (mTLS 처리)] ⇄ [Application (HTTP/plain gRPC)]
🔧 오프로드 구성 방식들
1️⃣ Istio + Envoy 기반 (Sidecar 방식)
- Envoy Sidecar가 mTLS 처리
- App은 내부적으로 평문 HTTP로 Envoy와 통신
[Pod A]
├── [App Container] ←→ localhost HTTP
└── [Envoy Sidecar] ⇄⇄⇄⇄ mTLS ⇄⇄⇄⇄ [Envoy Sidecar]
↑
[Pod B] [App Container]
2️⃣ Ingress Gateway에서 TLS 종료 + 내부는 mTLS (반오프로드)
[External Client]
⇄ TLS
[Ingress Gateway (Envoy)] ⇄ mTLS ⇄ [Internal Services]
3️⃣ 외부 프록시 또는 Node Local Sidecar
- Linkerd, Consul Connect, Cilium 등 Mesh가 mTLS 전담
- 앱은 일반 HTTP만 사용
