본문으로 건너뛰기
8.3 클라이언트 TLS/SSL 접속

8.3 클라이언트 TLS/SSL 접속

앞 절에서 본 client CA를 이용하면, 클라이언트는 비밀번호 대신 TLS client certificate로 데이터베이스에 인증할 수 있다. 비밀번호가 오가지 않으므로 유출 위험이 줄고, 인증서 폐기로 접근을 한번에 끊을 수 있다. 이 절에서는 client 인증서를 발급하고, psql로 검증하고, 애플리케이션 Pod에 인증서를 마운트해 접속하는 흐름을 실습 관점에서 정리한다.

인증 흐름

    flowchart TD
  KUBECTL["kubectl cnpg<br/>certificate"]
  SECRET["client cert Secret<br/>tls.crt·tls.key"]
  POD["애플리케이션 Pod<br/>volume mount"]
  RW["cluster-rw 서비스"]
  PG["PostgreSQL<br/>pg_hba: cert"]

  KUBECTL -->|발급| SECRET
  SECRET -->|마운트| POD
  POD -->|sslmode=verify-full| RW --> PG

  classDef node fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
  class KUBECTL,SECRET,POD,RW,PG node
  

1. client 인증서 발급

kubectl cnpg 플러그인으로 특정 사용자용 client 인증서를 발급한다. Cluster의 client CA가 서명하며, 결과는 지정한 이름의 Secret으로 만들어진다.

kubectl cnpg certificate cluster-app \
  --cnpg-cluster cluster-example \
  --cnpg-user app

발급된 인증서의 common name은 PostgreSQL 사용자명(app)과 같아야 한다. cert 인증은 인증서 CN을 데이터베이스 롤로 매핑하기 때문이다. 인증서 내용은 다음으로 확인한다.

kubectl get secret cluster-app \
  -o jsonpath="{.data['tls\.crt']}" \
  | base64 -d | openssl x509 -text -noout | head -n 11

기본 유효기간은 90일이며 CERTIFICATE_DURATION·EXPIRE_CHECK_THRESHOLD로 조정한다.

2. psql로 접속 검증

클라이언트는 세 개의 파일이 필요하다.

파라미터파일역할
sslcerttls.crtclient 공개 인증서
sslkeytls.keyclient 개인 키
sslrootcertca.crt서버 검증용 CA

인증서를 마운트한 Pod 안에서 다음처럼 접속한다. sslmode=verify-full은 서버 인증서와 호스트명까지 검증하는 가장 엄격한 모드다.

kubectl exec -it cert-test -- bash -c \
  "psql 'sslkey=/etc/secrets/app/tls.key \
         sslcert=/etc/secrets/app/tls.crt \
         sslrootcert=/etc/secrets/ca/ca.crt \
         host=cluster-example-rw.default.svc \
         dbname=app user=app sslmode=verify-full' \
   -c 'select version();'"
sslmode는 검증 강도를 정한다. require는 암호화만 하고 서버 신원은 확인하지 않으며, verify-ca는 CA를, verify-full은 CA와 호스트명까지 확인한다. 프로덕션에서는 verify-full을 기본으로 삼는다.

3. 애플리케이션 Pod에 인증서 마운트

애플리케이션에서는 CA Secret과 client 인증서 Secret을 볼륨으로 마운트하고, 접속 문자열을 환경 변수로 넣는다. 아래는 CloudNativePG 문서의 cert-test 예제를 간추린 형태다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cert-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cert-test
  template:
    metadata:
      labels:
        app: cert-test
    spec:
      containers:
      - name: cert-test
        image: ghcr.io/cloudnative-pg/webtest:1.6.0
        env:
        - name: DATABASE_URL
          value: >-
            sslkey=/etc/secrets/app/tls.key
            sslcert=/etc/secrets/app/tls.crt
            sslrootcert=/etc/secrets/ca/ca.crt
            host=cluster-example-rw.default.svc
            dbname=app user=app sslmode=verify-full
        volumeMounts:
        - name: app-cert
          mountPath: /etc/secrets/app
        - name: ca-cert
          mountPath: /etc/secrets/ca
      volumes:
      - name: app-cert
        secret:
          secretName: cluster-app
      - name: ca-cert
        secret:
          secretName: cluster-example-ca

client 인증서 Secret(cluster-app)에는 tls.crt·tls.key가, CA Secret(cluster-example-ca)에는 ca.crt가 들어 있어, 위 마운트 경로가 그대로 psql 파라미터와 맞물린다.

TLS 프로토콜 버전

CloudNativePG는 최소·최대 TLS 버전 모두 기본 TLSv1.3을 쓴다. 구형 클라이언트 라이브러리가 1.3을 지원하지 않으면 접속이 실패할 수 있으므로, 접속 오류가 나면 클라이언트의 TLS 지원 버전을 먼저 확인한다.

정리

  • kubectl cnpg certificate로 사용자별 client 인증서를 Secret으로 발급한다
  • 인증서 CN = PostgreSQL 사용자명. cert 인증이 CN을 롤로 매핑
  • 클라이언트는 sslcert·sslkey·sslrootcert 세 파일로 접속, 프로덕션은 sslmode=verify-full
  • 애플리케이션은 CA·client Secret을 볼륨 마운트하고 접속 문자열을 주입
  • 기본 TLS 버전은 1.3 — 구형 클라이언트 호환성에 주의

다음 절(8.4)에서는 인증서 없이 Secret 기반으로 애플리케이션을 연결하는 표준 방법을 본다.