LDAP 内部账号管理系统

LDAP及其必要性

对于任何一个研发团队,一套内部通用的帐号管理系统都是必不可少的。请注意我的用词:”内部通用”。

公司内部可能有各种系统:

  • 行政层面的OA系统、邮件系统、会议室预订系统。
  • 研发团队内部又可能有代码管理、项目进度管理、Bug追踪、依赖管理、Wiki等等。

如果没有内部通用帐号,那么每来一个新员工,就需要到上述所有系统中,分别注册一次。想象一下,这是多么让人头疼的事情!

因此,我们建议团队一定要拥有一套”内部通用”的帐号管理系统。

在这里,我们选用了LDAP(Lightweight Directory Access Protocol)。是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息。

在技术型团队中,LDAP可以当作内部帐号管理系统来使用。此外,LDAP可以很轻松地与其他系统对接,我们后面即将构建的代码管理、版本管理,都将通过LDAP帐号接入。

OpenLDAP服务的初步配置

能提供LDAP服务的开源项目有很多,我们选用了较为成熟的开源服务器OpenLDAP。

虽然OpenLDAP并不是微服务,但我们依然放到Kubernetes集群部署,主要原因是:

  • 方便运维: 如果不用Docker,就需要手动的安装、配置。一旦物理服务器发生故障,需要迁移服务时,就需要重新执行这些操作。运维起来非常麻烦。
  • 方便备份与恢复: 对于这类帐号系统,可用性倒要求并不高(偶尔挂掉1个小时,能接受),但是对数据安全性,特别是备份有较高要求。使用Docker后,我们只需要将产生的数据挂载到Volume上,然后定期备份Volume即可。

来看一下部署文件openldap-deployment.yaml:

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: openldap-deployment
  5. spec:
  6. selector:
  7. matchLabels:
  8. app: openldap
  9. replicas: 1
  10. template:
  11. metadata:
  12. labels:
  13. app: openldap
  14. spec:
  15. restartPolicy: OnFailure
  16. nodeSelector:
  17. kubernetes.io/hostname: minikube
  18. containers:
  19. - name: openldap-ct
  20. image: osixia/openldap:1.1.9
  21. ports:
  22. - containerPort: 389
  23. hostPort: 389
  24. - containerPort: 636
  25. hostPort: 636
  26. volumeMounts:
  27. - mountPath: "/etc/ldap/slapd.d"
  28. name: volume
  29. subPath: conf
  30. - mountPath: "/var/lib/ldap"
  31. name: volume
  32. subPath: data
  33. env:
  34. - name: LDAP_TLS
  35. value: "false"
  36. - name: LDAP_DOMAIN
  37. value: "coder4.com"
  38. - name: LDAP_ADMIN_PASSWORD
  39. value: "admin123"
  40. - name: LDAP_READONLY_USER
  41. value: "true"
  42. - name: LDAP_READONLY_USER_USERNAME
  43. value: "guest"
  44. - name: LDAP_READONLY_USER_PASSWORD
  45. value: "guest123"
  46. volumes:
  47. - name: volume
  48. hostPath:
  49. path: /data/openldap/

这是一个很长的文件,我们来逐条解释下:

  • restartPolicy: 虽然这是一个内部服务,但我们还是希望它能稳定提供服务。如果万一服务挂掉,希望能自动重启。因此我们设置自动重启策略为OnFailure。
  • nodeSelector: 我们强制选择了主机名。即这个Pod只能启动在minikube这台hostname的主机上,为什么呢?因为我们的OpenLDAP服务使用了本地Volume(hostVolume),如果不固定机器,允许Pod在任意物理机启动的话,对应Volume并不会自动迁移,导致之前的账户信息”丢失”。因此,对于需要使用Volume的服务,要么选择一种可自动迁移的Volume,要么就需要绑定到一台物理机上。如果你想选用自动迁移的Volume,可以参考官方Volumes文档
  • ports: 我们直接对集群外暴露了389和636两个端口。在实际生产中,我建议选择一台独立的物理机部署所有的内部服务(ldap、maven、git等)。为什么这样搞呢?如果物理机是固定的,我们可以给它分配一个固定的办公网IP,甚至固定的办公网DNS域名,然后简单地通过暴露端口的方式,就可以对全部办公网提供服务了。
  • volumeMounts & volumes: 定义了两个volume挂载点,分别挂载到容器的/etc/ldap/slapd.d(配置)和/var/lib/ldap(数据)目录上。对应的物理机挂载目录在/data/openldap/conf和/data/openldap/data上。
  • env: 通过环境变量完成了一些初始化的设定,具体如下。
    • 不用加密协议^1
    • 设置域为coder4.com,可以根据你的需求自行更改。
    • 创建系统管理员帐号,密码是admin123,这是一个超级管理员,对应用户名是admin(无法更改)
    • 创建系统只读帐号,用户名和密码是guest/guest123。这主要是用于其他服务与OpenLDAP服务的通信,只能读取、验证信息,不能做任何更改。

在部署前,我们先要保证物理机上的挂载点存在。

  1. minikube ssh
  2. $ cd /data
  3. $ sudo mkdir openldap

然后部署OpenLDAP服务:

  1. kubectl apply -f ./openldap-deployment.yaml

查看下状态,启动成功了:

  1. kubectl get pods
  2. NAME READY STATUS RESTARTS AGE
  3. openldap-deployment-7d6b7875f-hxqxf 1/1 Running 0 14m

获取集群的IP:

  1. minikube ip
  2. 192.168.99.100

验证下,端口已经成功暴露给了集群外:

  1. telnet 192.168.99.100 389
  2. Trying 192.168.99.100...
  3. Connected to 192.168.99.100.
  4. Escape character is '^]'.
  5. ^]

操作ldap集群,需要安装一些工具,以Ubuntu为例:

  1. sudo apt-get install ldap-utils

有了工具后,两个系统帐号已经创建成功:

  1. ldapwhoami -h 192.168.99.100 -p 389 -D "cn=admin,dc=coder4,dc=com" -w admin123
  2. dn:cn=admin,dc=coder4,dc=com
  3. ldapwhoami -h 192.168.99.100 -p 389 -D "cn=guest,dc=coder4,dc=com" -w guest123
  4. dn:cn=guest,dc=coder4,dc=com

至此,我们已经完成了OpenLDAP的基础配置,并且成功创建了两个系统帐号。

创建内部用户

在刚才的配置中,我们创建了两个系统帐号,但在实际工作中,团队成员一般不会使用系统帐号。

对于一个团队成员,它的帐号至少需要有如下属性:

  • 用户名, 一般是纯英文、拼音缩写
  • 中文姓名,这个不解释了
  • 密码,最好不是明文,而是加密存储
  • 邮箱,公司内部的电子邮箱地址

大公司的内部,会细分为多个团队,此时还应当将用户划分到相应的属组。由于篇幅所限,我们在此不讨论属组的问题。

在密码加密方面,我们采用ssha,它需要命令slappasswd,你可以在任何安装了openldap的机器上找到它:

  1. slappasswd -h {ssha} -s pass123
  2. {SSHA}yG3DQj7iol10+fzWoeBAgoZ+D+h9uQre

上述即生成了一个ssha加密过的密码pass123。

我们前面已经提到,LDAP是一个”目录式”的权限管理服务。其本身规则非常复杂到可以单独写一本书:-)

本书不会对其规则进行过多讲解,这里先提供了一个简单的模板,供大家学习。

./users.ldif

  1. version: 1
  2. # users org
  3. dn: ou=users,dc=coder4,dc=com
  4. objectClass: top
  5. objectClass: organizationalUnit
  6. ou: users
  7. # group org
  8. dn: ou=groups,dc=coder4,dc=com
  9. objectClass: top
  10. objectClass: organizationalUnit
  11. ou: groups
  12. # define users here
  13. dn: cn=lihy,ou=users,dc=coder4,dc=com
  14. objectClass: top
  15. objectClass: person
  16. objectClass: organizationalPerson
  17. objectClass: inetOrgPerson
  18. cn: lihy
  19. sn:: 5p2O6LWr5YWD
  20. mail: lihy@coder4.com
  21. userPassword: {SSHA}yG3DQj7iol10+fzWoeBAgoZ+D+h9uQre
  22. dn: cn=zhangsan,ou=users,dc=coder4,dc=com
  23. objectClass: top
  24. objectClass: person
  25. objectClass: organizationalPerson
  26. objectClass: inetOrgPerson
  27. cn: zhangsan
  28. sn:: 5byg5LiJ
  29. mail: zhangsan@coder4.com
  30. userPassword: {SSHA}yG3DQj7iol10+fzWoeBAgoZ+D+h9uQre
  31. # should also modify here if insert new user
  32. dn: cn=Users,ou=groups,dc=coder4,dc=com
  33. objectClass: top
  34. objectClass: groupOfUniqueNames
  35. cn: Users
  36. uniqueMember: cn=lihy,ou=users,dc=coder4,dc=com
  37. uniqueMember: cn=zhangsan,ou=users,dc=coder4,dc=com
  38. # define admin here
  39. dn: cn=Admin,ou=groups,dc=coder4,dc=com
  40. objectClass: top
  41. objectClass: groupOfUniqueNames
  42. cn: Admin
  43. uniqueMember: cn=lihy,ou=users,dc=coder4,dc=com

简单解释下:

  • 我们创建了2个组users和groups,前者存放用户,后者表示用户的属组。
  • 定义两个用户lihy和zhangsan,他们的密码用前面提到的SSLA加密
  • 将两个用户加入Users组内
  • 将lihy用户加入管理员组内

我们来应用这个模板:

  1. ldapadd -c -h 192.168.99.100 -p 389 -w admin123 -D "cn=admin,dc=coder4,dc=com" -f ./users.ldif

如上,需要用admin帐号,-c选项是忽略所有错误,继续执行。

验证一下新增的内部用户:

  1. ldapwhoami -h 192.168.99.100 -p 389 -D "cn=lihy,ou=users,dc=coder4,dc=com" -w pass123
  2. dn:cn=lihy,ou=users,dc=coder4,dc=com

添加新用户,需要操作三个步骤:

  1. 在user.idlf中增加用户的定义
  2. 在user.idlf对应属组中添加
  3. 执行ldapadd命令

LDAP系统管理脚本

不用我说大家也明白,上述步骤真的是非常繁琐,而且容易出错。

面对这种情况,大家可以选用第三方的工具来管理LDAP帐号,例如phpLDAPadmin,但是这需要额外维护一套系统,不免有些笨重。

为了降低维护成本,我提供了几个简单的小脚本,以满足日常的管理工作。

添加帐号,ldap_add.sh

  1. #!/bin/bash
  2. # const
  3. LDAP_SERVER_IP="192.168.99.100"
  4. LDAP_SERVER_PORT="389"
  5. LDAP_ADMIN_USER="cn=admin,dc=coder4,dc=com"
  6. LDAP_ADMIN_PASS="admin123"
  7. if [ x"$#" != x"3" ];then
  8. echo "Usage: $0 <username> <password> <realname>"
  9. exit -1
  10. fi
  11. # param
  12. USERNAME="$1"
  13. PASSWORD="$2"
  14. ENCRYPT_PASSWORD=$(slappasswd -h {ssha} -s "$PASSWORD")
  15. REALNAME="$3"
  16. REALNAME_BASE64=$(echo -n $REALNAME | base64)
  17. # add count & group
  18. cat <<EOF | ldapmodify -c -h $LDAP_SERVER_IP -p $LDAP_SERVER_PORT -w $LDAP_ADMIN_PASS -D $LDAP_ADMIN_USER
  19. dn: cn=$USERNAME,ou=users,dc=coder4,dc=com
  20. changetype: add
  21. objectClass: top
  22. objectClass: person
  23. objectClass: organizationalPerson
  24. objectClass: inetOrgPerson
  25. cn: $USERNAME
  26. sn:: $REALNAME_BASE64
  27. mail: $USERNAME@coder4.com
  28. userPassword: $ENCRYPT_PASSWORD
  29. dn: cn=Users,ou=groups,dc=coder4,dc=com
  30. changetype: modify
  31. add: uniqueMember
  32. uniqueMember: cn=$USERNAME,ou=users,dc=coder4,dc=com
  33. EOF

上述脚本通过ldapmodify命令,自动完成了我们之前提到的三个步骤。

我们试着添加新用户lisi

  1. ./ldap_add.sh lisi pass123 李四
  2. adding new entry "cn=lisi,ou=users,dc=coder4,dc=com"
  3. modifying entry "cn=Users,ou=groups,dc=coder4,dc=com"

验证一下,添加成功

  1. ldapwhoami -h 192.168.99.100 -p 389 -D "cn=lisi,ou=users,dc=coder4,dc=com" -w pass123
  2. dn:cn=lisi,ou=users,dc=coder4,dc=com

第二个常见的情况是,修改密码, ./ldap_modify_password.sh:

  1. #!/bin/bash
  2. # const
  3. LDAP_SERVER_IP="192.168.99.100"
  4. LDAP_SERVER_PORT="389"
  5. LDAP_ADMIN_USER="cn=admin,dc=coder4,dc=com"
  6. LDAP_ADMIN_PASS="admin123"
  7. if [ x"$#" != x"2" ];then
  8. echo "Usage: $0 <username> <newPassword>"
  9. exit -1
  10. fi
  11. # param
  12. USERNAME="$1"
  13. PASSWORD="$2"
  14. ENCRYPT_PASSWORD=$(slappasswd -h {ssha} -s "$PASSWORD")
  15. # modify
  16. cat <<EOF | ldapmodify -c -h $LDAP_SERVER_IP -p $LDAP_SERVER_PORT -w $LDAP_ADMIN_PASS -D $LDAP_ADMIN_USER
  17. dn: cn=$USERNAME,ou=users,dc=coder4,dc=com
  18. changetype: modify
  19. replace: userPassword
  20. userPassword: $ENCRYPT_PASSWORD
  21. EOF

我们尝试修改lisi的密码:

  1. ./ldap_modify_password.sh lisi hahaha
  2. modifying entry "cn=lisi,ou=users,dc=coder4,dc=com"

验证一下,新密码已经修改成功:

  1. ldapwhoami -h 192.168.99.100 -p 389 -D "cn=lisi,ou=users,dc=coder4,dc=com" -w hahaha
  2. dn:cn=lisi,ou=users,dc=coder4,dc=com

最后一个场景是删除用户,这里我们只删除用户,不删除其加入的属组

  1. #!/bin/bash
  2. # const
  3. LDAP_SERVER_IP="192.168.99.100"
  4. LDAP_SERVER_PORT="389"
  5. LDAP_ADMIN_USER="cn=admin,dc=coder4,dc=com"
  6. LDAP_ADMIN_PASS="admin123"
  7. if [ x"$#" != x"1" ];then
  8. echo "Usage: $0 <username>"
  9. exit -1
  10. fi
  11. # param
  12. USERNAME="$1"
  13. # delete user
  14. ldapdelete -c -h $LDAP_SERVER_IP -p $LDAP_SERVER_PORT -w $LDAP_ADMIN_PASS -D $LDAP_ADMIN_USER "cn=$USERNAME,ou=users,dc=coder4,dc=com"

尝试删除一下:

  1. ./ldap_delete.sh zhangsan

然后验证下,确实无法登录了

  1. ldapwhoami -h 192.168.99.100 -p 389 -D "cn=zhangsan,ou=users,dc=coder4,dc=com" -w pass123
  2. ldap_bind: Invalid credentials (49)

至此,我们完成了LDAP服务的构建,并可以通过简单的脚本完成帐号的添删改操作。