Featured image of post Java基础知识:LDAP

Java基础知识:LDAP

Java基础知识补充

What’s LDAP

Lightweight Directory Access Protocol -> LDAP

直译:轻量级目录访问协议(Directory -> 又可译为电话簿),但是意义不准确

不管是目录还是电话簿,核心点为查找

即 Directory 服务内部的数据组织形式是树形结构,这样做的目的是方便查找。其最开始存在的目的是做白页、黄页类应用,而我们可以利用这个结构做其它事情,比如用一个LDAP服务管理公司内所有地方的用户信息,这样就可以做到单点登录

白页:个人通信目录,这个在上世纪的美国电影中能见到。为什么是白页—以前使用白色纸张记录个人通信目录。黄页也是这个原因。 黄页:企业和团体通信目录,例如80、90后熟知的hao123网址黄页

Protocol Brief Introduction

与LDAP相关的协议很多,但主要是两个系列,这里列出,仅做了解用,感兴趣可以翻看

Series Protocol

X.500

X.500系列协议

目录服务的最初协议,完整地描述了目录、目录服务的组成、目录结构等内容。X.500本身只是一个概览,各部分的实现在其子协议如X.501、X.502中,所以说X.500是一个系列协议。

值得一提的是,常见的X.509公钥证书也属于该系列协议。

LDAP

LDAP并非只有一个协议文件,其涉及到方方面面,每个方面单独由一个协议文件描述,加星号的相对来说很重要

LDAP系列协议

RFC4511*:通过TCP传输消息的方式,规定了传输层面的消息类型、消息内容,如绑定、查询等

RFC4512*:信息模型,规定了schema、object class、attribute type等内容

RFC4513*:规定了鉴权方式和安全机制:TLS、SASL

RFC4514:规定了DN的字符串表示方式

RFC4515:规定了过滤器的格式,过滤器用于查找时指定条件

RFC4516:规定了统一资源定位符的格式,即LDAP端点长啥样

RFC4517*:语法和匹配规则,列举了所有允许的语法和匹配规则

RFC4518:字符串国际化

RFC4519*:应用程序的schema,列举了所有允许的object class和attribute type

RFC2377*:推荐的DIT组织方式,即目录服务的树的组织方式

Abbr

LDAP 总是会有一大堆相关缩写,这里列出常见的

  • 常见

DAP - Directory Access Protocol,即目录访问协议

LDAP - Lightweight Directory Access Protocol,轻量级目录访问协议

DIB - Directory Information Base,目录信息库

DIT - Directory Information Tree,目录信息树

DUA - Directory User Agent,用户代理

DN - Distinguished Name,可区别名称,即唯一名称

RDN - Relative Distinguished Name,相对唯一名称,指DIT内某个节点上的名称,上层节点的DN配合上本节点的RDN,能够构成本节点的DN

  • 一些属性类型的介绍

c - country name,国家名

cn - common name,通用名称

dc - domain component,域名组件

o - organization name,机构名

ou - organization unit name,机构的单位名

sn - surname,姓

st - state or province name,州或省

Understand X.500

如果不看X.500协议,LDAP中的很多东西是看不懂的。X.500定义了目录服务,LDAP只是它的轻量级实现。

所以如果对 X.500 有一些差不多的认知,LDAP就已经可以理解了,毕竟是阉割版

我们看看 X.500 规定了什么。

定义目录系统

Directory旨在提供一个用户友好的name-address类的映射,其中name不可变,address允许动态变化,即key-value结构。Directory由一批系统组成,每个系统持有对应现实世界的逻辑数据,这些存储的数据叫做DIB,即目录信息库。整体架构如下

image-20241125181515404

一个标准的目录系统,有以下几部分

  • 目录服务
  • 目录服务的用户
  • 访问端点-目录服务用来暴露自己
  • 用户和服务之间使用协议如LDAP进行交互
  • 用户使用LDAP客户端访问目录服务

DIB与DIT

目录信息库,由DIT(目录信息树)、节点Entry、Entry中的属性及属性值构成。

  • Entry代表一个节点,类型可以是object,也可以是alias,后者表示一个object entry的别名
  • 一个Entry内可以拥有多个属性
  • 一个属性内可以拥有一个Type,以及多个Value

下图展示了一个DIT的树形结构:

image-20241125181752110

一个假设的DIT如下

image-20241125181942148

DN:{C=GB, L=Winslow, O=Graphic Services, CN=Laser Printer}代表了Laser Printer

你可能会想,DIB和常见的数据库有什么关系呢?

实际上他们没什么关系

类似HTTP和TCP。硬要说有关系的话,DIB更加垂直吧🤔,仅适用于这种树形存储结构,查询多、更新少;

数据库则更加通用,相对而言也更加底层。

DIB只是规定了一种存储键值对数据的树形结构,可以用任何方式实现DIB,包括关系型数据库。理论上,HTTP也可以是其它实现,比如QUIC。

目录服务

对目录的一些增删改查操作,看一眼就行
  • 读操作

    • Read:读指定的entry。LDAP不支持此操作
    • Compare:比较给的值和指定entry的值是否一致。这个在验证密码时有用
    • List:列出指定entry的所有子entry
    • Search:列出满足指定过滤器的所有entry
    • Abandon:放弃,作用在一个挂起请求上,标识客户端对该请求不在感兴趣
  • 修改操作

    • 添加entry
    • 移除entry
    • 修改entry
    • 修改DN,修改某个entry的相对名称。如果该entry有子节点,则子节点的这部分名字也会被修改
  • 其它可能的输出

    • 错误
    • 转移(referral):可能当前服务无法处理这个请求,它会返回一个新的端点,类似HTTP的重定向

分布式Directory

image-20241125182800121

  • DSA:Directory System Agent。用来连接服务和DUA。他可以缓存Directory数据,可以用本地数据直接响应,也可以单纯做一个转发
  • LDAP Server:是Directory Service的一部分。他可以直接使用本地数据,也可以转发到其它LDAP Server获取数据

其它

协议还规定了目录服务的访问安全、备份等操作,这里暂时忽略。之后值得探究的倒是其安全协议 X.509

LDAP’s Safety Specifications

  • 使用TLS安全传输
  • 提供简单的匿名和账号密码认证
  • 支持SASL构建鉴权和安全服务层

The Practice of LDAP

LDAP目前最常用的用途是做单点登录,在这个场景中 LDAP 一般存储关于用户、用户认证信息、组、用户成员等等,充当一个用户信息的中心仓库,通常使用为用户认证和授权

image-20241125202043207

使用命令行构造LDAP树结构

!!!!

由于该方式过于底层,偏离学习初衷,故命令行方式构造数据树的实验笔者仅做记录

不同版本的 Linux 安装方法差别非常大!

此次实验记录的是快速搭建一个 LDAP 学习和测试环境,没有进行其他高级配置。建议使用虚拟机和全新安装的系统进行操作。

LDIF

LDAP Data Interchange Format,是一个格式化文件

LDAP 数据交换格式文件,它以文本形式存储,用于在服务器之间交换数据。

添加数据以及修改数据都需要通过 LDIF 文件来进行,可以跟关系型数据库的 SQL 文件做类比。

LDIF 文件的格式一般如下:

1
2
3
4
dn: <识别名>
<属性 1>: <值 1>
<属性 2>: <值 2>
...

LDAP 命令行构造数据

本次涉及软件:

  • 操作系统:CentOS 7.4.1708 最小安装(已关闭 SELinux 和防火墙)
  • 应用软件:openldap 2.4.44 、phpldapadmin 1.2.3
  1. CentOS 7 上安装并使用 OpenLDAP

yum install -y openldap-clients openldap-servers

  1. 创建数据库配置文件

cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG && chown ldap:ldap /var/lib/ldap/DB_CONFIG

  1. 启动 OpenLDAP 服务

开启:systemctl start slapd

开机自启:systemctl enable slapd


如果没有出现错误,那么 OpenLDAP 的安装就算完成了,不过当前的目录数据库还是空的,下面我们要对数据库进行初始化。

  1. 设置 OpenLDAP 管理员密码

slappasswd

输入密码,确认后会生成{SSHA}xxxxx这样的东西,记作passwdA值

  1. 生成 LDIF 文件

cat << EOF > chrootpw.ldif

然后将下方文本修改对应内容后,去掉所有注释,然后在命令行中粘贴回车

1
2
3
4
5
dn: olcDatabase={0}config,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}xxxxx  #{SSHA}xxxxx 修改为上一步记下来的 passwdA值
EOF 
  1. 执行 LDIF 文件

ldapadd -Y EXTERNAL -H ldapi:/// -f chrootpw.ldif

  1. 导入预设的模式

find /etc/openldap/schema/ -name "*.ldif" -exec ldapadd -Y EXTERNAL -H ldapi:/// -D "cn=config" -f {} \;


接下来要使用命令行来新建根节点

  1. 生成根节点管理员密码

上一次的操作,生成后保存为 passwdBBBB值

根节点管理员密码与 OpenLDAP 管理员密码不是同一回事!一个 LDAP 数据库可以包含多个目录树。

  1. 生成 LDIF 文件

首先请想好一个域名。以 zenandidi.com 为例:

下面根节点的 DN 应该这样写:dc=zenandidi,dc=com

cat << EOF > chdomain.ldif

然后将下方文本修改对应内容后,去掉所有注释,然后在命令行中粘贴回车

 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
dn: olcDatabase={1}monitor,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth"
  read by dn.base="cn=Manager,dc=xxx,dc=xxx" read by * none #修改 dc=xxx,dc=xxx 为自己的域名

dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=xxx,dc=xxx    #修改 dc=xxx,dc=xxx 为自己的域名

dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcRootDN
olcRootDN: cn=Manager,dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名

dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}xxxxx  #{SSHA}xxxxx 修改为上一步记下来的值

dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to attrs=userPassword,shadowLastChange by
  dn="cn=Manager,dc=xxx,dc=xxx" write by anonymous auth by self write by * none #修改 dc=xxx,dc=xxx 为自己的域名
olcAccess: {1}to dn.base="" by * read
olcAccess: {2}to * by dn="cn=Manager,dc=xxx,dc=xxx" write by * read #修改 dc=xxx,dc=xxx 为自己的域名
EOF
  1. 执行 LDIF 文件

ldapmodify -Y EXTERNAL -H ldapi:/// -f chdomain.ldif


接下来,开始进行添加用户、组节点

  1. 生成 LDIF 文件

cat << EOF > basedomain.ldif

然后将下方文本修改对应内容后,去掉所有注释,然后在命令行中粘贴回车

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
dn: dc=xxx,dc=xxx   #修改 dc=xxx,dc=xxx 为自己的域名
objectClass: top
objectClass: dcObject
objectclass: organization
o: root_ldap
dc: xxx #修改 xxx 为自己域名第一个点左边的内容

dn: cn=Manager,dc=xxx,dc=xxx    #修改 dc=xxx,dc=xxx 为自己的域名
objectClass: organizationalRole
cn: Manager
description: Directory Manager

dn: ou=People,dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
objectClass: organizationalUnit
ou: People

dn: ou=Group,dc=xxx,dc=xxx  #修改 dc=xxx,dc=xxx 为自己的域名
objectClass: organizationalUnit
ou: Group
EOF
  1. 执行 LDIF 文件

请先修改下面命令的dc=xxx,dc=xxx为自己的域名然后再执行

ldapadd -x -D cn=Manager,dc=xxx,dc=xxx -W -f basedomain.ldif

然后需要输入设定的根节点管理员密码 (生成passwdBBBB值时输入的密码

  1. 清理 LDIF 文件

rm basedomain.ldif chdomain.ldif chrootpw.ldif

至此,一个 LDAP 目录树就构建完毕了。

LDAP UI操作

但是 LDAP 的命令行管理工具非常难用,尤其是对于新手来说。

所以,下面我们来安装phpLDAPadmin这个图形化管理工具,方便新手学习

如果上面的命令行操作有实际操作的话,那么可以跳转到这里

UI创建LDAP环境与数据

进入phpLDAPAdmin

该操作使用docker-compose一键部署openLDAPphpldapadminself-service-password

  1. 准备open-ldapdocker-compose文件

如下直接给出笔者的实验docker-compose

 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
version: '2'
services:
  openldap:
    image: osixia/openldap:1.5.0  ### 如果有私有仓库可以从自己的私有仓库拉取镜像
    container_name: openldap
    restart: always
    environment:
      LDAP_LOG_LEVEL: "256"
      LDAP_ORGANISATION: "ndzzmc"   ### 您的组织名称
      LDAP_DOMAIN: "gsymgsym.com"    ### 公司域名
      LDAP_BASE_DN: "dc=gsymgsym,dc=com"   ### 根据域名组成
      LDAP_ADMIN_PASSWORD: "Aa123+Bb"   ### 密码自己来设置
      LDAP_CONFIG_PASSWORD: "Aa123+BbCc"
      LDAP_READONLY_USER: "false"
      #LDAP_READONLY_USER_USERNAME: "readonly"
      #LDAP_READONLY_USER_PASSWORD: "readonly"
      LDAP_RFC2307BIS_SCHEMA: "false"
      LDAP_BACKEND: "mdb"
      #LDAP_TLS: "true"
      #LDAP_TLS_CRT_FILENAME: "zaq.test.pem"
      #LDAP_TLS_KEY_FILENAME: "zaq.test.key"
      #LDAP_TLS_DH_PARAM_FILENAME: "dhparam.pem"
      #LDAP_TLS_CA_CRT_FILENAME: "ca.crt"
      #LDAP_TLS_ENFORCE: "false"
      #LDAP_TLS_CIPHER_SUITE: "SECURE256:-VERS-SSL3.0"
      # LDAP_TLS_VERIFY_CLIENT: "demand"
      LDAP_REPLICATION: "false"
      #LDAP_REPLICATION_CONFIG_SYNCPROV: 'binddn="cn=admin,cn=config" bindmethod=simple credentials="$$LDAP_CONFIG_PASSWORD" searchbase="cn=config" type=refreshAndPersist retry="60 +" timeout=1 starttls=critical'
      #LDAP_REPLICATION_DB_SYNCPROV: 'binddn="cn=admin,$$LDAP_BASE_DN" bindmethod=simple credentials="$$LDAP_ADMIN_PASSWORD" searchbase="$$LDAP_BASE_DN" type=refreshAndPersist interval=00:00:00:10 retry="60 +" timeout=1 starttls=critical'
      #LDAP_REPLICATION_HOSTS: "#PYTHON2BASH:['ldap://ldap.example.org','ldap://ldap2.example.org']"
      KEEP_EXISTING_CONFIG: "false"
      LDAP_REMOVE_CONFIG_AFTER_SETUP: "true"
      #LDAP_SSL_HELPER_PREFIX: "ldap"
    tty: true
    stdin_open: true
    volumes:
      - /opt/openldap/ldap:/var/lib/ldap
      - /opt/openldap/slapd.d:/etc/ldap/slapd.d
      - /opt/openldap/certs:/container/service/lapd/assets/certs
    ports:
      - "389:389"
      - "636:636"
    # For replication to work correctly, domainname and hostname must be
    # set correctly so that "hostname"."domainname" equates to the
    # fully-qualified domain name for the host.
    domainname: "gsymgsym.com"
    hostname: "ldap-server"
  phpldapadmin:
    image: osixia/phpldapadmin:latest
    container_name: phpldapadmin
    restart: always
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: "openldap"   ### 如果部署后登录不进去有可能是这里出了问题,直接换为部署openldap服务的可达IP试试
      PHPLDAPADMIN_HTTPS: "false"
    ports:
      - "50081:80"
    depends_on:
      - openldap
  self-service-password:
    container_name: self-service-password
    image: tiredofit/self-service-password:latest
    restart: always
    ports:
      - "50080:80"
    environment:
      - LDAP_SERVER=ldap://openldap:389
      - LDAP_BINDDN=cn=admin,dc=gsymgsym,dc=com
      - LDAP_BINDPASS=XXXX
      - LDAP_BASE_SEARCH=dc=gsymgsym,dc=com
      - MAIL_FROM=it@open.com
      - MAIL_FROM_NAME=账号自助服务平台
      - SMTP_DEBUG=0
      - SMTP_HOST=smtp.qiye.aliyun.com
      - SMTP_USER=it@open.com
      - SMTP_PASS=jYda52VZ8Ftw1111
      - SMTP_PORT=465
      - SMTP_SECURE_TYPE=ssl
      - SMTP_AUTH_ON=true
      - NOTIFY_ON_CHANGE=true
    volumes:
      - /etc/localtime:/etc/localtime
      - /opt/openldap/self-service-password/htdocs:/www/ssp
      - /opt/openldap/self-service-password/logs:/www/logs
    deploy:
      resources:
        limits:
           memory: 2G
        reservations:
           memory: 512M
  1. Docker 部署

docker-compose up -d

image-20241125212300170

然后点击Login登录

image-20241125212348944

Login DN 根据docker-compose就是cn=admin,dc=gsymgsym,dc=com

Password 就是LDAP_ADMIN_PASSWORD的设置项Aa123+Bb

下图的报错是测试错误密码

image-20241125212626161

正确登录后,如下图

image-20241125212715821

创建数据

这里先在dc下面创建一个ou=group和一个ou=user。暂时先不按部分区分

如果想再更细节区分的话,则就先创建一个ou,再在该ou下创建一个ou=group即可。

创建ou

0wh7x-x0ylm-10hz

重复此操作,创建ou=user,结果如下图

image-20241126131634209

创建 Posix Group

接下来创建Posix Group在group

wyqid-99xbj

创建 User Account

ou=user下创建用户

vvfob-uhvgf

为用户填写其他属性

ji71q-5y1j4

已有LDAP环境和数据后的UI登录

  1. 安装phpLDAPadmin

yum -y install phpldapadmin

  1. 修改phpLDAPadmin的配置文件

vi /etc/phpldapadmin/config.php

请按实际情况以及注释提示修改以下内容,完成去除 # 号和后面的注释,在命令行窗口中按下 G(大写),然后按下 O(大写),将上面修改的内容直接粘贴到命令行窗口中,再按下 ESC ,最后输入:wq按回车。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$servers = new Datastore();
$servers->newServer('ldap_pla');
$servers->setValue('server','name','My LDAP Server');
$servers->setValue('server','host','127.0.0.1');
$servers->setValue('server','port',389);
$servers->setValue('server','base',array('dc=xxx,dc=xxx')); #修改 dc=xxx,dc=xxx 为自己的域名
$servers->setValue('login','auth_type','session');
$servers->setValue('login','bind_id','cn=Manager,dc=xxx,dc=xxx');   #修改 dc=xxx,dc=xxx 为自己的域名
$servers->setValue('login','bind_pass','<密码>'); #填入 5.7.1 设定的根节点管理员密码
$servers->setValue('server','tls',false); 
  1. 启动httpd服务

启动:systemctl start httpd

开机启动:systemctl enable httpd

  1. 登录phpLDAPadmin

打开浏览器,访问 http://CentOS7IP/phpldapadmin ,然后按图片中的提示登录即可。

v2-fb61d749b627993fd2a2eb4b8c793d9f_1440w

配置完效果如下

v2-e2728ad753765e90636bb53bc7457ee7_1440w

LDAP与第三方集成

第三方平台的集成基本上按照操作手册来做,故不做实验

仅做记录

LDAP和JRIA的集成

  1. 登录进JRIA的控制台(需要是管理员身份)
  2. 点击:配置—>用户管理—>用户目录—>添加目录—>LDAP

fbc6918f37c7924197dfc69cbfb25755

31b8466529fcc1645027fb0a61cae7cc

补充解释

特别解释几个属性的含义:

LDAP模式

  • “基础DN” 填写LDAP的根节点,类似dc=zaq,dc=test; “附加用户DN” 填写限制用户搜索范围的值,类似ou=people,不填的话从基础DN开始搜索; “附加组DN” 填写限制用户组搜索范围的值,和上一项类似;

LDAP权限

  • 只读:JIRA只能从LDAP中读取用户以及用户组信息,所有对用户及用户组的修改不能通过JIRA进行。 本地只读:相比只读来说,可以在JIRA中添加组,并且会将LDAP同步过来的用户加入到该组中。 读写:不但可以读取LDAP上的用户及组信息,还可以通过JIRA修改这些信息,这些信息会自动同步到LDAP。

8e84a4b415229700c13da3b5bd70141e

LDAP和Confluence的集成

找到gitlab的配置文件:/etc/gitlab/gitlab.rb,然后修改下面的内容

 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
### LDAP Settings
###! Docs: https://docs.gitlab.com/omnibus/settings/ldap.html
###! **Be careful not to break the indentation in the ldap_servers block. It is
###!   in yaml format and the spaces must be retained. Using tabs will not work.**

 gitlab_rails['ldap_enabled'] = true
# gitlab_rails['prevent_ldap_sign_in'] = false

###! **remember to close this block with 'EOS' below**
 gitlab_rails['ldap_servers'] = YAML.load <<-'EOS'
   main: # 'main' is the GitLab 'provider ID' of this LDAP server
     label: 'LDAP'
     host: 'xx.xx.xx.xx'  ### LDAP服务地址
     port: 389
     uid: 'uid'   ### 指定登录gitlab使用LDAP的哪个字段作为账号
     bind_dn: 'cn=admin,dc=zaq,dc=test'  ### 这里用自己的管理员账号(需要一个有read权限的账号验证通过后搜索用户输入的用户名是否存在)
     password: 'XXX'
     encryption: 'plain' # "start_tls" or "simple_tls" or "plain"
#     verify_certificates: true
#     smartcard_auth: false
     active_directory: false       ### 如果是 Active Directory LDAP server 则设为true
     allow_username_or_email_login: true  ### 是否允许email登录
#     lowercase_usernames: false    ### 是否将用户名转为小写
     block_auto_created_users: false   ### 是否自动创建用户。如果设置为true则自动注册的账户是被锁定的,需要管理员账户手动的为这些账户解锁,因此此处将其设置为false。当设置为false的时候,就需要保证,对于第三方登录的用户完全可控。
     base: 'ou=people,dc=zaq,dc=com'   ### 从哪个位置搜索用户,这里填自己的
     user_filter: ''     ### 表示以某种过滤条件筛选用户,比如我们只希望组为gitlab的用户来访问GitLab,则这里可以设置为:memberOf=ou=gitlab,ou=people,dc=zaq,dc=com
#	 attributes:	# LDAP 中用户的属性
#	   username: ['uid', 'userid', 'sAMAccountName']
#      email: ['mail', 'email', 'userPrincipalName']
#      name: 'cn'
#      first_name: 'givenName'
#      last_name:  'sn'
     ## EE only
     group_base: ''
     admin_group: ''
     sync_ssh_keys: false
 EOS

修改完后重启GitLab!

最后最好再取消GitLab的注册功能,新用户只能通过 LDAP 认证的方式进行登陆。

拉取代码时要用LDAP创建的账号密码拉取。

LDAP和Nexus的集成

  1. 登录管理员账号

7162286dc6f76fceba126353661f3219

ad70b545603a246422f77f585dfacefb

458a3b3dcbd24cc1ffbb446f916b9c7d

a71cbfcb19ecccfbd71a02968b099519

LDAP和Harbor的集成

特别注意

在没有添加任何用户之前,你可以修改认证模式(Database模式或者LDAP模式), 但当Harbor系统中已经有至少一个用户之后(除了admin用户外),将不能够修改认证模式。

新版本

新版本的可以直接在页面进行修改,如下:

登录管理员账户,选中配置管理

fa5b6831e2a940711ae277ab2917653e

旧版本

老版本则需要修改harbor.cfg文件,如下

 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
40
41
42
43
##By default the auth mode is db_auth, i.e. the credentials are stored in a local database.
#Set it to ldap_auth if you want to verify a user's credentials against an LDAP server.
auth_mode = ldap_auth
 
#The url for an ldap endpoint.
ldap_url = xx.xx.xx.xx
 
#A user's DN who has the permission to search the LDAP/AD server.
#If your LDAP/AD server does not support anonymous search, you should configure this DN and ldap_search_pwd.
ldap_searchdn = cn=admin,dc=zaq,dc=test
 
#the password of the ldap_searchdn
ldap_search_pwd = xxx
 
#The base DN from which to look up a user in LDAP/AD
ldap_basedn = dc=zaq,dc=com
 
#Search filter for LDAP/AD, make sure the syntax of the filter is correct.
#ldap_filter = (objectClass=person)
 
# The attribute used in a search to match a user, it could be uid, cn, email, sAMAccountName or other attributes depending on your LDAP/AD
ldap_uid = uid
 
#the scope to search for users, 0-LDAP_SCOPE_BASE, 1-LDAP_SCOPE_ONELEVEL, 2-LDAP_SCOPE_SUBTREE
ldap_scope = 2
 
#Timeout (in seconds)  when connecting to an LDAP Server. The default value (and most reasonable) is 5 seconds.
ldap_timeout = 5
 
#Verify certificate from LDAP server
ldap_verify_cert = true
 
#The base dn from which to lookup a group in LDAP/AD
ldap_group_basedn = ou=IT,dc=shileizcc,dc=com
 
#filter to search LDAP/AD group
ldap_group_filter = objectclass=group
 
#The attribute used to name a LDAP/AD group, it could be cn, name
ldap_group_gid = cn
 
#The scope to search for ldap groups. 0-LDAP_SCOPE_BASE, 1-LDAP_SCOPE_ONELEVEL, 2-LDAP_SCOPE_SUBTREE
ldap_group_scope = 2

Capture LDAP Data

这里尝试抓取LDAP通讯流量

环境:Java1.8,Docker,Wireshark

使用上方的Dockerfile创建好容器后,使用如下java代码进行连接

一个 Java Bean

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.study.ldapp;

public class LdapUser {

    public String cn;
    public String sn;
    public String uid;
    public String userPassword;
    public String displayName;
    public String mail;
    public String description;
    public String uidNumber;
    public String gidNumber;
    /**忽略get\set方法**/
}

一个模拟客户端

注意!!!

如下代码笔者只运行了链接认证部分

  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
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
package com.study.ldapp;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

public class LdapStore {
    public static void main(String[] args) throws NamingException {
        String url = "ldap://localhost:389/";
        String basedn = "dc=gsymgsym,dc=com";  // basedn
        String root = "cn=admin,dc=gsymgsym,dc=com";  // 用户
        String pwd ="Aa123+Bb";  // pwd

        LdapContext ctx=ldapConnect(url,root,pwd);//集团 ldap认证
        List<LdapUser> jtlm=readLdap(ctx,basedn);//获取集团ldap中用户信息

        url = "ldap://localhost:389/";
        basedn = "dc=gsymgsym,dc=com";  // basedn
        root = "cn=admin,dc=gsymgsym,dc=com";  // 用户
        pwd ="Aa123+Bb";  // pwd
        ctx=ldapConnect(url,root,pwd);//cdh ldap认证
        List<LdapUser> cdhlm=readLdap(ctx,basedn);//获取cdh ldap中用户信息
        //通过时间戳初始化一个uid
        long init_uid= getNowTimeStamp();
        //循环集团ldap用户
        for(int i=0;i<jtlm.size();i++){
            LdapUser jtu=jtlm.get(i);
            init_uid=init_uid+i;//生成一个唯一的ID
            jtu.setGidNumber(init_uid+"");//设置gid
            jtu.setUidNumber(init_uid+"");//设置uid
            // if(!"testwx".equals(jtu.getUid()))
            //	 continue;
            boolean not_exist =true;
            //循环匹配cdh ldap用户
            for(int j=0;j<cdhlm.size();j++){
                LdapUser cdhu=cdhlm.get(j);
                //双方用户存在并且密码相同
                if( jtu.getUid().equals(cdhu.getUid()) && jtu.getUserPassword().equals(cdhu.getUserPassword()) ){
                    not_exist=false;
                    break;
                }else if(jtu.getUid().equals(cdhu.getUid()) && !jtu.getUserPassword().equals(cdhu.getUserPassword())){//双方用户存在,但是密码不同
                    modifyInformation(jtu,ctx);//修改cdh ldap的密码,改为集团ldap的用户密码
                    not_exist=false;
                    break;
                }
            }
            //cdh ldap中没有此用户就添加
            if(not_exist){
                boolean b = addGoups(jtu,ctx);
                if(b)
                    addUser(jtu,ctx);
            }
        }
        if(ctx!=null)
            ctx.close();
    }
    /**
     * 添加组
     * @param lu
     * @param ctx
     * @return
     */
    public static boolean addGoups(LdapUser lu,LdapContext ctx) {
        BasicAttributes attrsbu = new BasicAttributes();
        BasicAttribute objclassSet = new BasicAttribute("objectClass");
        objclassSet.add("posixGroup");
        objclassSet.add("top");
        attrsbu.put(objclassSet);
        attrsbu.put("cn", lu.getCn());//显示账号
        attrsbu.put("userPassword", "{crypt}x");//显示

        attrsbu.put("gidNumber",lu.getGidNumber());/*显示组id */
        attrsbu.put("memberUid", lu.getCn());//显示账号
        try {
            String cn="cn="+lu.getCn()+",ou=Group,dc=tcjf,dc=com";
            System.out.println(cn);
            ctx.createSubcontext(cn, attrsbu);
            System.out.println("添加用户group成功");
            return true;
        } catch (Exception e) {
            System.out.println("添加用户group失败");
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 添加用户
     * @param lu
     * @param ctx
     * @return
     */
    public static boolean addUser(LdapUser lu,LdapContext ctx) {
        BasicAttributes attrsbu = new BasicAttributes();
        BasicAttribute objclassSet = new BasicAttribute("objectClass");
        // objclassSet.add("account");
        objclassSet.add("posixAccount");
        objclassSet.add("inetOrgPerson");
        objclassSet.add("top");
        objclassSet.add("shadowAccount");
        attrsbu.put(objclassSet);
        attrsbu.put("uid",  lu.getUid());//显示账号
        attrsbu.put("sn", lu.getSn());//显示姓名
        attrsbu.put("cn", lu.getCn());//显示账号
        attrsbu.put("gecos", lu.getCn());//显示账号
        attrsbu.put("userPassword", lu.getUserPassword());//显示密码
        attrsbu.put("displayName", lu.getDisplayName());//显示描述
        attrsbu.put("mail", lu.getMail());//显示邮箱
        attrsbu.put("homeDirectory", "/home/" + lu.getCn());//显示home地址
        attrsbu.put("loginShell", "/bin/bash");//显示shell方式
        attrsbu.put("uidNumber", lu.getUidNumber());/*显示id */
        attrsbu.put("gidNumber", lu.getGidNumber());/*显示组id */

        try {
            String dn="uid="+lu.getCn()+",ou=People,dc=tcjf,dc=com";
            System.out.println(dn);
            ctx.createSubcontext(dn, attrsbu);
            System.out.println("添加用户成功");
            return true;
        } catch (Exception e) {
            System.out.println("添加用户失败");
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 修改属性
     * @param lu
     * @param ctx
     * @return
     */
    public static boolean modifyInformation(LdapUser lu,LdapContext ctx) {
        try {
            ModificationItem[] mods = new ModificationItem[1];
            String dn="uid="+lu.getCn()+",ou=People,dc=tcjf,dc=com";
            /*添加属性*/
            //  Attribute attr0 = new BasicAttribute("description", "测试");
            //  mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE,attr0);

            /*修改属性*/
            Attribute attr0 = new BasicAttribute("userPassword", lu.getUserPassword());
            mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr0);

            /*删除属性*/
            //  Attribute attr0 = new BasicAttribute("description", "测试");
            //  mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr0);
            ctx.modifyAttributes(dn, mods);
            System.out.println("修改成功");
            return true;
        } catch (NamingException ne) {
            System.out.println("修改失败");
            ne.printStackTrace();
            return false;
        }

    }

    /**
     * 删除
     * @param dn
     * @param ctx
     * @return
     */
    public  static boolean delete(String dn,LdapContext ctx) {
        try {
            ctx.destroySubcontext(dn);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 获取ldap认证
     * @param url
     * @param root
     * @param pwd
     * @return
     */
    public static LdapContext ldapConnect(String url,String root,String pwd){
        String factory = "com.sun.jndi.ldap.LdapCtxFactory";
        String simple="simple";
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY,factory);
        env.put(Context.PROVIDER_URL, url);
        env.put(Context.SECURITY_AUTHENTICATION, simple);
        env.put(Context.SECURITY_PRINCIPAL, root);
        env.put(Context.SECURITY_CREDENTIALS, pwd);
        LdapContext ctx = null;
        Control[] connCtls = null;
        try {
            ctx = new InitialLdapContext(env, connCtls);
            System.out.println( "认证成功:"+url);
        }catch (javax.naming.AuthenticationException e) {
            System.out.println("认证失败:");
            e.printStackTrace();
        } catch (Exception e) {
            System.out.println("认证出错:");
            e.printStackTrace();
        }
        return ctx;
    }

    /**
     * 获取用户信息
     * @param ctx
     * @param basedn
     * @return
     */
    public static List<LdapUser> readLdap(LdapContext ctx,String basedn){

        List<LdapUser> lm=new ArrayList<LdapUser>();
        try {
            if(ctx!=null){
                //过滤条件
                String filter = "(&(objectClass=*)(uid=*))";
                String[] attrPersonArray = { "uid", "userPassword", "displayName", "cn", "sn", "mail", "description" };
                SearchControls searchControls = new SearchControls();//搜索控件
                searchControls.setSearchScope(2);//搜索范围
                searchControls.setReturningAttributes(attrPersonArray);
                //1.要搜索的上下文或对象的名称;2.过滤条件,可为null,默认搜索所有信息;3.搜索控件,可为null,使用默认的搜索控件
                NamingEnumeration<SearchResult> answer = ctx.search(basedn, filter.toString(),searchControls);
                while (answer.hasMore()) {
                    SearchResult result = (SearchResult) answer.next();
                    NamingEnumeration<? extends Attribute> attrs = result.getAttributes().getAll();
                    LdapUser lu=new LdapUser();
                    while (attrs.hasMore()) {
                        Attribute attr = (Attribute) attrs.next();
                        if("userPassword".equals(attr.getID())){
                            Object value = attr.get();
                            lu.setUserPassword(new String((byte [])value));
                        }else if("uid".equals(attr.getID())){
                            lu.setUid(attr.get().toString());
                        }else if("displayName".equals(attr.getID())){
                            lu.setDisplayName(attr.get().toString());
                        }else if("cn".equals(attr.getID())){
                            lu.setCn(attr.get().toString());
                        }else if("sn".equals(attr.getID())){
                            lu.setSn(attr.get().toString());
                        }else if("mail".equals(attr.getID())){
                            lu.setMail(attr.get().toString());
                        }else if("description".equals(attr.getID())){
                            lu.setDescription(attr.get().toString());
                        }
                    }
                    if(lu.getUid()!=null)
                        lm.add(lu);

                }
            }
        }catch (Exception e) {
            System.out.println("获取用户信息异常:");
            e.printStackTrace();
        }

        return lm;
    }

    /**
     * string 转 数值
     * @param m
     * @return
     */
    public static String strToint(String m){
        if(m==null || "".equals(m))
            return "-1";
        char [] a=m.toCharArray();
        StringBuffer sbu = new StringBuffer();
        for(char c:a)
            sbu.append((int)c);
        System.out.println(sbu.toString());
        return sbu.toString();
    }
    /**
     * 获取时间戳
     * @return
     */
    public static long getNowTimeStamp() {
        long time = System.currentTimeMillis();
        time =  time / 1000;
        return time;
    }
}

使用Wireshark监听本地回环流量,指定tcp.port=389

image-20241126212525363

找到协议为LDAP的第一个流量,右键该项->跟踪流->TCP Stream,得到如下

image-20241126212706039

接下来就可以根据兴趣去研究LDAP协议了

如下是我流量包的base64,感兴趣的可以拿来解码后保存为.pcap文件研究

解压方法:echo "1M....+SiYAAA="|base64 -d > LDAP.pcap

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1MOyoQIABAAAAAAAAAAAAAAABAAAAAAAa8RFZ/PgCAA4AAAAOAAAAAIAAABFAAA0TwRAAIAGAAB/
AAABfwAAAQi1AYXkU/U2AAAAAIAC//+TJQAAAgT/1wEDAwgBAQQCa8RFZyfhCAA4AAAAOAAAAAIA
AABFAAA0TwdAAIAGAAB/AAABfwAAAQGFCLUCKHpG5FP1N4AS//8WpgAAAgT/1wEDAwgBAQQCa8RF
Z0fhCAAsAAAALAAAAAIAAABFAAAoTwhAAIAGAAB/AAABfwAAAQi1AYXkU/U3Aih6R1AQJ/kppAAA
a8RFZyvjCABdAAAAXQAAAAIAAABFAABZTwlAAIAGAAB/AAABfwAAAQi1AYXkU/U3Aih6R1AYJ/l9
MgAAMC8CAQFgKgIBAwQbY249YWRtaW4sZGM9Z3N5bWdzeW0sZGM9Y29tgAhBYTEyMytCYmvERWdG
4wgALAAAACwAAAACAAAARQAAKE8KQACABgAAfwAAAX8AAAEBhQi1Aih6R+RT9WhQECf5KXMAAGvE
RWex5wgAOgAAADoAAAACAAAARQAANk8LQACABgAAfwAAAX8AAAEBhQi1Aih6R+RT9WhQGCf55eQA
ADAMAgEBYQcKAQAEAAQAa8RFZ8rnCAAsAAAALAAAAAIAAABFAAAoTwxAAIAGAAB/AAABfwAAAQi1
AYXkU/VoAih6VVAQJ/kpZQAAa8RFZ0PqCADFAAAAxQAAAAIAAABFAADBTw1AAIAGAAB/AAABfwAA
AQi1AYXkU/VoAih6VVAYJ/kf0AAAMIGWAgECY3QEEmRjPWdzeW1nc3ltLGRjPWNvbQoBAgoBAwIB
AAIBAAEBAKAShwtvYmplY3RDbGFzc4cDdWlkMDsEA3VpZAQMdXNlclBhc3N3b3JkBAtkaXNwbGF5
TmFtZQQCY24EAnNuBARtYWlsBAtkZXNjcmlwdGlvbqAbMBkEFzIuMTYuODQwLjEuMTEzNzMwLjMu
NC4ya8RFZ1/qCAAsAAAALAAAAAIAAABFAAAoTw5AAIAGAAB/AAABfwAAAQGFCLUCKHpV5FP2AVAQ
J/gozQAAa8RFZ7ftCAA6AAAAOgAAAAIAAABFAAA2Tw9AAIAGAAB/AAABfwAAAQGFCLUCKHpV5FP2
AVAYJ/jkOgAAMAwCAQJlBwoBAAQABABrxEVn2O0IACwAAAAsAAAAAgAAAEUAAChPEEAAgAYAAH8A
AAF/AAABCLUBheRT9gECKHpjUBAn+Si+AABrxEVny+4IAFAAAABQAAAAAgAAAEUAAExPEUAAgAYA
AH8AAAF/AAABCLUBheRT9gECKHpjUBgn+YcEAAAwIgIBA0IAoBswGQQXMi4xNi44NDAuMS4xMTM3
MzAuMy40LjJrxEVn4e4IACwAAAAsAAAAAgAAAEUAAChPEkAAgAYAAH8AAAF/AAABAYUItQIoemPk
U/YlUBAn+CibAABrxEVnQ+8IACwAAAAsAAAAAgAAAEUAAChPE0AAgAYAAH8AAAF/AAABCLUBheRT
9iUCKHpjUBEn+SiZAABrxEVnU+8IACwAAAAsAAAAAgAAAEUAAChPFEAAgAYAAH8AAAF/AAABAYUI
tQIoemPkU/YmUBAn+CiaAABrxEVndvIIACwAAAAsAAAAAgAAAEUAAChPFUAAgAYAAH8AAAF/AAAB
AYUItQIoemPkU/YmUBEn+CiZAABrxEVnnvIIACwAAAAsAAAAAgAAAEUAAChPFkAAgAYAAH8AAAF/
AAABCLUBheRT9iYCKHpkUBAn+SiYAAA=

Ref

https://blog.csdn.net/zou8944/article/details/122287398

https://blog.csdn.net/pushiqiang/article/details/119982651

https://www.cnblogs.com/siyunianhua/p/18309700

https://zhuanlan.zhihu.com/p/32732045

https://blog.csdn.net/ysf15609260848/article/details/126002321

https://blog.csdn.net/ysf15609260848/article/details/126002452

Licensed under CC BY-NC-SA 4.0
Dan❤Anan
Built with Hugo
主题 StackJimmy 设计