JDBC를 사용한 MySQL SSL 연결

QueryPie 개발기 #9: 데이터베이스에 안전하게 접속하기

jayden-lee
16 min readFeb 28, 2019

📑영어(English Version)- https://medium.com/p/bee3bcb59029/

데이터베이스에 왜 보안이 중요할까?

스타트업에서 대기업에 이르기까지 거의 모든 기업은 중요한 정보를 수집하고 보관하기 위해서 데이터베이스를 사용하고 있습니다. 그러나 당연하듯이 사용하고 있는 데이터베이스에는 여러 보안 이슈가 존재합니다. 크게 보면 아래와 같은 문제들이 있습니다.

데이터베이스 보안 이슈
  1. 데이터베이스 접속 정보 유출: User name, Password 등 접속 가능한 정보의 노출로 인해 권한이 없는 제 3자의 접근이 가능해지는 경우
  2. 데이터 유출 및 임의 변경: 접속 정보 유출 시, 데이터베이스 내 데이터를 외부로 유출하거나 임의로 변경해버리는 경우
  3. 데이터베이스 접근 제어의 어려움: 사용자에 따라 데이터 수정, 추가 및 삭제 등 각기 다른 권한을 부여하고 관리해야하는 이슈

특히 민감한 개인정보를 저장한 데이터베이스에 발생하는 보안 문제는 개인의 프라이버시를 크게 침해하므로 사회적으로도 큰 파장을 일으킵니다. 과거 많은 사람들을 불안에 떨게 했던 데이터 유출 사고가 수십 차례 있었고, 최근에는 페이스북과 같은 글로벌 기업에서도 개인정보 유출 문제로 많은 이슈가 등장하고 있습니다. 이에 따라 기업들에게 철저한 데이터 보안을 요구하는 목소리가 점점 커져가고 있고, 데이터베이스 보안 솔루션 역시 이제 선택이 아닌 필수라 할 수 있습니다.

그래서 오늘은 몇 가지 조치를 통해 데이터베이스의 보안을 강화하는 방법을 소개하고자 합니다. 바로 “JDBC를 사용한 SSL 기반 데이터베이스 접속방법”입니다.

SSL(Secure Sockets Layer)은 왜 필요한가?

웹 서핑을 하다 가끔씩 사이트 접속이 막히면서 “이 사이트는 보안 연결(HTTPS)이 사용 되지 않았습니다”라는 메시지가 뜨곤 합니다. SSL이라는 말은 생소하더라도 이런 상황은 누구나 한 번쯤은 겪어봤을 것 같습니다.

SSL 적용되지 않은 웹 사이트 경고 문구

위 메시지는 크롬에서 접속한 웹 사이트에 HTTPS(SSL)가 적용되지 않을 때 표시되는 문구입니다. 경고창을 보고나서도 계속하기 버튼을 눌러 해당 웹 사이트를 이용하는 경우도 있습니다. 하지만 SSL을 지원하지 않는 웹사이트에 접속하면 정보를 주고 받을 때 암호화 된 통신을 사용하지 않기 때문에 사용자와 웹사이트(웹 서버)간에 주고 받는 정보를 중간에 제 3자가 엿들을 수 있습니다.

Man in the Middle (중간자 공격)

중요하지 않은 정보라면 문제되지 않겠지만 카드번호, 계정 비밀번호, 개인의 프라이버시 등 민감한 정보를 중간에 누군가 가로챈다면 어떨까요? 정보가 암호화 되지 않았기 때문에 제 3자(중간자)가 정보를 탈취하여 악용할 수 있습니다. 이는 중간자(MITM, Man in the middle) 공격이라고 합니다.

하지만 사용자와 서버 간 연결에 보안 인증 절차를 추가한다면 중간자 공격을 막을 수 있습니다. 예를 들어 TLS/SSL 프로토콜은 공개 키를 기반으로 한 인증 절차인데요, 애플리케이션에서도 SSL 기능을 필수적으로 지원함으로써 개인정보를 안전하게 지킬 수 있습니다.

데이터베이스에서도 SSL 기능이 필요할까?

웹 사이트처럼 데이터베이스 연결을 할 때도 SSL 기능이 유용합니다. 데이터베이스에 암호화 된 연결(SSL 연결)을 하지 않으면, 사용자와 데이터베이스 간에 주고 받는 쿼리 문장 또는 결과 데이터가 암호화 되지 않은 상태로 중간자에게 노출 될 수 있기 때문입니다.

SSL을 사용하지 않아 보안에 취약한 특정 상황을 가정해 보겠습니다. 사용자가 데이터베이스에 일반적인 접속을 한 상태에서 고객의 민감한 개인정보를 담고 있는 테이블 데이터를 조회하는 SELECT * FROM STAFF 쿼리를 수행합니다.

수행된 쿼리를 오픈 소스 패킷 분석 프로그램 wireshirk를 통해 분석하면 사용자와 데이터베이스 사이에서 전달되는 정보를 확인할 수 있습니다. 아래 이미지를 보면 사용자가 실행한 쿼리 문장 SELECT * FROM STAFF가 고스란히 노출되었습니다. 이렇게 해커에 의해 개인정보가 중간에 유출되거나 쿼리가 위변조 되는 공격을 받을 수 있습니다.

일반 연결에서 사용자가 실행한 쿼리 정보 노출

그러나 데이터베이스를 SSL로 연결하면 사용자와 데이터베이스 간에 주고 받는 정보가 암호화 되어서 전달됩니다. SSL 연결을 하고 나서 다시 wireshirk를 통해 패킷을 분석하면, 이번엔 사용자가 실행한 쿼리 정보가 암호화가 되어서 중간에 어떤 쿼리를 수행했는지 알아챌 수 없습니다.

SSL 연결을 통해 사용자가 실행한 쿼리 정보 암호화

MySQL SSL 연결 알아보기

(1) SSL Mode

그럼 이제 본격적으로 SSL 연결은 어떻게 하는 것인지 다루어보겠습니다. 먼저 MySQL 데이터베이스에 SSL 연결하기 전에 MySQL 공식 문서에서 SSL 관련 내용을 먼저 살펴보겠습니다.

MySQL Connector/J는 JDBC API를 구현한 드라이버입니다. Java언어에서 MySQL 데이터베이스 연결 및 작업을 하기 위해서 사용됩니다.

MySQL Connector/J는 SSL을 사용하여 JDBC 드라이버와 MySQL 서버 간에 통신되는 모든 데이터를 암호화합니다. MySQL 서버에서 SSL 기능이 동작하기 위해서는 다음과 같은 작업이 이루어져야 합니다.

  1. SSL 기능을 지원하는 MySQL Server 설치
  2. 상호(양방향)인증을 사용하는 경우에 서명된 클라이언트 인증서 필요

Connector/J는 기본적으로 MySQL 서버와 보안 연결을 설정합니다. 그리고 OpenSSL로 컴파일된 MySQL 5.7과 8.0 버전은 데이터베이스 시작 시에 자동으로 누락된 SSL 파일을 생성하고, 이에 따라 SSL 연결 구성을 할 수 있습니다.

SSL 연결은 상황에 따라 SSL Mode를 선택할 수 있습니다. SSL Mode값에 따라 SSL 연결의 필수 여부 혹은 인증서 검증 방법을 정할 수 있는데요, 종류는 다음과 같습니다.

MySQL Workbench의 SSL 연결 설정
  1. DISABLED (=No)
    SSL 연결을 하지 않고 일반 연결을 할 때 사용합니다.
  2. PREFERRED (=If available, 기본값)
    우선 자동으로 SSL 연결을 시도하고, MySQL 서버가 SSL를 지원하지 않을 경우에는 일반 연결로 접속합니다.
  3. REQUIRED (=Require)
    항상 SSL 연결을 시도합니다. 따라서 MySQL 서버가 SSL을 지원하지 않으면 연결이 수립되지 않습니다. CA(Certificate Authority, 신뢰할 수 있는 인증기관)와 Hostname 유효성 검사은 하지 않습니다.
  4. VERIFY_CA (=Require and Verify CA)
    항상 SSL 연결을 시도합니다. CA 검증은 하지만 Hostname이 일치하지 않더라도 접속을 허용합니다.
  5. VERIFY_IDENTITY (=Require and Verify Identity)
    항상 SSL 연결을 시도합니다. CA와 Hostname 유효성 모두를 검증합니다.

추가적인 보안을 위해서 단방향(클라이언트 or 서버) 혹은 양방향(서버+클라이언트) SSL 인증을 설정 할 수 있습니다. 여기서 양방향 SSL 인증을 설정하게 되면, 클라이언트는 서버로부터 전달 받은 공개 키 인증서를 검증하고 서버에서는 클라이언트에서 보낸 인증서를 검증하게 됩니다.

(2) 서버 인증서 유효성 검증 (a.k.a 서버 인증 설정)

MySQL Connector/J는 버전에 따라 SSL 연결 속성을 설정하는 방법이 다른데요,

8.0.12 및 이전 버전: 연결 속성 useSSL, verifyServerCertificate 값이 true일 때, 서버 인증서 유효성 검증이 활성화됩니다. 단 Hostname 검증은 지원하지 않습니다.

8.0.13 버전 이후: 클라이언트의 SSLMode가 VERIFY_CA 또는 VERIFY_IDENTITY 설정 되었을 경우에만 서버 인증이 활성화 됩니다. 서버 인증이 활성화 된 경우 클라이언트는 SSL 접속 시에 인증서(ca.pem 파일)를 포함해야합니다.

📌서버 인증 설정 방법: 저장소 생성 및 인증서 추가

우선 인증서가 개인 인증 기관에서 발행된 경우에는 클라이언트 트러스트 저장소의 인증서 목록에 인증서를 추가해야 합니다. Java 언어에서 지원하는 ‘keytool’ 유틸리티를 사용하면, 아래와 같이 트러스트 저장소 생성 및 인증서 목록 추가하는 작업을 쉽게 할 수 있습니다.

keytool -importcert -alias MySQLCACert -file ca.pem -keystore truststore -storepass mypassword

트러스트 저장소에 인증서 목록을 추가 할 때 지정한 비밀번호 값인 ‘mypassword’는 추후 JDBC 연결 속성에서 사용되므로 반드시 기억해야 합니다.

Java에서 트러스트 저장소를 설정하는 방법은 다양하지만, 이번 글은 JDBC 연결 속성을 통한 설정 방법에 대해 다루고 있으므로 해당 방법만 확인해보겠습니다.

JDBC 연결 속성을 담는 Properties 객체는 키와 값 형태로 이루어진 자료구조입니다. 먼저 trustCertificateKeyStoreUrl 키에는 이전에 생성한 트러스트 저장소 파일 위치에 해당하는 URL 값을 설정합니다. 그리고 trustCertificateKeyStorePassword 키에는 트러스트 저장소를 생성할 때 설정한 비밀번호 값(=mypassword)을 설정합니다.

Properties properties = new Properties();
properties.put(“trustCertificateKeyStoreUrl”, “path_to_truststore_file”);
properties.put(“trustCertificateKeyStorePassword”, “mypassword”);

(3) 클라이언트 인증서 유효성 검증 (a.k.a 클라이언트 인증 설정)

서버에서는 클라이언트를 인증하기 위해 클라이언트에게 SSL 인증서(client-cert.pem, client-key.pem)를 요구하고 검증할 수 있습니다.

보통 서버에서는 연결을 시도하는 클라이언트가 자체 키 세트와 서버에서 확인할 수 있는 미리 서명된 SSL 인증서를 가지고 있을 경우에 해당 클라이언트를 인증합니다.

일부 MySQL 서버 빌드에서는 모든 클라이언트가 사용할 수 있는 인증서와 개인키(client-cert.pem 및 client-key.pem 파일)를 포함하여 통신 암호화를 위한 SSL키와 인증서를 생성할 수 있습니다. 이렇게 서버 빌드에서 생성된 SSL 인증서는 서버가 허용하는 CA 인증(ca.pem)에 의해 이미 서명되어 있기 때문에 별도의 서명은 필요하지 않습니다.

만약 서버에서 생성한 클라이언트 키 및 인증서 파일을 사용하지 않으려면 직접 인증서와 키를 생성해야 하는데, 이 때는 Java SSL 라이브러리 또는 Connector/J에서 클라이언트 키 및 인증서 파일을 사용할 수 있도록 Java 키 저장소를 가져와야 합니다.

📌클라이언트 인증 설정 : Java 키 저장소 생성 방법

  1. 키 저장소 생성하기 전에 클라이언트 키 및 인증서 파일을 PKCS#12로 변환
openssl pkcs12 -export -in client-cert.pem -inkey client-key.pem \ 
-name “mysqlclient” -passout pass:mypassword -out client-keystore.p12

2. client-keystore.p12 파일을 이용해서 Java 키 저장소를 생성

keytool -importkeystore -srckeystore client-keystore.p12 -srcstoretype pkcs12 \ 
-srcstorepass mypassword -destkeystore keystore -deststoretype JKS -deststorepass mypassword

3. clientCertificateKeyStoreUrl키에는 키 저장소 파일이 위치하는 URL 값을 설정, clientCertificateKeyStorePassword 키에는 키 저장소를 생성할 때 입력한 비밀번호 입력

Properties prpoperties = new Properties();
properties.put(“clientCertificateKeyStoreUrl”, “file:path_to_keystore_file”);
properties.put(“clientCertificateKeyStorePassword”, “mypassword”);

(4) MySQL 서버 SSL 옵션 활성화

우선 사용하고 있는 MySQL 데이터베이스가 SSL 기능을 지원하는지 알아보도록 하겠습니다. 참고로 OpenSSL로 컴파일된 MySQL 5.7과 8.0 버전에서는 위에서 언급한 것처럼 데이터베이스 시작 시에 SSL 설정을 자동으로 해주는 스크립트가 포함되어 있습니다.

MySQL 데이터베이스에서 접속하고 나서 show variables like ‘%ssl%’ 쿼리를 실행하면 SSL 관련된 옵션이 활성화되어 있는지 상태를 확인할 수 있습니다. 아래 이미지에서 그리드를 살펴보면, have_ssl 값이 YES로 표시되어 있습니다. 이는 현재 사용하고 있는 MySQL 데이터베이스가 SSL 기능을 지원한다는 의미입니다.

MySQL 데이터베이스 SSL 연결 지원 여부 확인

MySQL 서버의 SSL 옵션을 활성화 하기 위해서는 SSL키를 생성한 후 MySQL 서버 환경설정 파일에 설정 작업을 해줘야 합니다. 그러면 openssl을 이용하여 SSL 키를 생성해보겠습니다.

  1. Certificate Authority(CA) 생성
> openssl genrsa 2048 > ca-key.pem
> openssl req -new -x509 -nodes -days 3650 -key ca-key.pem > ca-cert.pem

2. MySQL 서버 SSL 키와 인증서 생성

> openssl req -newkey rsa:2048 -days 3650 -nodes -keyout server-key.pem > server-req.pem
> openssl x509 -req -in server-req.pem -days 3650 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 > server-cert.pem
> openssl rsa -in server-key.pem -out/ server-key.pem

3. 클라이언트 SSL 키와 인증서 생성

> openssl req -newkey rsa:2048 -days 3650 -nodes -keyout client-key.pem > client-req.pem
> openssl x509 -req -in client-req.pem -days 3650 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 > client-cert.pem
> openssl rsa -in client-key.pem -out client-key.pem

4. MySQL 서버 구성 파일 수정
/etc/my.cnf 파일을 열고 다음과 같은 내용을 추가 및 수정합니다.

[mysqld]
ssl-ca=ca-cert.pem
ssl-cert=server-cert.pem
ssl-key=server-key.pem
[client]
ssl-cert=client-cert.pem
ssl-key=client-key.pem

5. 그리고 MySQL 서비스를 재시작하면 SSL 옵션이 활성화됩니다.

MySQL에서는 mysql_ssl_rsa_setup 유틸리티를 제공합니다. 이를 이용하면 SSL 연결에 필요한 파일을 쉽게 생성하고 진입 장벽을 낮출 수 있습니다. 하지만, 이 방법을 통해 생성된 인증서는 매우 안전하지 않을 수 있기 때문에 등록된 인증 기관에서 인증서 및 키를 얻는 것을 추천합니다.

JDBC 를 이용한 SSL 연결 방법

지금까지 알아본 내용을 바탕으로 Connector/J를 사용하여 MySQL 데이터베이스에 SSL 연결하는 방법에 대해 알아보겠습니다.

아래 방법은 Connector/J 8.0.13 버전을 기준으로 테스트한 코드입니다.

클라이언트와 서버 서로 간에 양방향 SSL 인증을 사용하는 예제를 살펴보겠습니다. SSL Mode는 VERIFY_CA로 선택하겠습니다.

  1. SSL 모드 종류에 따라 연결 속성 값을 설정을 합니다.
    연결 속성 sslMode의 값을 VERIFY_CA로 설정합니다.
  2. ca.pem 서버 인증서를 이용하여 truststore 파일 생성
    (ca.pem 파일을 truststore로 가져오는 방법은 위에서 알아 본 “서버 인증 설정”을 참고합니다.)
  3. 클라이언트 키 및 인증서를 이용하여 keystore 파일 생성
    (클라이언트 키(client-key.pem) 및 인증서(client-cert.pem) 파일을 Java의 keystore로 가져오는 방법은 위에 “클라이언트 인증 설정”을 참고합니다.)

위 순서대로 인증 설정을 하면 SSL 연결 속성 설정 코드는 아래와 같이 작성됩니다.

Connector/J 드라이버를 이용하여 SSL 연결 속성 설정

맺음말: 데이터베이스 보안과 QueryPie

SSL에 관해 다룬 이번 포스팅은 사실 CHEQUER에서 새롭게 준비 중인 데이터베이스 툴 ‘QueryPie’의 연결 속성을 개발하면서 정리한 내용입니다. 이전에도 어느 정도 알고 있다고 생각했지만, 해당 기능을 처음부터 직접 개발하려고 하니 쉽지 않았습니다. 특히 Java의 보안 관련 기능과 인증서 개념은 다시 공부해가면서 개발할 수 있어 좋았습니다.

그렇지만 데이터베이스 보안에서 SSL 기반의 접속 뿐 아니라 다른 기능적인 보완 요소들도 필요한데요, QueryPie에서는 데이터베이스 보안을 강화하기 위해 접속 정보 유출을 방지하고, 모든 구성원이 허용된 권한 내에서 데이터베이스를 안전하게 사용할 수 있는 기능 역시 열심히 개발 중에 있습니다. 이와 관련해서 자세한 내용은 해당 글에서 확인하실 수 있습니다.

마지막으로 저를 포함한 다른 팀원들이 열심히 개발 중인 QueryPie가 어떤 툴인지 궁금하시다면, 이 글을 참고해주세요.

--

--