LDAP在企业开发中常用的一种协议,实现企业账户统一管理的方案。常用的LDAP开源方案主要是openldap, Debian系列和CentOS系统都会自带,但需要注意配置文件不同。在基于LDAP开发相关功能时,由于LDAP协议属于明文协议,业务对安全性要求较高的场合需要搭配TLS完成对数据加密传输。LDAP默认支持TLS协议,开放的端口是636。
0x1 基础配置
openldap服务配置TLS:
slapd手动运行
1 2
| /usr/sbin/slapd -u ldap -h ldap:/// ldaps:///
|
service服务运行 (CentOS环境)
验证
1 2 3
| ps uax|grep slapd
lsof -i:636
|
openldap docker服务配置(简易):
- 环境变量设置
1 2 3 4 5 6
| LDAP_TLS: true LDAP_TLS_CRT_FILENAME: ldap.crt LDAP_TLS_KEY_FILENAME: ldap.key LDAP_TLS_DH_PARAM_FILENAME: dhparam.pem LDAP_TLS_CA_CRT_FILENAME: ca.crt
|
0x2 开发使用
经过以上简单配置,ldap已经支持TLS传输数据了。开发语言中实现ldap客户端的方式不同,需要注意开发语言官方文档
1 2 3 4 5 6 7 8
| yum install cyrus-sasl cyrus-sasl-devel cyrus-sasl-ldap
phpbrew ext install ldap -- --with-ldap=/usr/local/Cellar/openldap/2.4.47 --with-ldap-sasl=/usr/local/opt/cyrus-sasl
php --ri ldap
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| echo "TLS_REQCERT never" >> /etc/openldap/ldap.conf
<?php $host = "127.0.0.1"; $port = 389;
$username = 'cn=username,ou=people,dc=test,dc=com' $password = '123456'
$conn = ldap_connect($host, $port);
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
ldap_start_tls($conn);
$auth = ldap_bind($conn, $username, $password); echo $auth ? "ok" : "fail";
<?php $host = "ldaps://127.0.0.1"; $port = 636;
$username = 'cn=username,ou=people,dc=test,dc=com' $password = '123456'
$conn = ldap_connect($host, $port);
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
$auth = ldap_bind($conn, $username, $password); echo $auth ? "ok" : "fail";
|
1 2 3 4 5 6 7 8
| import ldap ldap = "ldap://127.0.0.1:389" ldaps = "ldaps://127.0.0.1:636" ldapconn = ldap.initialize(ldaps)
username = 'cn=username,ou=people,dc=test,dc=com' password = '123456' ldapconn.simple_bind_s(username, password)
|
1 2 3 4 5 6 7 8 9 10 11 12
| l, err := Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) if err != nil { log.Fatal(err) } defer l.Close()
err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { log.Fatal(err) }
|
0x3 TLS分析
上述的配置基本实现了ldap通信数据加密需求,如果留意代码中注释的话,会对文章开头TLS端口636与代码中实际使用却不同。这个需要指出的是客户端语言实现不同导致的。
以PHP语言为例分析LDAP扩展中实现通信逻辑:
- 使用636端口测试
1 2 3
| 输出结果 `PHP Warning: ldap_start_tls(): Unable to start TLS: `
|
- 关闭636端口测试
在PHP文档ldap_start_tls函数评论中提到,ldaps和startTLS不是一回事,ldaps服务开放636端口,389端口支持加密和非加密数据传输; 并且ldaps不推荐使用。
PHP扩展ldap本质是调用openldap库中的函数实现,重点分析openldap中ldap_connect
和ldap_start_tls
两个函数实现。
笔者以openldap-2.4.47版本分析startTLS和ldaps实现及区别
openldap库目录结构:
1 2 3 4 5 6 7 8
| openldap-2.4.47 - libraries/libldap/* - init.c - open.c. - tls2.c. - options.c - url.c. - request.c
|
ldap_connect函数主要实现功能
1 2 3 4 5 6 7 8
| int ldap_initialize( LDAP **ldp, LDAP_CONST char *url )
int ldap_create( LDAP **ldp )
int ldap_set_option(LDAP *ld, int option, LDAP_CONST void *invalue)
|
ldap_start_tls函数主要实现功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| int ldap_start_tls_s(LDAP *ld, LDAPControl **serverctrls, LDAPControl **clientctrls) {
if ( ldap_tls_inplace( ld ) ) { return LDAP_LOCAL_ERROR; } rc = ldap_extended_operation_s( ld, LDAP_EXOP_START_TLS, NULL, serverctrls, clientctrls, &rspoid, &rspdata ); if ( rc == LDAP_SUCCESS ) { rc = ldap_int_tls_start( ld, ld->ld_defconn, NULL ); }
return rc;
}
|
ldap使用流程对比:
1 2
| ldap_connect -> ldap_bind ldap_connect -> ldap_start_tls -> ldap_bind
|
ldap_start_tls在bind认证前开启TLS验证,ldaps是在ldap_bind操作时再初始化TLS会话,身份认证操作。两种操作在底层实现相差不大,主要是服务端389端口支持加密和非加密两种功能。
slapd服务端连接判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| Connection * connection_init( ber_socket_t s, Listener *listener, const char* dnsname, const char* peername, int flags, slap_ssf_t ssf, struct berval *authid LDAP_PF_LOCAL_SENDMSG_ARG(struct berval *peerbv)) {
#ifdef HAVE_TLS if ( flags & CONN_IS_TLS ) { c->c_is_tls = 1; c->c_needs_tls_accept = 1; } else { c->c_is_tls = 0; c->c_needs_tls_accept = 0; } #endif
slap_sasl_open( c, 0 ); slap_sasl_external( c, ssf, authid );
slapd_add_internal( s, 1 );
backend_connection_init(c);
|
有一点需要注意的是客户端使用ldap_start_tls函数时,会自动加载本地目录/etc/openldap/ldap.conf
文件, 具体参考PHP测试说明。其中ldap.conf配置内容参考man ldap.conf
命令说明及官方文档。
0x4 参考
- openldap TLS文档
- openldap docker
- php TLS使用
- golang-ldap库