迁移 LDAP 用户到 Google Apps 平台

人手和精力不足的情况下自己维护邮件服务器实在是件头疼的事情,我们打算把这件吃力不讨好的事交给 Google,有免费的 Google Apps for Education 为啥要自己弄呢?Gmail 要比自己搭建的 SquirrelMail 和 roundcube 界面好用多了,而且还有免费的 Google Docs, Google Drive, Google Chat 可用,非常适合我们现在的情况。

我们的用户信息都保存在一台 LDAP 服务器上,要把全部用户信息(用户名、密码、Email、电话等)迁移到 Google Apps 的话需要迁移工具(没人想手动输入500个用户和电话!)。自己用 Python-LDAP 和 Google Apps Administrative APIs 写个 LDAP 到 Google Apps 的转换工具应该不难,不过要更懒一点,找现成工具。。。

Google 为帮助我们这些懒人和企业用户把数据迁到云端开发了不少工具,Google Apps Directory Sync 正是我们要找到,一个用于同步 LDAP 活动目录到 Google Apps 的好工具。

步骤

1、首先登陆到 Google Apps 平台,在自己帐号里的 Domain settings 的 User settings 页面选择 “Enable provisioning API”;

google apps directory sync

2、下载 Windows 或 Linux 版本的 Google Apps Directory Sync 工具并安装;

3、打开 Google Apps Directory Sync 工具,在里面依次配置 General Settings, Google Apps Configuration, LDAP Configuration, User Accounts, Notifications 等,LDAP 配置支持 MS Active Directory, OpenLDAP, Lotus Domino,我们的 LDAP 服务器用的是 OpenLDAP;

4、如果一切正常,在 Sync 里的 Validation Results & Sync 会看到 “打勾” 状态;

5、在真正同步(Sync & apply changes)之前先模拟(Simulate sync)一遍,点击下面蓝色的 “Simulate sync” 会出来结果报告;

google apps directory sync

6、查看上面的模拟结果,如果符合自己的要求的话就可以点击红色的 “Sync & apply changes” 开始实际操作了。

使用 Python-LDAP 操作 LDAP

周末看到那些排队血拼的人们,不用走进 shopping mall、不用看到那些五颜六色的打折和视觉冲击就能感受到 “节日要到了!”。一年又快结束了,这周完成备份、升级之类的收尾工作,接下来就是6周的假期,没啥大安排,假期第1周去南非德班参加高性能计算会议,回来后和家人短途旅行,然后圣诞节在家休息学点新东西,比如修车什么的,几次痛苦经历告诉我出来玩迟早是要坏的,对于 hiking/camping/road trip/4×4 这几个关键字的爱好者来说懂点维修常识是必须的。废话留到假期再说吧,接下来六周可能没有技术方面的博客更新~

最近对 LDAP 服务器上面的数据做处理,有机会接触了一下 Python-LDAP 这个库和 LDAP/Kerberos. 去除所有打印和错误处理的代码后,用 Python-LDAP 操作 LDAP 的骨干代码其实很简单,就这么几行,唯一遇到的一个小麻烦就是折腾了一个多小时才知道 ‘TRUE’ 要大写(后面有说到)。

安装 Python-LDAP

在 Ubuntu/Debian 下安装 python-ldap 模块:

$ sudo apt-get install python-ldap

在 CentOS/RHEL 下安装 python-ldap 模块:

# yum install python-ldap

创建

创建一条 LDAP 新纪录。有个要注意的地方,我们的 LDAP 有个属性 active,用来判断用户帐号是否是激活的 attrs[‘active’] = ‘TRUE’,这里的 ‘TRUE’ 不能用小写的 ‘true’,刚开始被 LDAP 管理工具上的小写 ‘true’ 误导,老以为 Python 程序里也应该用小写,结果总报错。

phpLDAPadmin

def ldap_add(firstname, lastname, username):
    l = ldap.open(LDAP_HOST)
    l.protocol_version = ldap.VERSION3
    l.simple_bind(LDAP_BIND, LDAP_PASS)

    cn = firstname + ' ' + lastname
    addDN = "cn=%s,ou=People,dc=vpsee,dc=com" % cn
    attrs = {}
    attrs['objectclass'] = ['top','person','inetOrgPerson','posixAccount','vpseeAccount']
    attrs['cn'] = cn
    attrs['givenName'] = firstname
    attrs['homeDirectory'] = '/home/people/%s' % username
    attrs['loginShell'] = '/bin/bash'
    attrs['sn'] = lastname
    attrs['uid'] = username
    attrs['uidNumber'] = ldap_newuid()
    attrs['gidNumber'] = ldap_getgid()
    attrs['active'] = 'TRUE'
    ldif = modlist.addModlist(attrs)
    l.add_s(addDN, ldif)
    l.unbind_s()

查找和读取

查找和读取一条 LDAP 纪录,比如根据 username 查找出 cn:

def ldap_getcn(username):
    try:
        l = ldap.open(LDAP_HOST)
        l.protocol_version = ldap.VERSION3
        l.simple_bind(LDAP_BIND, LDAP_PASS)

        searchScope = ldap.SCOPE_SUBTREE
        searchFilter = "uid=*" + username + "*"
        resultID = l.search(LDAP_BASE, searchScope, searchFilter, None)
        result_set = []
        while 1:
            result_type, result_data = l.result(resultID, 0)
            if (result_data == []):
                break
            else:
                if result_type == ldap.RES_SEARCH_ENTRY:
                    result_set.append(result_data)
        return result_set[0][0][1]['cn'][0]
    except ldap.LDAPError, e:
        print e

更新

更新一条 LDAP 纪录,比如更新用户状态 active 为 false:

def ldap_deactive(username):
    try:
        l = ldap.open(LDAP_HOST)
        l.protocol_version = ldap.VERSION3
        l.simple_bind(LDAP_BIND, LDAP_PASS)

        deactiveDN = ("cn=%s," + LDAP_BASE) % ldap_getcn(username)
        old = {'active':'TRUE'}
        new = {'active':'FALSE'}
        ldif = modlist.modifyModlist(old, new)
        l.modify_s(deactiveDN, ldif)
        l.unbind_s()
    except ldap.LDAPError, e:
        print e

删除

删除一条 LDAP 纪录:

def ldap_delete(username):
    try:
        l = ldap.open(LDAP_HOST)
        l.protocol_version = ldap.VERSION3
        l.simple_bind(LDAP_BIND, LDAP_PASS)

        deleteDN = ("cn=%s," + LDAP_BASE) % ldap_getcn(username)
        l.delete_s(deleteDN)
    except ldap.LDAPError, e:
        print e

在 Ubuntu 12.04 上配置 Radius 和 LDAP 服务

上次提到了我们实验室的 Wi-Fi 更改了认证方式,不再使用 WPA2/PSK 通用密码,而是通过 LDAP 把 Wi-Fi 上网密码和个人帐号联系起来,大家只需要用自己平时的 Unix/Linux/Mail/GroupWise 帐号就可以登陆 Wi-Fi,那么如何配置这样一个系统呢?涉及到的东西有点多,首先需要一个支持 Radius 的无线路由器,然后需要一台装有 Radius 的服务器,这台服务器还需要作为 LDAP 客户端和 LDAP 服务器连起来。

准备工作

  • Linksys WRT 54GS 无线路由器,可以替换开源固件的 hacker-friendly 路由器;
  • DD-WRT 固件,刷固件的过程可以参考:把 Linksys WRT54GL 路由器当作交换机用
  • FreeRadius 服务,安装在一台 Ubuntu 服务器上;
  • LDAP 客户端,安装在同一台 Ubuntu 服务器上;
  • LDAP 服务器,一般安装在另一台服务器上,也可以和上面的 FreeRadius/LDAP client 装在一起,LDAP 服务器的配置和安装过程超出本文范围,这里不介绍。

配置 Wi-Fi

刷固件以后按照下图配置 Linksys WRT 54GS,指定 Radius 服务器的 IP 和端口、安全模式、WPA 算法、WPA Shared Key 等,这个 WPA Shared Key 由下面的 FreeRadius 配置指定:

dd-wrt and radius

安装和配置 LDAP

这里只是安装 LDAP 客户端,不包括 LDAP 服务器的安装,所以需要事先知道 LDAP 服务器的 IP 或域名等相关信息。

$ sudo apt-get install ldap-auth-client ldap-auth-config libpam-ldap

填入 LDAP 服务器地址 host, base 等:

$ sudo vi /etc/ldap.conf
host ldap.vpsee.com
base dc=vpsee,dc=com
pam_password exop
pam_lookup_policy yes

nss_initgroups_ignoreusers Debian-exim,backup,bin,daemon,dhcp,games,gnats,irc,klog,libuuid
,list,lp,mail,man,news,ntpd,proxy,root,snmp,sshd,statd,sync,sys,syslog,uucp,www-data

确认 nsswitch.conf 先搜索本地文件,用户登陆的时候先检索本地 /etc/passwd 找不到相关用户名再检索 ldap 服务器上的用户数据库:

$ sudo vi /etc/nsswitch.conf
passwd:         files ldap
group:          files ldap
shadow:         files ldap
automount:      files

hosts:          files dns
networks:       files

protocols:      db files
services:       db files
ethers:         db files
rpc:            db files

安装和配置 FreeRadius

安装 FreeRadius:

$ sudo apt-get install freeradius freeradius-utils freeradius-ldap openssl 

编辑 clients.conf,确定哪个 IP 范围可以访问 Radius 服务,这里 secret 一行的 testing123 就是上面 DD-WRT 要用到的 WPA Shared Key:

$ sudo vi /etc/freeradius/clients.conf
...
client 192.168.2.0/24 {
        require_message_authenticator = no
        secret          = testing123
        shortname       = vpsee-network
}
...

配置 default,确定 pam 一行没有被注释掉:

$ sudo vi /etc/freeradius/sites-available/default
...
authorize {
        preprocess
        chap
        mschap
        suffix
        eap {
                ok = return
        }
        unix
        files
        expiration
        logintime
        pap
}
...
authenticate {
        Auth-Type PAP {
                pap
        }
        Auth-Type CHAP {
                chap
        }
        Auth-Type MS-CHAP {
                mschap
        }
        pam
        unix
        eap
}
...

同样的确定 inner-tunnel 配置文件里面的 pam 一行没有被注释掉:

$ sudo vi /etc/freeradius/sites-available/inner-tunnel
...
authenticate {
        Auth-Type PAP {
                pap
        }
        Auth-Type CHAP {
                chap
        }
        Auth-Type MS-CHAP {
                mschap
        }
        pam
        unix
        eap
}
...

在 users 加入下面认证类型:

$ sudo vi /etc/freeradius/users
...
DEFAULT Auth-Type = PAM
      Fall-Through = 1
...

测试

配置完毕后重启 freeradius:

$ sudo /etc/init.d/freeradius restart

如果要 debug 的话这样启动 freeradius:

$ sudo /etc/init.d/freeradius stop
$ sudo freeradius -X

先测试使用 LDAP 方式登陆系统是否正确,使用 LDAP 用户名和密码从另一台电脑 ssh 登陆到这台 Radius 服务器,如果不用本地帐号而用 LDAP 帐号顺利登陆的话就说明这台服务器已经能顺利使用 LDAP 登陆了:

$ ssh vpsee@192.168.2.65

测试 Radius 服务是否能正确从 LDAP 那里得到验证信息,使用 ldap 用户名和密码测试 Radius 服务是否反馈正确消息 Access-Accept:

$ radtest vpsee "password" 192.168.2.65 1 testing123
Sending Access-Request of id 233 to 192.168.2.65 port 1812
	User-Name = "vpsee"
	User-Password = "password"
	NAS-IP-Address = 192.168.2.65
	NAS-Port = 1
rad_recv: Access-Accept packet from host 192.168.2.65 port 1812, id=233, length=20

另一种办法

另外有个办法是不用 pam 的方式,直接用 freeradius-ldap 模块和 LDAP 服务器连接,这种办法不适合我们实验室的应用场景,不过配置要简单一些。简单说一下就是利用 freeadius 的 ldap 模块直接连接 LDAP 服务器验证用户。先安装 freeradius-ldap 模块:

$ sudo apt-get install freeradius-ldap

然后配置 clients.conf

$ sudo vi /etc/freeradius/module/ldap
...
ldap {
        server = "ldap.vpsee.com"
        identity = "cn=admin,o=My Org,c=UA"
        password = mypass
        basedn = "o=My Org,c=UA"
        filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})"
...

然后取消下面2个文件里面关于 ldap 的相关注释:

$ sudo vi /etc/freeradius/sites-available/default
$ sudo vi /etc/freeradius/sites-available/inner-tunnel

重启 freeradius 让配置生效:

$ sudo /etc/init.d/freeradius restart

Mac OS X Lion 上使用 TTLS/PAP 访问 Wi-Fi

我们实验室的 Wi-Fi 更改了认证方式,不再采用简单的 WPA2/PSK (Pre-Shared Key),这种方式的弊端显而易见,所有人都知道 Wi-Fi 密码,大家容易把这个 “共用” 密码传给外人,如果是 “个人” 密码的话会好一些,所以我们打算通过 LDAP 把这个密码和个人的邮件密码连在一起,这样会有效的阻止密码扩散(不会有人轻易告诉别人自己的邮件密码),还减轻了管理员经常换密码的痛苦。除了密码管理方面的好处外,还可以通过 Radius 对用户进行统计、对通讯加密等等。我们采用了 DD-WRT + Radius + LDAP 这种方式和现有的基础设施连在一起,用户通过 Linksys WRT 54GS 无线 AP 输入自己内部的 LDAP 帐号就可以登陆 Wi-Fi,和我们其他服务一样 “一次登陆到处使用”。

Mac OS X Lion (10.7) 上默认使用的是 TTLS/MSCHAPv2 方式,如果使用 TTLS/PAP 的话就需要更改配置,问题是 Mac OS X Lion 的配置文件页面没有提供任何方式更改或新建配置来支持 PAP. 打开 Apple > System Preferences … > Network > Wi-Fi > Advanced … > 802.1X 后会发现是空的列表,没有新建按钮也没有导人,怎么增加一个 Profile 呢?我记得 Mac OS X Snow Leopard (10.6) 是有个新建按钮的,不知道为啥 Apple 把这个按钮去掉了,官方给的解释是通常这个 .mobileconfig 配置文件都是由网络管理员提供的,用户拿到这个文件双击就可以完成配置,不需要自己动手配置。

解决这个问题有两个办法:第一个是下载一个别人配置好的.mobileconfig 文件,其实就是一个 xml,然后调整一下后双击使用;第二个是下载 iPhone Configuration Utility 工具自己创建一个 .mobileconfig 文件。办法一可以直接到这里下载 .mobileconfig 后修改,这里不详细介绍了。这里采用办法二:

1、下载和安装 iPhone Configuration Utility 3.5 for Mac OS X 工具
2、打开 Applications > Utilities > iPhone Configuration Utility.app 选择左边列表的 Configurations Profiles;
3、按照下图在 General 页面填写信息;

ttls/pap on mac os x

4、按照下图在 Wi-Fi 页面填写必要的信息,比如 SSID 等,注意选择 WPA/WPA2 Enterprise, TTLS 和 PAP;

ttls/pap on mac os x

5、点击菜单的 Export 配置到某个文件,然后双击这个后缀名为 .mobileconfig 的文件就会自动导入到上面的 802.1X 页面,会提示输入 Radius 服务器的登陆信息等;
6、最后切换无线网络测试一下。