통상적인 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 ConnectionsMySQL 5.0 Documentation: SSL Command OptionsPEAR Manual: DB: Introduction - connectPHP Manual: mysqli_ssl_setMySQL 문서에 의하면 설치된 MySQL이 SSL을 지원하는지를 확인하는 4가지 절차가 나와 있다.
여기에서는 패키지로 설치하는 것만 생각할 테니 다음의 절차를 통한다.
(이후 노란 배경에 #는 shell prompt, mysql>은 DB의 command prompt를 나타냄.
회색 배경은 명령의 결과, 또는 파일의 내용임.)
1. SSL이 지원되도록 compile 된 패키지인지 확인서버에서 다음을 실행한다.
# mysqld --ssl --help
에러가 나오지 않고 대충
mysqld Ver 5.0.45-Debian_1-log for pc-linux-gnu on i486 (Debian etch distribution)
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 -u root -p (DB에 root로 로긴한다. 물론 패스워드를 입력해야 한다.)
mysql> SHOW VARIABLES LIKE 'have_openssl';
그러면 다음과 같은 output이 나올 것이다.
+---------------+----------+
| Variable_name | Value |
+---------------+----------+
| have_openssl | DISABLED |
+---------------+----------+
1 row in set (0.00 sec)
내가 사용한 패키지의 기본 설정으로는 openssl을 사용하는 옵션이 꺼져 있다. (물론 지원은 된다.)이제 저 옵션을 켜야 하는데, 서버를 다시 시작하지 않고도 할 수 있을 테지만, 일단은 문서에 나오는 대로 서버를 새로 시작하는 방법을 따른다. (물론, 이렇게 해야 서버를 다른 이유로 재시작할 때도 SSL 옵션이 켜지게 되겠지.)
참고로 MySQL은 OpenSSL 말고 내장된 yaSSL을 사용할 수도 있는데, 심심풀이 삼아서 알아보려면 다음을 실행한다.
mysql> show variables like '%ssl%';
결과는 다음과 같을 것이다.
+---------------+----------+
| 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)
위에서 보는 바와 같이 yaSSL도 지금은 사용하지 않도록 돼 있다. 그리고 인증서나 기타 SSL과 관련된 정보들이 하나도 들어가 있지 않음을 알 수 있다.
3. SSL 관련 서버쪽 옵션서버에서 다음을 실행해야 한다. "실행한다"가 아닌 것에 주의 ^^; 뭘 실행해야 하는 것인지 일단 살펴보기로 하자.
# mysqld --ssl-ca=cacert.pem --ssl-cert=server-cert.pem --ssl-key=server-key.pem
이렇게 실행되도록 만들어야 하는데 일단 사용되는 옵션을 조금만 뜯어보면,
- --ssl-ca 옵션은 CA의 리스트와 그 인증서들을 가져올 파일을 지정해 주는 것이고,
- --ssl-cert 옵션은 이 서버가 사용할 공개키가 포함된 인증서가 저장된 파일을 지정해 주는 것이고,
- --ssl-key 옵션은 이 서버가 사용할 비밀키가 저장된 파일을 지정해 주는 것이다.
일단은 비밀키, 공개키와 인증서를 만들어야 함을 알 수 있다. 이것들을 만들기 위해서는 잠시 MySQL에서 벗어나서 OpenSSL을 가지고 놀아야 한다.
참고로 Debian 패키지에 딸려 오는 설정 파일(/etc/mysql/my.cnf)에는 다음과 같은 부분이 있다.
# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
#
# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem
세가지 옵션이 comment 처리돼 있고, 그 위의 설명에는 인증서들을 만드는 데에 tinyca라는 프로그램을 사용하면 좋다고 돼 있다. 이 tinyca라는 게 무엇이냐 하면 GUI로 인증서를 만들어주는 툴인데, 나의 경우 서버에는 GUI 툴을 실행하기 위한 X-window 관련 패키지들이 하나도 깔려 있지 않다. 그러니 저 패키지를 깔려면 그다지 사용하게 되지 않을 대량의 라이브러리들을 깔아야 한다는 의미가 되겠다. 그런 건 싫지. 그리고 왠지 guru의 분위기를 풍겨 보자면 openssl을 직접 가지고 놀아 보는 게 좋을 것 같다. 므흐흐... (아, 정말 쓸 데 없는 이유다. -.-;;)
4. 인증서 만들기인증서라는 것은 자신의 공개키와, 그 공개키를 다른 사람이 믿을 수 있게 만들기 위해서 CA라는 인증기관의 서명을 덧붙인 것을 말한다. 그런데 문제는 CA의 서명을 받으려면 돈도 들지 모르고 이래 저래 귀찮아진다. 그러니 여기서 사용할 것들은 모두 "local"에서만 의미를 갖도록 만든다. 무슨 말이냐 하면 혼자서 CA 역할도 하고 북치고 장구치고 하는 것이다. 이 글의 목적이 애초에 제대로 된 인증 시스템 안에 편입하는 것이 아니고, 단순히 "암호화 통신"만을 가능하게 만들려는 것이기 때문이다.
일단 서버에 이미 openssl을 위해서 openssl 관련 라이브러리들이 깔려 있어야 하니
# aptitude install openssl
이라고 해서 같이 설치할 거냐고 물어보는 관련 패키지들까지 모조리 설치한다. 그 다음에
# openssl -h
라고 실행하면 '-h'라는 명령이 없다고 불평은 하지만, 착하게도 사용 가능한 명령어 목록을 보여준다. :)
openssl:Error: '-h' is an invalid command.
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도 깔렸을 테니
# man req
라고 하면 openssl에서 req 명령의 설명을 보여준다. 간단히 썰을 풀면, 인증서를 검증하는 것과
인증서를 생성하는 것을 해주는 명령이라고 한다. 우리가 여기서 필요한 것은 인증서를 생성하는 것이다. 설명 중에 보면 "It can additionally create self signed certificates for use as root CAs for example."라고 하는데 여기서 나는 root CA의 역할까지 몽땅 해버릴 작정이다. :)
우선 genrsa 명령으로 서명 받을 키를 만든다. 명령 이름에서 유추할 수 있듯이 이 명령은 RSA에 사용할 private key를 만드는 명령이다.
# openssl genrsa -out myprivkey.pem 2048
openssl에 관련된 어떤 문서들을 보면 -des3이나 idea나 -des 같은 옵션을 붙이도록 돼 있는데 여기서는 이렇게 하면 안된다. 이 키는 서버가 (그러니까 사람이 아니라 프로그램이) 사용할 키이기 때문이다. 참고로 저 옵션 중에 하나를 붙이면 비밀키가 암호화돼서 저장되기 때문에 키를 사용하려고 할 때마다 암호를 풀기 위해서 passphrase를 입력해 줘야 한다. 프로그램이라면 당연히 그렇게 할 수가 없지.
그 다음에는 저 키로부터 공개키를 얻어서 그것을 서명받아야 한다.
# openssl req -new -x509 -key myprivkey.pem -out mycacert.pem -days 0
이렇게 만든 인증서를 self-certified certificate이라고 부른다. 알아 듣기 쉬운 말로 하면, 자신의 공개키가 맞다는 것을 자기 이름을 걸고 보증한다는 뜻이다. 제대로는 자신의 공개키를 자신의 비밀키로 서명한 것이다. 이런 것은 원래 root CA나 하는 일인데, 나는 혼자서 북치고 장구칠 작정이기 때문에 혼자서 키 만들고 혼자서 서명한다. ^^
역시나 심심풀이 삼아서 이것을 검증해 보면
# openssl verify -verbose -CAfile mycacert.pem mycacert.pem
mycacert.pem: OK
라고 나온다.
5. 서버 설정하기Debian의 경우 mysql의 설정파일이 /etc/mysql/my.cnf 라고 돼 있다. 이 파일에서 다음 부분을 찾아서 수정한다.
# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
#
# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem
아래와 같이 바꾼다.
# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
#
ssl-ca=/etc/mysql/mycacert.pem
ssl-cert=/etc/mysql/mycacert.pem
ssl-key=/etc/mysql/myprivkey.pem
물론 위에서 만든 myprivkey.pem과 mycacert.pem은 /etc/mysql에 넣어두어야 한다.
그 다음에 서버를 재실행한다.
# /etc/init.d/mysql restart
이제 mysql에 로그인해서 살펴보면
mysql> show variables like '%ssl%';
+---------------+--------------------------+
| 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 형태가 좋다.
$dsn = 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);
#로 comment 처리된 부분은 써줘도 안써줘도 되는데 지금처럼 혼자서 북치고 장구치는 상황에서는 안 써주는 게 낫다.
7.3 php의 mysqli_connect(), mysqli_ssl_set()을 이용할 경우SSL을 이용하려면 mysql_* function들은 안되고 mysqli_* function들을 써야 한다. 저 i는 improved의 의미라나. 어쨌든, 그런데 이 경우에 persistent connection을 사용할 수 없게 된다. 이건 아쉽..
일단 mysqli_init()으로 db link하나를 생성한다. (맞다. 쫌 불편해진다.)
$db = mysqli_init();
mysqli_ssl_set()을 이용해서 ssl관련 변수들을 설정한다.
원래 syntax는 mysqli_ssl_set(mysqli $link, string $key, string $cert, string $ca, string $capath, string $cipher)인데, 여기에서 $link와 $ca 빼고는 설정 안해도 되기 때문에 모두 NULL로 한다. 그러면 다음과 같이 된다.
mysqli_ssl_set($db, NULL, NULL, 'mycacert.pem', NULL, NULL);
그 다음에 실제로 연결한다. (function이름도 mysqli_real_connect()이다. ^^)
mysqli_real_connect($db, 'server.mydomain.net', 'testuser', 'testpass', 'fortest');
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이 꼭 좋은 건 아니라고 돼 있긴 하다.