카테고리 : MySQL/기술노트

MySQL 네트워크 암호화: TLS 설정, 인증서 검증, 운영 점검 쿼리

MySQL 연결 구간에서 TLS를 어떻게 강제하고, 인증서를 어떻게 검증하며, 운영 중 어떤 쿼리로 암호화 상태를 점검해야 하는지 정리한다.

저자: MySQL 기술 노트 작성: 2026.06.28 약 11분 6,040자
다운로드

1. 왜 MySQL 네트워크 암호화를 운영 항목으로 다뤄야 하는가

MySQL 보안을 말할 때 많은 팀은 계정 권한, SQL injection, 데이터 파일 암호화, 백업 암호화부터 떠올린다. 그러나 실제 운영 환경에서 가장 먼저 노출되는 경계는 종종 클라이언트와 MySQL 서버 사이의 네트워크 구간이다. 애플리케이션 서버, 배치 노드, Bastion, ETL 워커, BI 도구, 프록시 계층이 서로 다른 호스트에 분리되어 있으면 인증 정보와 조회 결과는 네트워크를 지나간다. 이 구간이 평문이면 다음 위험이 동시에 생긴다.

  • 계정 비밀번호나 인증 토큰이 노출될 수 있다.
  • 조회 결과와 쓰기 요청이 중간자 공격(man-in-the-middle)에 노출될 수 있다.
  • 내부망이라는 이유만으로 보안을 완화했다가 프록시, VPN, VPC Peering, 하이브리드 구간에서 예상치 못한 위험이 커질 수 있다.
  • 감사나 규제 대응에서 “전송 중 암호화(encryption in transit)” 요구사항을 만족하지 못할 수 있다.

MySQL의 TLS는 단순히 “암호화 켠다” 수준의 체크박스가 아니다. 서버가 어떤 인증서를 제시하는지, 클라이언트가 그 인증서를 어떤 수준까지 검증하는지, 운영자가 실제 연결이 암호화되었는지 어떻게 증명하는지까지 포함해야 비로소 운영 설계가 완성된다.

이 글의 목적은 다음 세 가지를 한 번에 정리하는 것이다.

  1. MySQL TLS 연결이 어떤 흐름으로 성립하는가
  2. require_secure_transport, 인증서 체인, --ssl-mode를 어떻게 해석해야 하는가
  3. 운영 중 어떤 SQL과 명령으로 실제 암호화 상태를 점검해야 하는가

2. TLS가 보호하는 것과 보호하지 않는 것

먼저 범위를 분리해야 한다. MySQL 네트워크 암호화는 전송 중 데이터(in transit) 를 보호한다. 즉, 클라이언트와 서버 사이를 흐르는 인증 교환, SQL 텍스트, 결과 집합, 프로토콜 메타데이터가 암호화된다. 반면 다음 항목은 TLS만으로 해결되지 않는다.

  • 서버 내부 디스크의 데이터 파일 암호화
  • 백업 파일 암호화
  • 잘못 설계된 계정 권한
  • 애플리케이션이 평문으로 남기는 로그
  • 클라이언트 호스트 자체가 침해되었을 때의 메모리 노출

따라서 운영자는 TLS를 “모든 보안을 대신하는 기능”이 아니라, 인증·권한·감사·비밀 관리 체계와 함께 구성되는 전송 구간 보호층으로 이해해야 한다.

3. MySQL TLS 연결의 내부 흐름

MySQL 연결은 TCP 연결 직후 곧바로 SQL을 실행하지 않는다. 먼저 서버와 클라이언트가 protocol capability와 인증 방식을 교환하고, 필요하면 TLS 협상을 수행한 뒤, 인증이 성공하면 세션이 열린다. 이때 중요한 운영 포인트는 다음과 같다.

  • 서버가 제시하는 인증서의 신뢰 체인
  • 클라이언트가 서버 이름(hostname)까지 검증하는지 여부
  • TLS가 필수인지, 평문 fallback이 가능한지 여부
  • 프록시나 로드밸런서가 TLS 종료 지점이 되는지 여부
sequenceDiagram
    autonumber
    participant C as Client
    participant P as Proxy(Optional)
    participant S as MySQL Server

    C->>P: TCP connect / ClientHello
    alt 프록시가 TLS를 종료하는 경우
        P->>C: Server Certificate
        C->>P: TLS handshake complete
        P->>S: 별도 TLS 또는 평문 연결
    else 클라이언트가 MySQL에 직접 TLS 연결
        S->>C: Handshake + TLS capability
        C->>S: TLS negotiation / Handshake response
        S->>C: Server Certificate
        C->>S: 인증서 검증 후 인증 정보 전송
        S->>C: OK / ERR
    end

운영 해석에서 가장 자주 놓치는 지점은 암호화 여부와 인증서 검증 수준이 별개라는 사실이다. 연결이 TLS로 암호화되어도 클라이언트가 서버 인증서를 제대로 검증하지 않으면 중간자 공격에 취약할 수 있다. 반대로 서버에 인증서가 있어도 애플리케이션이 PREFERRED 같은 느슨한 모드로 접속하면 장애 시 평문 연결로 되돌아갈 가능성을 남길 수 있다.

4. 서버 측 설정: TLS를 켠다는 것의 의미

MySQL 서버 관점에서는 TLS 운영이 대체로 다음 축으로 나뉜다.

4.1 인증서와 개인키

서버는 최소한 다음 요소를 준비해야 한다.

  • 신뢰할 CA 또는 사설 CA 체인
  • 서버 인증서(ssl_cert)
  • 서버 개인키(ssl_key)
  • 필요 시 클라이언트 인증서 검증용 CA(ssl_ca)

이때 운영자가 반드시 기억할 점은 서버 인증서의 Common Name(CN) 또는 SAN(Subject Alternative Name)이 실제 접속 hostname과 맞아야 VERIFY_IDENTITY가 성립한다는 것이다. 예를 들어 애플리케이션은 db-prod.internal.example.com으로 접속하는데 인증서는 mysql01.internal.example.com만 포함한다면, 암호화는 가능해도 엄격한 hostname 검증은 실패한다.

4.2 TLS 강제 여부

MySQL 8.0에서는 require_secure_transport가 가장 직접적인 강제 스위치다.

  • ON: 보안 전송이 아닌 연결을 거부한다.
  • OFF: 클라이언트와 설정에 따라 평문 연결이 가능할 수 있다.

운영 관점에서는 단순히 이 값을 ON으로 두는 것만으로 끝나지 않는다. 애플리케이션 드라이버가 실제로 TLS를 사용하도록 설정되어 있는지, 프록시가 TLS를 종료하는 구조인지, 로컬 소켓 연결 예외를 어떻게 관리할지까지 같이 점검해야 한다.

4.3 허용 TLS 버전

tls_version은 서버가 허용하는 TLS 프로토콜 버전을 제한한다. 오래된 버전 호환성을 이유로 너무 넓게 열어 두면 보안 수준이 약해질 수 있고, 반대로 너무 공격적으로 줄이면 구형 드라이버가 연결하지 못할 수 있다. 따라서 이 변수는 “보안팀 정책”과 “실제 클라이언트 호환성”의 교차점으로 다뤄야 한다.

4.4 관리용 접속과 일반 접속

환경에 따라 관리 포트, 복제 채널, 프록시 백엔드 연결, 운영자 수동 접속이 모두 다른 경로를 사용할 수 있다. 이때 한 경로만 TLS를 쓰고 나머지가 평문이면 설계상 빈틈이 생긴다. 특히 장애 대응용 임시 스크립트나 오래된 배치가 여전히 평문으로 붙는 경우가 잦다.

5. 클라이언트 측 검증: 암호화만으로는 충분하지 않다

MySQL 클라이언트와 다수의 드라이버는 TLS 관련 검증 단계를 제공한다. 이름은 커넥터마다 조금 다를 수 있지만, 운영적으로는 보통 다음 순서로 이해하면 된다.

  • DISABLED: TLS를 사용하지 않는다.
  • PREFERRED: 가능하면 TLS를 쓰지만 실패 시 평문 fallback 가능성이 있다.
  • REQUIRED: TLS는 반드시 사용하지만, 인증서 체인이나 hostname 검증은 충분하지 않을 수 있다.
  • VERIFY_CA: TLS와 CA 체인 검증을 수행한다.
  • VERIFY_IDENTITY: TLS와 CA 체인 검증에 더해 서버 이름까지 검증한다.

실무에서는 REQUIREDVERIFY_IDENTITY를 혼동하는 경우가 매우 많다. REQUIRED는 “암호화는 하겠다”에 가깝고, VERIFY_IDENTITY는 “내가 접속한 서버가 정말 기대한 서버인지까지 확인하겠다”에 가깝다. 운영 신뢰 모델이 중요한 서비스라면 기본 목표는 VERIFY_IDENTITY에 가깝게 잡는 편이 안전하다.

6. 운영자가 먼저 확인할 서버 변수

다음 SQL은 TLS 운영 상태를 점검할 때 가장 먼저 실행할 수 있는 안전한 출발점이다. 이 쿼리는 현재 서버 버전과 TLS 강제, 인증서 경로, 허용 버전 같은 핵심 변수를 보여 준다.

SELECT VERSION() AS mysql_version;

SHOW VARIABLES
WHERE Variable_name IN (
  'have_ssl',
  'require_secure_transport',
  'ssl_ca',
  'ssl_capath',
  'ssl_cert',
  'ssl_key',
  'tls_version',
  'admin_tls_version'
);

실행 결과(MySQL 8.0.46):

mysql> SELECT VERSION() AS mysql_version;

+---------------+
| mysql_version |
+---------------+
| 8.0.46        |
+---------------+
1 row in set (0.00 sec)

mysql> SHOW VARIABLES
    -> WHERE Variable_name IN (
    ->   'have_ssl',
    ->   'require_secure_transport',
    ->   'ssl_ca',
    ->   'ssl_capath',
    ->   'ssl_cert',
    ->   'ssl_key',
    ->   'tls_version',
    ->   'admin_tls_version'
    -> );

+--------------------------+-----------------+
| Variable_name            | Value           |
+--------------------------+-----------------+
| admin_tls_version        | TLSv1.2,TLSv1.3 |
| have_ssl                 | YES             |
| require_secure_transport | OFF             |
| ssl_ca                   | ca.pem          |
| ssl_capath               |                 |
| ssl_cert                 | server-cert.pem |
| ssl_key                  | server-key.pem  |
| tls_version              | TLSv1.2,TLSv1.3 |
+--------------------------+-----------------+
8 rows in set (0.01 sec)

이 결과를 읽을 때의 핵심 해석은 다음과 같다.

  • require_secure_transport=ON이면 적어도 서버 정책상 평문 연결은 허용하지 않으려는 상태다.
  • ssl_ca, ssl_cert, ssl_key 경로가 비어 있지 않다고 해서 곧바로 hostname 검증까지 안전하다는 뜻은 아니다.
  • tls_version은 허용 버전 폭을 보여 주지만, 실제 세션이 어떤 버전으로 협상되었는지는 별도 확인이 필요하다.

7. 현재 세션이 실제로 TLS를 사용하는지 확인하는 방법

서버 설정이 맞아 보여도, 실제 연결 세션이 암호화되었는지는 별도 점검해야 한다. 특히 프록시, 오래된 커넥터, 로컬 소켓 접속, 테스트 스크립트가 섞인 환경에서는 “서버는 TLS 가능”하지만 “일부 세션은 평문”인 상태가 생길 수 있다.

먼저 가장 간단한 방법은 현재 세션의 SSL 상태 변수를 보는 것이다.

SHOW SESSION STATUS LIKE 'Ssl_version';
SHOW SESSION STATUS LIKE 'Ssl_cipher';

실행 결과(MySQL 8.0.46):

mysql> SHOW SESSION STATUS LIKE 'Ssl_version';

+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Ssl_version   |       |
+---------------+-------+
1 row in set (0.00 sec)

mysql> SHOW SESSION STATUS LIKE 'Ssl_cipher';

+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Ssl_cipher    |       |
+---------------+-------+
1 row in set (0.00 sec)

이 결과는 현재 접속 세션이 어떤 TLS 버전과 cipher를 사용 중인지 보여 준다. 값이 비어 있으면 TLS가 적용되지 않았거나, 현재 접속 경로가 로컬 소켓처럼 네트워크 TLS와 다른 경로일 수 있다. 따라서 운영 현장에서는 애플리케이션이 실제 사용하는 접속 방식으로 동일한 점검을 수행해야 의미가 있다.

8. Performance Schema로 TLS 관측 범위를 넓히기

MySQL 8.0에서는 performance_schema.tls_channel_status를 통해 TLS 채널 속성을 점검할 수 있다. 이 테이블은 버전과 설정에 따라 채워지는 값이 다를 수 있으므로, 먼저 객체 존재 여부를 확인한 뒤 조회하는 편이 안전하다.

SHOW TABLES FROM performance_schema LIKE 'tls_channel_status';

SELECT CHANNEL, PROPERTY, VALUE
FROM performance_schema.tls_channel_status
WHERE PROPERTY IN (
  'Enabled',
  'Current_tls_version',
  'Current_tls_cert',
  'Ssl_accepts',
  'Ssl_finished_accepts'
)
ORDER BY CHANNEL, PROPERTY;

실행 결과(MySQL 8.0.46):

mysql> SHOW TABLES FROM performance_schema LIKE 'tls_channel_status';

+---------------------------------------------------+
| Tables_in_performance_schema (tls_channel_status) |
+---------------------------------------------------+
| tls_channel_status                                |
+---------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT CHANNEL, PROPERTY, VALUE
    -> FROM performance_schema.tls_channel_status
    -> WHERE PROPERTY IN (
    ->   'Enabled',
    ->   'Current_tls_version',
    ->   'Current_tls_cert',
    ->   'Ssl_accepts',
    ->   'Ssl_finished_accepts'
    -> )
    -> ORDER BY CHANNEL, PROPERTY;

+-------------+----------------------+-----------------+
| CHANNEL     | PROPERTY             | VALUE           |
+-------------+----------------------+-----------------+
| mysql_admin | Current_tls_cert     |                 |
| mysql_admin | Current_tls_version  | TLSv1.2,TLSv1.3 |
| mysql_admin | Enabled              | No              |
| mysql_admin | Ssl_accepts          | 0               |
| mysql_admin | Ssl_finished_accepts | 0               |
| mysql_main  | Current_tls_cert     | server-cert.pem |
| mysql_main  | Current_tls_version  | TLSv1.2,TLSv1.3 |
| mysql_main  | Enabled              | Yes             |
| mysql_main  | Ssl_accepts          | 0               |
| mysql_main  | Ssl_finished_accepts | 0               |
+-------------+----------------------+-----------------+
10 rows in set (0.00 sec)

이 결과는 채널별로 TLS가 켜져 있는지, 어떤 버전 범위를 허용하는지, 어떤 인증서 파일을 참조하는지, 실제 accept 카운터가 증가하고 있는지 빠르게 확인하는 데 유용하다. 다만 환경에 따라 빈 결과가 나올 수 있으며, 중요한 것은 빈 결과를 “문제가 없다”로 읽지 않는 것이다. 다음 경우를 반드시 분리해야 한다.

  • 해당 테이블은 존재하지만 현재 노출되는 TLS 채널 정보가 없음
  • 서버가 TLS를 지원하지만 현재 세션/채널에 대한 상세 정보가 제한적임
  • 운영 환경이 프록시 종단 구조라서 실제 TLS 종료 지점이 MySQL 서버가 아님

즉, 이 테이블은 강력한 보조 관측점이지만, 클라이언트 세션 상태와 클라이언트 측 접속 옵션을 대체하지는 않는다.

9. 클라이언트와 운영 도구에서의 실전 점검 예시

SQL만으로 충분하지 않을 때는 실제 접속 커맨드가 중요하다. 예를 들어 mysql CLI에서는 다음처럼 접속 모드와 CA를 명시할 수 있다.

mysql \
  --host=db.example.com \
  --user=app_user \
  --ssl-mode=VERIFY_IDENTITY \
  --ssl-ca=/etc/mysql/certs/ca.pem

운영적으로 이 명령이 의미하는 바는 다음과 같다.

  • 서버 인증서가 신뢰된 CA 체인으로 서명되어 있어야 한다.
  • 접속 hostname이 인증서 SAN 또는 CN과 일치해야 한다.
  • 중간자 공격이나 잘못된 endpoint 연결을 조기에 차단할 수 있다.

인증서 내용을 사전에 확인할 때는 다음과 같은 운영 명령도 자주 사용한다.

openssl x509 -in server-cert.pem -text -noout
openssl verify -CAfile ca.pem server-cert.pem

단, 인증서 파일 자체를 읽는 검증과 실제 클라이언트 접속 시 hostname 검증이 성공하는지는 다른 문제다. 파일 검증이 통과해도 애플리케이션이 IP 주소로 접속하거나, 프록시 앞단 이름과 인증서 이름이 다르면 VERIFY_IDENTITY는 실패할 수 있다.

10. 프록시, 로드밸런서, Aurora MySQL에서의 차이

TLS 운영은 단일 MySQL 서버보다 중간 계층이 들어갈 때 더 복잡해진다.

10.1 프록시가 TLS를 종료하는 경우

ProxySQL, L4/L7 프록시, 서비스 메시 계층이 TLS를 종료하면 “클라이언트 ↔ 프록시” 구간과 “프록시 ↔ MySQL” 구간을 별도로 점검해야 한다. 앞단만 암호화되고 백엔드가 평문이면 내부 보안 기준을 만족하지 못할 수 있다.

10.2 Aurora MySQL의 운영 포인트

Aurora MySQL에서는 사용자가 OS 레벨 인증서 파일을 직접 관리하는 전통적 self-managed 방식과 다르다. 일반적으로는 AWS가 제공하는 endpoint와 CA 체인을 기준으로 접속하며, 운영자는 다음 항목을 더 중요하게 봐야 한다.

  • 애플리케이션이 AWS 제공 CA 번들을 최신 상태로 유지하는가
  • CA rotation 공지가 있을 때 드라이버와 인증서 번들을 함께 점검하는가
  • cluster endpoint, reader endpoint, custom endpoint 이름이 인증서 검증 모델과 충돌하지 않는가
  • RDS Proxy를 쓰는 경우 프록시 구간과 DB 백엔드 구간의 TLS 정책을 분리해서 점검하는가

Aurora에서는 스토리지 계층과 복제 메커니즘이 Community MySQL과 다르므로, “디스크 암호화”와 “네트워크 TLS”를 혼동하면 안 된다. 전송 구간 암호화 점검은 여전히 클라이언트 연결 관점에서 해야 한다.

11. 자주 발생하는 실패 패턴과 오해

11.1 REQUIRED면 충분하다고 생각하는 경우

암호화만 강제하고 서버 신원 검증을 생략하면, 잘못된 endpoint나 중간자 장비를 구별하지 못할 수 있다. 중요한 서비스라면 VERIFY_IDENTITY 수준을 기본 검토 기준으로 삼는 편이 좋다.

11.2 인증서 파일만 배포하고 hostname 검증을 설계하지 않은 경우

DB 접속 대상이 DNS 별칭, VIP, 프록시 endpoint, 장애 조치용 alias를 오가면 인증서 SAN 설계가 따라와야 한다. 운영자는 장애 조치 이름까지 포함해 “실제 애플리케이션이 접속하는 이름” 기준으로 인증서를 설계해야 한다.

11.3 서버 변수만 보고 안심하는 경우

have_ssl=YES, require_secure_transport=ON만으로는 불충분하다. 실제 세션의 Ssl_version, Ssl_cipher, 프록시 종단 위치, 애플리케이션 드라이버 설정을 같이 보지 않으면 운영 현실을 놓친다.

11.4 인증서 갱신을 배포 이벤트로만 취급하는 경우

인증서 교체는 단순 파일 교체가 아니라 애플리케이션 재배포, CA 번들 업데이트, 드라이버 호환성 점검, 만료 전 사전 검증을 포함하는 운영 이벤트다. 특히 여러 언어 런타임과 컨테이너 이미지가 섞인 환경에서는 일부 워커만 오래된 CA를 들고 있을 수 있다.

12. 운영 체크리스트

배포 전 체크리스트

  • 클라이언트 기본 접속 모드가 PREFERRED에 머물지 않고 의도한 수준(VERIFY_CA 또는 VERIFY_IDENTITY
  • require_secure_transport

운영 중 점검 체크리스트

  • SHOW SESSION STATUS LIKE 'Ssl_%'
  • performance_schema.tls_channel_status
  • 장애 조치, endpoint 변경, CA rotation 후에 VERIFY_IDENTITY

13. 의사결정 기준

전송 중 암호화를 설계할 때는 다음 질문에 예/아니오로 답해 보면 좋다.

  1. 평문 연결을 정말 허용할 이유가 있는가
    • 대부분의 운영 서비스에서는 없다. 특별한 이유가 없다면 require_secure_transport와 엄격한 클라이언트 모드를 기본값으로 본다.
  2. 서버 신원 검증까지 필요한가
    • 멀티호스트, 프록시, 클라우드 endpoint, 외부망/하이브리드 경로가 섞이면 사실상 필요하다.
  3. 인증서 교체가 장애 없이 반복 가능한가
    • 반복 가능하지 않으면 보안 이벤트가 곧 가용성 리스크가 된다.
  4. 실제 애플리케이션 세션에서 검증 가능한가
    • 수동 mysql CLI 테스트만 있고 애플리케이션 드라이버 검증이 없으면 운영 증거가 부족하다.

14. 결론

MySQL TLS 운영의 핵심은 “암호화를 켰는가”가 아니라 어떤 경로에서, 어떤 수준으로, 어떤 증거를 가지고 암호화를 보장하는가에 있다. 서버 변수 점검은 시작점일 뿐이며, 실제 운영 품질은 클라이언트 검증 모드, 인증서 이름 설계, 프록시 종단 구조, Aurora/RDS 같은 관리형 환경의 CA rotation 대응까지 포함해야 확보된다.

다음 단계의 학습 주제로는 MySQL 인증(authentication) 플러그인, 프록시 계층의 연결 보안, 또는 Performance Schema를 활용한 세션/접속 관측을 이어서 보면 좋다. TLS를 이해하면 이후의 인증·감사·운영 자동화 설계도 훨씬 더 정확해진다.