そろそろLDAPにしてみないか?

第11回OpenLDAPとSSL

今回のテーマは暗号化です。SMTPやPOP3プロトコルでは平文パスワードがネットワーク上を流れることが知られていますが、LDAPでも同じことが言え、tcpdumpなどでキャプチャしてみると、パスワード文字列を簡単に取得することができてしまいます。

たとえば、UNIXアカウントをOpenLDAPで管理する場合、実際のパスワード情報はLDAPサーバ上のuserPassword属性中に暗号化された状態で格納されていますが、ネットワーク上のトラフィックをキャプチャしてみると、平文のパスワードを取得することができてしまいます。

信頼されたネットワーク内のみで運用するのであれば話は別ですが、盗聴の危険性を考慮した場合、ネットワーク経路の暗号化を是非検討してみてください。

暗号化の種類

データを暗号化させるためには、いくつかの方法が考えられますが、これはSMTP/POP3/IMAPなどの基本的なプロトコルにおける考え方と同様です。

ひとつ目の方法はSSL/TLSにより、トラフィック全体を暗号化させることです。この場合SSLを使用しますので、LDAPサーバ上にサーバ鍵、証明書、CA情報などを設置しておく必要があります。トラフィックをSSLでカプセリングすることで、パスワード情報だけではなく接続の最初から最後までの全情報を暗号化させることができることが最大のメリットです。また、暗号化だけではなく信頼された接続先に安全に接続できる、という意味も持ちます。

もうひとつの方法は、LDAPバインド時の認証情報のみを暗号化させることです。この場合、バインド時の情報だけが暗号化されていますので、エントリを取得する際の情報は平文の状態となります。SMTP Authの設定を経験されたことのある読者はご存じかと思いますが、CRAM-MD5方式などの暗号化メソッドがそれに該当します。データの一部しか暗号化されないのであれば、⁠最初からSSL/TLSにしていたほうが良いのでは?」と考える読者の方もいらっしゃるかもしれませんが、全てを暗号化するわけではないため、SSLよりもパフォーマンス的に優れる、というのがメリットです。

今回は前者のSSLによる設定を紹介します。

OpenLDAPとSSL/TLS設定

SSL設定を行うためにはサーバ鍵、証明書などの情報が必須です。通常であればVeriSignのような認証局にCSRを提出し、証明書を取得する手順となりますが、今回は皆さん予想されているとおり、自己証明書を使った構築方法を説明します。

ちなみに、証明書を作成するためのOpenSSLには証明書を手軽に作成するための、CA.shやCA.plというスクリプトが含まれていることが多いのですが、お使いのディストリビューションによっては、opensslコマンドは存在するが、CA.shが存在しない、という場合もありますので、今回はCA.shを使わない方法を説明します。

サーバ鍵の作成

opensslコマンドを使ってサーバ鍵を作成しますが、例のごとく鍵にパスフレーズが含まれていると、安全ではあるのですが運用上不便なところも多いため、パスフレーズ無しの鍵を作成しておきます。

図1 サーバ鍵の作成
% mkdir /tmp/ssl
% cd /tmp/ssl
% openssl genrsa -in server.key.tmp -out server.key 
Generating RSA private key, 1024 bit long modulus 
................................................................++++++ 
.++++++ 
unable to write 'random state' 
e is 65537 (0x10001) 
Enter pass phrase for server.key.tmp: パスフレーズを入力
Verifying - Enter pass phrase for server.key.tmp: パスフレーズを入力
% openssl rsa -in server.key.tmp -out server.key 
Enter pass phrase for server.key.tmp: 先ほどのパスフレーズを入力
writing RSA key 

次に自分自身でサインする証明書を作成します。今回の目的は先に述べたよう、暗号化のみであるため、公の認証局は使用しません。また、後々の運用を楽にするため、有効期限をかなり長めに設定します。

図2 自己証明書の作成
% openssl req -new -x509 -days 3650 -key server.key -out server.crt 
You are about to be asked to enter information that will be incorporated 
into your certificate request. 
What you are about to enter is what is called a Distinguished Name or a DN. 
There are quite a few fields but you can leave some blank 
For some fields there will be a default value, 
If you enter '.', the field will be left blank. 
----- 
Country Name (2 letter code) [AU]:JP 
State or Province Name (full name) [Some-State]:Tokyo 
Locality Name (eg, city) []:Minato-ku 
Organization Name (eg, company) [Internet Widgits Pty Ltd]:BLUECOARA 
Organizational Unit Name (eg, section) []: 
Common Name (eg, YOUR name) []:secure.bluecoara.net 
Email Address []: 

整理しておきますと、サーバ鍵と証明書の内容は次のようなフォーマットになります。

リスト1 サーバ鍵(server.key)
-----BEGIN RSA PRIVATE KEY----- 
MIICXgIBAAKBgQDJU87P/DCNcbT1GbUF4GGsd0AlA637NO9RZ8VV3WGca5ovPqII 
SYYjCEaVdlLdhfw545W5gS4LprsAFU00DKz9G+WAk2GwZgFxO8W5xB8RtvUTokeK 
s/PsOxDFR8PX2UTRZqZePnfxtAHx/Yd7e5EfamZpJ5/eqshjIbcP3p6MUQIDAQAB 
AoGBAMMTb9tb03faa6rqo9rz1fCVBBRZgmVcn19Tm4RdpE06fgW1PK+m/lBHno0I 
P7biMsms2dELeKBVh/DGsy3x8lL+eFEBYo9d9jeHUY6E0Y4e1GhNDisZwKehQUiV 
VwqHI81YbDMmtrZ2U4RWuddgKSyeVa+o63zYqhw/Tps7H+zZAkEA6FD2Sl12ISKZ 
WOZsj6q0orS4TjAjNOU+Z3Ql0deHzkbRN4xJL4QizVIP36+aiEpWydW7d7cnaxu1 
Hpoy7bti4wJBAN3aF2ql39nzwpYaQclbXYVgpsoxSPiapo/8ZpuV7dR6izuqW84l 
BsefE+1cfoU3yKtkZd6xRwG5gw5lqts31jsCQQDi2aQWK7tdUcbCC4d4pm1q7+OE 
3faTAUN1i/IePUMshvRnNIKGdpXxPi+n8naInWawAhfkJ6HDMjQjJNmUUMWXAkEA 
ydlgbNw266rpZIDHUlkaofvqRRTKe8bZvS+8e9rR5P0KKWM63Di8DmwiSVhalQLI 
RYi3VwKcxGjxYOwHpCUNeQJAAoEVQVxUhQq2aXKOaEdjjmODTh+WnbY0OcRgX6KX 
ZDKv4VOV+kRO5G1pKjtM/PMclhzf4l5lHZe5/BWE1LA2Gw== 
-----END RSA PRIVATE KEY----- 
リスト2 証明書(server.crt)
-----BEGIN CERTIFICATE----- 
MIIDEDCCAnmgAwIBAgIJALnZerfv4y/qMA0GCSqGSIb3DQEBBQUAMGQxCzAJBgNV 
BAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzESMBAGA1UEBxMJTWluYXRvLWt1MRIwEAYD 
VQQKEwlCTFVFQ09BUkExHTAbBgNVBAMTFHNlY3VyZS5ibHVlY29hcmEubmV0MB4X 
DTA4MDUwNDAzMjMxM1oXDTE4MDUwMjAzMjMxM1owZDELMAkGA1UEBhMCSlAxDjAM 
BgNVBAgTBVRva3lvMRIwEAYDVQQHEwlNaW5hdG8ta3UxEjAQBgNVBAoTCUJMVUVD 
T0FSQTEdMBsGA1UEAxMUc2VjdXJlLmJsdWVjb2FyYS5uZXQwgZ8wDQYJKoZIhvcN 
AQEBBQADgY0AMIGJAoGBAMlTzs/8MI1xtPUZtQXgYax3QCUDrfs071FnxVXdYZxr 
mi8+oghJhiMIRpV2Ut2F/DnjlbmBLgumuwAVTTQMrP0b5YCTYbBmAXE7xbnEHxG2 
9ROiR4qz8+w7EMVHw9fZRNFmpl4+d/G0AfH9h3t7kR9qZmknn96qyGMhtw/enoxR 
AgMBAAGjgckwgcYwHQYDVR0OBBYEFP2Kyzw0LBvTf2Q5DjVpckh/2tXAMIGWBgNV 
HSMEgY4wgYuAFP2Kyzw0LBvTf2Q5DjVpckh/2tXAoWikZjBkMQswCQYDVQQGEwJK 
UDEOMAwGA1UECBMFVG9reW8xEjAQBgNVBAcTCU1pbmF0by1rdTESMBAGA1UEChMJ 
QkxVRUNPQVJBMR0wGwYDVQQDExRzZWN1cmUuYmx1ZWNvYXJhLm5ldIIJALnZerfv 
4y/qMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAOjeLtA8YNh6QUuoP 
tLYt6RcJgWLVqpkpFv8f3kJgY02AIeSvcWP7sHCFtALf4YkhKEG8eKfQt3kA2jHb 
C3XJzxWGHMLoMBwe3IPdQbeadp8NyWVq6PqlAp0SpX42qRA3oUK0w6d/S4GUnE0S 
8n8o7k/mglbG1Q+aQdY9JQbD+o8= 
-----END CERTIFICATE----- 

では、これらのファイルを便宜上/etc/openldapディレクトリ中にコピーしてください。安全のためサーバ鍵のパーミッションは管理者のみが参照できるように設定しておきます。

図3 ファイルの配置
# cp server.key /etc/openldap/
# cp server.crt /etc/openldap/
# chmod 600 /etc/openldap/server.key
# chown ldap:ldap /etc/openldap/server.*

また、公の認証局を使用する場合には、必要に応じてその認証局のCA情報を同ディレクトリに保存しておいてください。

slapd.confの設定

slapd.confの設定は非常にシンプルで、Apacheやその他サーバソフトウェアを設定するときと同じ概念となります。もちろんこれ以外に細かいパラメータは存在するのですが、SSL対応サーバを構築する場合、⁠サーバ鍵」⁠証明書」の2つを設定することで暗号化を実現することができます。

リスト3 slapd.confの一部
# CAバンドルが必要な場合には指定
#TLSCACertificateFile /etc/openldap/ca-bundle.crt 
TLSCertificateFile /etc/openldap/server.crt 
TLSCertificateKeyFile /etc/openldap/server.key 

最後にslapdプロセスを再起動して準備は完了です。このように証明書情報を適切にセットしておくと、通常の389/tcpだけではなくSSL専用の636/tcpが使用されるようになります。

クライアントからの接続確認

接続確認をするために、接続がldaps(LDAP+SSL)であることを-Hオプションを使用して明示します。

図4 クライアントからの接続
ldapsearch -x -H "ldaps://10.0.100.10/" -b "dc=example,dc=com" 
ldap_bind: Can't contact LDAP server (-1) 
	additional info: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed 

あれれ、エラーが表示されてしまいました。

冒頭で軽く触れたように、SSLの本来の目的とは暗号化だけではありません。たとえ通信が暗号化されていたとしても、フィッシングサイトと暗号化通信してしまってはセキュリティの意味も無いためです。自己証明書を使用した場合、暗号化を実現することができますが、クライアントは接続先を正当なサイトと判断することができないためこのエラーが表示されているわけです(いわゆるオレオレ証明書と呼ばれているものです⁠⁠。

そこで、ldapsearchコマンドが参照する設定ファイルを編集し、接続先の妥当性を確認しないようにしておきます。

リスト4 /etc/openldap/ldap.confの追加
TLS_REQCERT     never

もしOpenLDAPをソースからコンパイルした場合には、/usr/local/etc/openldap/ldap.confとなっている場合が多いでしょう。同ファイル名の/etc/ldap.confとは用途が異なるため注意してください。

正しく設定した場合は、エントリを検索できるようになっているはずです。

図5
% ldapsearch -x -H "ldaps://10.0.100.10/" -b "dc=example,dc=com" 
-- 省略 --
# hogeuser, People, example.com 
dn: uid=hogeuser,ou=People,dc=example,dc=com 
objectClass: account 
objectClass: posixAccount 
objectClass: ldapPublicKey 
uid: hogeuser 
cn: hogeuser 
loginShell: /bin/bash 
uidNumber: 2000 
gidNumber: 2000 
homeDirectory: /home/hogeuser 
sshPublicKey: ssh-dss AAAAB3NzaC1kc3MAAACBAJ5sBOzM/PCkETefX7yzrs+oEVOp3hwuBSpL 
 96pbkfoyQ5jvMABT6aGzXqQUTZu00Gke+G+CJeOg3rw9K7+ghrNuB4Rv33l1LdILjTTMFqDsvMo02 
 Un6DKv/EvAW++rarKDDU5DMJOEPqkOWTsPb683WP77fHcBxKsALVtVAFTMzAAAAFQDVPmzJd39IYv 
 KMWQsJQvzPefUX8wAAAIEAliBMDP2SFtPoAZMAbCtAwWtQmXh7C/+CJwrQEJMDYb1Pp+7jaOk+7Ag 
 sGMTA2abtSsPDvvhlrNXOyqx+EMYxibwnX4dnGS7NQAsQhqUmvqzzKfySD/UvJ6GQYtB9FMpju0L/ 
 qH5B5jtdfwggXTaGRXuadnzAZ7rrOOMvosqyhc8AAACBAInNQo10pbrnkp9grL+Db2/Rp1JXVajN0 
 2isPzfpS7uX9rohAlyTVLAjlwLwTGrp6CFwG4/t9e7jxlIo4Wm2r7LXgLr9u7+dg+oMENJpYkt/0N 
 tLBq40dICE8yhha58cQau5z98Ajc6dO9yvB2Bp6C3oDIiumPq/e2IMGjYYrtcP nomo@rx8 

実際にtcpdumpなどで636/tcpに流れるデータを確認してみてください。

StartTLS

こちらもOpenLDAPに限った話ではなく、SMTPやPOP3などでは比較的多く使われている技術ですが、OpenLDAPはStartTLSをサポートしています。StartTLSとは、接続の確立時には平文によるデータ通信を行い、STARTTLSコマンドをクライアントが発行することにより、以降の通信を全て暗号化してしまう技術です。

この接続はldapsearchコマンド-Zオプションを渡すだけで実現できます。それぞれのコマンドを実行中に389/tcpをtsharkコマンドでキャプチャしてみました。

リスト5 通常のldapsearch(389/tcpを使用)
496.959580   10.0.100.1 -> 10.0.100.10  TCP 51450 > ldap [SYN] Seq=0 Win=5840 Len=0 MSS=1460 TSV=63349770 TSER=0 WS=7 
496.960331  10.0.100.10 -> 10.0.100.1   TCP ldap > 51450 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 TSV=424013 TSER=63349770 WS=2 
496.960368   10.0.100.1 -> 10.0.100.10  TCP 51450 > ldap [ACK] Seq=1 Ack=1 Win=5888 Len=0 TSV=63349771 TSER=424013 
496.966756   10.0.100.1 -> 10.0.100.10  LDAP bindRequest(1) "" simple 
496.966934  10.0.100.10 -> 10.0.100.1   TCP ldap > 51450 [ACK] Seq=1 Ack=15 Win=5792 Len=0 TSV=424019 TSER=63349772 
496.967765  10.0.100.10 -> 10.0.100.1   LDAP bindResponse(1) success 
496.967786   10.0.100.1 -> 10.0.100.10  TCP 51450 > ldap [ACK] Seq=15 Ack=15 Win=5888 Len=0 TSV=63349772 TSER=424021 
496.968298   10.0.100.1 -> 10.0.100.10  LDAP searchRequest(2) "dc=example,dc=com" wholeSubtree 
496.971835  10.0.100.10 -> 10.0.100.1   LDAP searchResEntry(2) "dc=example,dc=com" 
496.973767  10.0.100.10 -> 10.0.100.1   LDAP searchResEntry(2) "ou=People,dc=example,dc=com" 
496.973854   10.0.100.1 -> 10.0.100.10  TCP 51450 > ldap [ACK] Seq=71 Ack=208 Win=5888 Len=0 TSV=63349774 TSER=424022 
496.974530  10.0.100.10 -> 10.0.100.1   LDAP searchResEntry(2) "ou=Group,dc=example,dc=com" 
496.975029  10.0.100.10 -> 10.0.100.1   LDAP searchResEntry(2) "cn=sshuser,ou=Group,dc=example,dc=com" 
496.975088   10.0.100.1 -> 10.0.100.10  TCP 51450 > ldap [ACK] Seq=71 Ack=417 Win=5888 Len=0 TSV=63349774 TSER=424025 
496.975724  10.0.100.10 -> 10.0.100.1   LDAP searchResEntry(2) "cn=hogeuser,ou=Group,dc=example,dc=com" 
496.976129  10.0.100.10 -> 10.0.100.1   LDAP searchResEntry(2) "uid=sshuser,ou=People,dc=example,dc=com" 
496.976146   10.0.100.1 -> 10.0.100.10  TCP 51450 > ldap [ACK] Seq=71 Ack=1411 Win=7680 Len=0 TSV=63349775 TSER=424027 
496.977116  10.0.100.10 -> 10.0.100.1   LDAP searchResEntry(2) "uid=hogeuser,ou=People,dc=example,dc=com" 
496.977590  10.0.100.10 -> 10.0.100.1   LDAP searchResDone(2) success 
496.977607   10.0.100.1 -> 10.0.100.10  TCP 51450 > ldap [ACK] Seq=71 Ack=2301 Win=9344 Len=0 TSV=63349775 TSER=424028 
496.977764   10.0.100.1 -> 10.0.100.10  LDAP unbindRequest(3) 
496.977975   10.0.100.1 -> 10.0.100.10  TCP 51450 > ldap [FIN, ACK] Seq=78 Ack=2301 Win=9344 Len=0 TSV=63349775 TSER=424028 
496.979528  10.0.100.10 -> 10.0.100.1   TCP ldap > 51450 [FIN, ACK] Seq=2301 Ack=79 Win=5792 Len=0 TSV=424031 TSER=63349775 
496.979562   10.0.100.1 -> 10.0.100.10  TCP 51450 > ldap [ACK] Seq=79 Ack=2302 Win=9344 Len=0 TSV=63349775 TSER=424031 
リスト6 StartTLS時のldapsearch(389/tcpを使用)
513.712634   10.0.100.1 -> 10.0.100.10  TCP 51451 > ldap [SYN] Seq=0 Win=5840 Len=0 MSS=1460 TSV=63353959 TSER=0 WS=7 
513.712858  10.0.100.10 -> 10.0.100.1   TCP ldap > 51451 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 TSV=426214 TSER=63353959 WS=2 
513.712890   10.0.100.1 -> 10.0.100.10  TCP 51451 > ldap [ACK] Seq=1 Ack=1 Win=5888 Len=0 TSV=63353959 TSER=426214 
513.713697   10.0.100.1 -> 10.0.100.10  LDAP extendedReq(1) LDAP_START_TLS_OID 
513.713825  10.0.100.10 -> 10.0.100.1   TCP ldap > 51451 [ACK] Seq=1 Ack=32 Win=5792 Len=0 TSV=426216 TSER=63353959 
513.714085  10.0.100.10 -> 10.0.100.1   LDAP extendedResp(1) [LDAP_START_TLS_OID responseName missing] 
513.714101   10.0.100.1 -> 10.0.100.10  TCP 51451 > ldap [ACK] Seq=32 Ack=15 Win=5888 Len=0 TSV=63353959 TSER=426217 
513.717707   10.0.100.1 -> 10.0.100.10  SSLv2 Client Hello 
513.721392  10.0.100.10 -> 10.0.100.1   TLSv1 Server Hello, Certificate, Server Hello Done 
513.722359   10.0.100.1 -> 10.0.100.10  TLSv1 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message 
513.726489  10.0.100.10 -> 10.0.100.1   TLSv1 Change Cipher Spec, Encrypted Handshake Message 
513.727464   10.0.100.1 -> 10.0.100.10  TLSv1 Application Data, Application Data 
513.727822  10.0.100.10 -> 10.0.100.1   TLSv1 Application Data, Application Data 
513.728385   10.0.100.1 -> 10.0.100.10  TLSv1 Application Data, Application Data 
513.728864  10.0.100.10 -> 10.0.100.1   TLSv1 Application Data, Application Data 
513.729058  10.0.100.10 -> 10.0.100.1   TLSv1 Application Data, Application Data 
513.729175  10.0.100.10 -> 10.0.100.1   TLSv1 Application Data, Application Data 
513.729871   10.0.100.1 -> 10.0.100.10  TCP 51451 > ldap [ACK] Seq=584 Ack=1533 Win=13056 Len=0 TSV=63353963 TSER=426228 
513.730069  10.0.100.10 -> 10.0.100.1   TLSv1 Application Data, Application Data, Application Data, Application Data, Application Data, Application Data, Application Data, 
513.730250  10.0.100.10 -> 10.0.100.1   TLSv1 Application Data 
513.730266   10.0.100.1 -> 10.0.100.10  TCP 51451 > ldap [ACK] Seq=584 Ack=3887 Win=18816 Len=0 TSV=63353963 TSER=426231 
513.731168   10.0.100.1 -> 10.0.100.10  TLSv1 Application Data, Application Data 
513.731567  10.0.100.10 -> 10.0.100.1   TLSv1 Encrypted Alert 
513.731691  10.0.100.10 -> 10.0.100.1   TCP ldap > 51451 [FIN, ACK] Seq=3924 Ack=658 Win=6864 Len=0 TSV=426233 TSER=63353963 
513.731914   10.0.100.1 -> 10.0.100.10  TLSv1 Encrypted Alert 
513.748263  10.0.100.10 -> 10.0.100.1   TCP ldap > 51451 [RST] Seq=3925 Win=0 Len=0 
513.748373   10.0.100.1 -> 10.0.100.10  TCP 51451 > ldap [RST, ACK] Seq=695 Ack=3925 Win=18816 Len=0 TSV=63353968 TSER=426232 

このように、同じ389/tcpを使用しているにもかかわらず、StartTLSを使用した場合には通信内容が暗号化されています。

最後に

今回はSSL/TLSによる通信経路の暗号化についてお話ししてきました。

暗号化は効果的なセキュリティ対策ですが、複雑な暗号化、復号化はCPU負荷を生み出すことを考慮しておかなければなりません。たとえば、メールサーバのルックアップデータベース用にLDAPを使用していた場合、メールの流量によってはLDAP検索速度にボトルネックが発生し、メール配送のスループットが落ちてしまうことがあります。⁠セキュリティ上必要だから」という漠然とした理由でメールサーバ、LDAPサーバ間の接続をSSLにしていては、さらにボトルネックが大きくなってしまう可能性もあります。

本当にそのネットワーク上にSSLが必要なのか検討した上で、さらに可能であれば事前にパフォーマンステストを行ってからSSLを導入するようにしてください。

おすすめ記事

記事・ニュース一覧