본문 바로가기
Spring | Java | Kotlin

[Spring] Kubernetes 상에서의 Elasticsearch 서버 Self-Signed TLS 연결 (SSL Bundle)

by eFFx 2025. 5. 13.

Kubernetes 상에서 Elasticsearch를 운영 중에 있는데, Self-Signed CA를 적용하였더니 Spring Application에서 연결을 하지 못하는 문제가 발생하였습니다.

 

Spring Boot 3.1에서 추가된 SSL Bundle 기능을 이용하여 인증서를 처리해보겠습니다.

 

먼저 Kubernetes상에서 cert-manager와 ECK를 통해 Elasticsearch를 구성하였다고 가정합니다.

 

관련 설정

더보기

사용한 Certificate

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: {{ .Values.elasticsearch.name }}-cert
  namespace: {{ .Values.namespace }}
spec:
  secretName: {{ .Values.elasticsearch.name }}-cert
  isCA: false
  subject:
    organizations:
      - "eFFx"
  privateKey:
    algorithm: RSA
    size: 2048
    encoding: PKCS1
  issuerRef:
    kind: Issuer
    name: <사용할 Issuer>
  commonName: {{ .Values.elasticsearch.name }}-es-http
  dnsNames:
    - {{ .Values.elasticsearch.name }}-es-http
    - {{ .Values.elasticsearch.name }}-es-http.{{ .Values.namespace }}
    - {{ .Values.elasticsearch.name }}-es-http.{{ .Values.namespace }}.svc
    - {{ .Values.elasticsearch.name }}-es-http.{{ .Values.namespace }}.svc.cluster.local
    - {{ .Values.elasticsearch.name }}-es-internal-http
    - {{ .Values.elasticsearch.name }}-es-internal-http.{{ .Values.namespace }}
    - {{ .Values.elasticsearch.name }}-es-internal-http.{{ .Values.namespace }}.svc
    - {{ .Values.elasticsearch.name }}-es-internal-http.{{ .Values.namespace }}.svc.cluster.local
    - {{ .Values.elasticsearch.name }}-es-transport
    - {{ .Values.elasticsearch.name }}-es-transport.{{ .Values.namespace }}
    - {{ .Values.elasticsearch.name }}-es-transport.{{ .Values.namespace }}.svc
    - {{ .Values.elasticsearch.name }}-es-transport.{{ .Values.namespace }}.svc.cluster.local
  keystores:
    jks:
      create: true
      password: <JKS 비밀번호>

 

그 다음 Spring application.yaml에서 sslBundle을 이용하여, 인증서를 주입합니다.

spring:
  elasticsearch:
    uris: ${ELASTICSEARCH_URI}
    host: ${ELASTICSEARCH_HOST}
    port: ${ELASTICSEARCH_PORT}
    username: ${ELASTICSEARCH_USERNAME}
    password: ${ELASTICSEARCH_PASSWORD}
  ssl:
    bundle:
      jks:
        es:
          reload-on-update: true
          keystore:
            location: ${CERT_PATH}/keystore.jks
            password: ${CERT_PASSWORD}
            type: JKS
          truststore:
            location: ${CERT_PATH}/truststore.jks
            password: ${CERT_PASSWORD}
            type: JKS

 

이렇게 하면 Spring 에서 SslBundles 라고 하는 Bean을 통해 주입받을 수 있습니다.

 

생소한 기능이다 보니 공식 블로그 글을 첨부합니다.

https://spring.io/blog/2023/06/07/securing-spring-boot-applications-with-ssl

 

Securing Spring Boot Applications With SSL

Secure Sockets Layer (SSL) and Transport Layer Security (TLS) are key components of securing communications between systems in a layered or service-oriented architecture. Spring Boot applications in such an architecture often accept incoming network connec

spring.io

 

이후 ElasticsearchConfig를 작성합니다.

import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.ssl.SslBundles
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.data.elasticsearch.client.ClientConfiguration
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration
import java.time.Duration

@Configuration
class ElasticsearchConfig(
    private val sslBundles: SslBundles
) : ElasticsearchConfiguration() {
    @Value("\${spring.elasticsearch.host}")
    private lateinit var host: String

    @Value("\${spring.elasticsearch.port}")
    private lateinit var port: String

    @Value("\${spring.elasticsearch.username}")
    private lateinit var username: String

    @Value("\${spring.elasticsearch.password}")
    private lateinit var password: String

    override fun clientConfiguration(): ClientConfiguration =
        ClientConfiguration.builder()
            .connectedTo("$host:$port")
            .usingSsl(sslBundles.getBundle("es").createSslContext())
            .withBasicAuth(username, password)
            .build()
}

 

SslBundles를 주입받어서 sslContext로 변경하여 주입하면 됩니다.

만약 RestTemplate 같은 경우에는 바로 sslBundle을 사용할 수 있습니다.

 

 

마지막으로 Kubernetes 에서 Deployment를 작성합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: effx-backend
  labels:
    app: effx-backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: effx-backend
  template:
    metadata:
      name: effx-backend
      labels:
        app: effx-backend
    spec:
      containers:
        - name: effx-backend
          image: <image>
          ports:
            - containerPort: 8080
              protocol: TCP
          env:
          ...
            - name: CERT_PATH
              value: /etc/effx/cert # 경로는 아무곳이나 겹치지 않으면 됩니다.
            - name: CERT_PASSWORD
              value: <위에서 작성한 JKS 비밀번호 (가능하면 valueFrom을 사용합시다.)>
          volumeMounts:
            - name: elasticsearch-cert
              mountPath: /etc/effx/cert
      volumes:
        - name: elasticsearch-cert
          secret:
            secretName: elasticsearch-cert
      restartPolicy: Always

 

이렇게 하면 컨테이너에 인증서가 마운트되어 Spring에서 사용할 수 있습니다.

 

 

참고 문서: https://piotrminkowski.com/2024/02/19/spring-boot-ssl-hot-reload-on-kubernetes/