통상적인 APM(apache + php + mysql) 환경에서 소규모의 프로젝트를 할 때에는 대개 apache와 php와 mysql이 모두 한 서버에서 돌아가는 경우가 있다. 이런 경우라면 php와 mysql 사이의 통신을 모두 local에서만 하도록 하면 안전은 별로 신경을 쓸 필요가 없다.
그런데 만약에 DB를 따로 분리하면 php가 돌아가는 머신과 DB 서버가 돌아가는 머신 사이의 통신을 안전하게 보호해야 하지 않을까 하는 생각이 들기 마련이다. 바쁘고 정신 없다면 그냥 사용자 인증만 하도록 하면 되겠지만, 누군가가 sniffing을 해 버리면 DB의 내용이 노출된다. 그러니 이런 때에는 암호 통신을 하면 좋은데 생각해 볼 수 있는 방법은 ssh tunnel을 사용하는 것과 SSL을 사용하는 것이 있겠다. SSH tunnel을 사용하는 것은 php에서 이런 것이 지원되는지 아직 모르니 일단 SSL을 이용하는 것에 대해서 썰을 풀도록 하자.
(이건 혼자서 심심풀이 땅콩, 내지는 나중에 내가 찾아보기 쉽게 쓰는 것이니 다른 곳에 더 좋은 문서가 있을 수도 있음.)
사용한 환경 (이건 뭐 적당히 달라도 될 듯. 버젼에 영향을 받는 경우는 본문 중에 따로 표시)
Server: Debian etch, MySQL 5.0, OpenSSL 0.9.8
Client: Debian etch, MySQL 5.0 C library, PHP 5, PHP PEAR, OpenSSL 0.9.8
참고한 문서
MySQL 5.0 Documentation: Using SSL Connections
MySQL 5.0 Documentation: SSL Command Options
PEAR Manual: DB: Introduction - connect
PHP Manual: mysqli_ssl_set
MySQL 문서에 의하면 설치된 MySQL이 SSL을 지원하는지를 확인하는 4가지 절차가 나와 있다.
여기에서는 패키지로 설치하는 것만 생각할 테니 다음의 절차를 통한다.
(이후 노란 배경에 #는 shell prompt, mysql>은 DB의 command prompt를 나타냄.
회색 배경은 명령의 결과, 또는 파일의 내용임.)
1. SSL이 지원되도록 compile 된 패키지인지 확인
서버에서 다음을 실행한다.
에러가 나오지 않고 대충
Copyright (C) 2000 MySQL AB, by Monty and others
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL license
Starts the MySQL database server
Usage: mysqld [OPTIONS]
For more help options (several pages), use mysqld --verbose --help
와 같은 식의 안내 메시지가 나오면 된 것이다.
(내가 사용된 Debian 패키지에는 SSL이 지원되는 것이 들어가 있다.)
2. Run time에서 SSL 옵션이 켜져 있는지 확인
서버에서 다음을 실행한다.
mysql> SHOW VARIABLES LIKE 'have_openssl';
그러면 다음과 같은 output이 나올 것이다.
| Variable_name | Value |
+---------------+----------+
| have_openssl | DISABLED |
+---------------+----------+
1 row in set (0.00 sec)
참고로 MySQL은 OpenSSL 말고 내장된 yaSSL을 사용할 수도 있는데, 심심풀이 삼아서 알아보려면 다음을 실행한다.
| Variable_name | Value |
+---------------+----------+
| have_openssl | DISABLED |
| have_ssl | DISABLED |
| ssl_ca | |
| ssl_capath | |
| ssl_cert | |
| ssl_cipher | |
| ssl_key | |
+---------------+----------+
7 rows in set (0.00 sec)
3. SSL 관련 서버쪽 옵션
서버에서 다음을 실행해야 한다. "실행한다"가 아닌 것에 주의 ^^; 뭘 실행해야 하는 것인지 일단 살펴보기로 하자.
# mysqld --ssl-ca=cacert.pem --ssl-cert=server-cert.pem --ssl-key=server-key.pem
이렇게 실행되도록 만들어야 하는데 일단 사용되는 옵션을 조금만 뜯어보면,
- --ssl-ca 옵션은 CA의 리스트와 그 인증서들을 가져올 파일을 지정해 주는 것이고,
- --ssl-cert 옵션은 이 서버가 사용할 공개키가 포함된 인증서가 저장된 파일을 지정해 주는 것이고,
- --ssl-key 옵션은 이 서버가 사용할 비밀키가 저장된 파일을 지정해 주는 것이다.
참고로 Debian 패키지에 딸려 오는 설정 파일(/etc/mysql/my.cnf)에는 다음과 같은 부분이 있다.
#
# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem
4. 인증서 만들기
인증서라는 것은 자신의 공개키와, 그 공개키를 다른 사람이 믿을 수 있게 만들기 위해서 CA라는 인증기관의 서명을 덧붙인 것을 말한다. 그런데 문제는 CA의 서명을 받으려면 돈도 들지 모르고 이래 저래 귀찮아진다. 그러니 여기서 사용할 것들은 모두 "local"에서만 의미를 갖도록 만든다. 무슨 말이냐 하면 혼자서 CA 역할도 하고 북치고 장구치고 하는 것이다. 이 글의 목적이 애초에 제대로 된 인증 시스템 안에 편입하는 것이 아니고, 단순히 "암호화 통신"만을 가능하게 만들려는 것이기 때문이다.
일단 서버에 이미 openssl을 위해서 openssl 관련 라이브러리들이 깔려 있어야 하니
Standard commands
asn1parse ca ciphers crl crl2pkcs7
dgst dh dhparam dsa dsaparam
ec ecparam enc engine errstr
gendh gendsa genrsa nseq ocsp
passwd pkcs12 pkcs7 pkcs8 prime
rand req rsa rsautl s_client
s_server s_time sess_id smime speed
spkac verify version x509
Message Digest commands (see the `dgst' command for more details)
md2 md4 md5 rmd160 sha
sha1
Cipher commands (see the `enc' command for more details)
aes-128-cbc aes-128-ecb aes-192-cbc aes-192-ecb aes-256-cbc
aes-256-ecb base64 bf bf-cbc bf-cfb
bf-ecb bf-ofb cast cast-cbc cast5-cbc
cast5-cfb cast5-ecb cast5-ofb des des-cbc
des-cfb des-ecb des-ede des-ede-cbc des-ede-cfb
des-ede-ofb des-ede3 des-ede3-cbc des-ede3-cfb des-ede3-ofb
des-ofb des3 desx rc2 rc2-40-cbc
rc2-64-cbc rc2-cbc rc2-cfb rc2-ecb rc2-ofb
rc4 rc4-40
이 중에서 필요한 명령은 genrsa와 req 명령이다. Debian의 경우 패키지로 깔면 man page도 깔렸을 테니
우선 genrsa 명령으로 서명 받을 키를 만든다. 명령 이름에서 유추할 수 있듯이 이 명령은 RSA에 사용할 private key를 만드는 명령이다.
그 다음에는 저 키로부터 공개키를 얻어서 그것을 서명받아야 한다.
# openssl req -new -x509 -key myprivkey.pem -out mycacert.pem -days 0
이렇게 만든 인증서를 self-certified certificate이라고 부른다. 알아 듣기 쉬운 말로 하면, 자신의 공개키가 맞다는 것을 자기 이름을 걸고 보증한다는 뜻이다. 제대로는 자신의 공개키를 자신의 비밀키로 서명한 것이다. 이런 것은 원래 root CA나 하는 일인데, 나는 혼자서 북치고 장구칠 작정이기 때문에 혼자서 키 만들고 혼자서 서명한다. ^^
역시나 심심풀이 삼아서 이것을 검증해 보면
5. 서버 설정하기
Debian의 경우 mysql의 설정파일이 /etc/mysql/my.cnf 라고 돼 있다. 이 파일에서 다음 부분을 찾아서 수정한다.
#
# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem
#
ssl-ca=/etc/mysql/mycacert.pem
ssl-cert=/etc/mysql/mycacert.pem
ssl-key=/etc/mysql/myprivkey.pem
그 다음에 서버를 재실행한다.
이제 mysql에 로그인해서 살펴보면
| Variable_name | Value |
+---------------+--------------------------+
| have_openssl | YES |
| have_ssl | YES |
| ssl_ca | /etc/mysql/mycacert.pem |
| ssl_capath | |
| ssl_cert | /etc/mysql/mycacert.pem |
| ssl_cipher | |
| ssl_key | /etc/mysql/myprivkey.pem |
+---------------+--------------------------+
7 rows in set (0.00 sec)
안타까운 것은 여기까지는 그냥 준비라는 사실 ^^
이제 DB 서버에서 grant 명령 등을 이용해서 사용자 설정들을 해 줘야 한다.
6. 사용자 등록
일단 시험용 database를 하나 만든다.
mysql> create database fortest;
그리고 시험용 사용자를 만든다.
mysql> grant all privileges on fortest.* to testuser@testhost identified by 'testpass' require SSL;
다른 것들은 mysql의 설명을 참조하고, 중요한 것은 끝에 require SSL이 붙어야 한다는 것이다.
7. Client 연결
client 쪽, 그러니까 php가 돌고 있는 쪽에서는 몇가지 설정할 것이 없는데 설치돼 있는 패키지가 ssl을 지원하도록 컴파일 된 것이기만 하면 된다. 내가 설치한 패키지는 지원이 된다. 그러니 남은 것은 연결할 때 어떤 option들을 주느냐이다.
7.1 mysql 명령을 이용하는 경우
이건 거의 시험삼아 접속해 보는 경우일 것이다.
client에서 다음을 실행한다.
# mysql -u testuser -p -h server.mydomain.net --ssl --ssl-ca=mycacert.pem fortest
물론, mycacert.pem 파일이 다른 곳에 있으면 그곳까지의 경로를 다 써준다.
앞서서 설정한 패스워드(testpass)를 입력하면 접속이 돼야 한다. 여기서 물론 server.mydomain.net은 가상의 주소이고 실제로는 자기 서버 주소를 쓴다.
7.2 PEAR 중의 DB.php를 이용할 경우
PEAR 패키지 중에 있는 DB module은 dsn이라는 것을 이용하여 DB 서버가 어디에 있고 어떻게 접속할 것인지 정한다. dsn은 웹 페이지 URL과 유사한 string 형태로 지정할 수도 있고 array 형태로 지정할 수도 있는데, 실수를 방지하기에도 그렇고 보기에도 array 형태가 좋다.
'phptype' => 'mysqli',
'username' => 'testuser',
'password' => 'testpass',
'hostspec' => 'server.mydomain.net',
'database' => 'fortest',
#'key' => <optional>,
#'cert' => <optional>,
'ca' => 'mycacert.pem',
#'capath' => <optional>,
#'cipher' => <optional>,
);
$options = array(
'ssl' => true,
);
$db_link = DB::connect($dsn, $options);
7.3 php의 mysqli_connect(), mysqli_ssl_set()을 이용할 경우
SSL을 이용하려면 mysql_* function들은 안되고 mysqli_* function들을 써야 한다. 저 i는 improved의 의미라나. 어쨌든, 그런데 이 경우에 persistent connection을 사용할 수 없게 된다. 이건 아쉽..
일단 mysqli_init()으로 db link하나를 생성한다. (맞다. 쫌 불편해진다.)
mysqli_ssl_set()을 이용해서 ssl관련 변수들을 설정한다.
원래 syntax는 mysqli_ssl_set(mysqli $link, string $key, string $cert, string $ca, string $capath, string $cipher)인데, 여기에서 $link와 $ca 빼고는 설정 안해도 되기 때문에 모두 NULL로 한다. 그러면 다음과 같이 된다.
그 다음에 실제로 연결한다. (function이름도 mysqli_real_connect()이다. ^^)
8. 마무리
음... 마무리는... 음...
php의 mysqli extension을 쓰면 좀 불편해지기도 하고 persistent connection을 쓸 수 없어지는 문제도 있는데, PEAR를 사용하면 persistent connection을 쓸 수 있도록 하는 옵션이 있다. 이런 면에서는 PEAR를 사용하는 편이 좋을 것 같긴 하다. (근데 실제로 persistent connection이 되는지 안 되는지 확인은 안 해 봤음. :p)
ps. 지금 보니 PEAR를 이용하더라도 phptype이 mysqli로 돼 있으면 결국 php의 mysqli extension을 내부적으로 사용하기 때문에 presistent connection은 사용할 수 없다. 그러니까, 결론은 ssl connection을 사용하느냐 persistent connection을 사용하느냐의 선택이 돼 버린다는 얘기다. 쩝... 하긴 어딘가에서 보니 persistent connection이 꼭 좋은 건 아니라고 돼 있긴 하다.