Keystone服务详解
# Keystone服务详解
首先来介绍OpenStack里的第一个核心组件,在企业内部任何一个核心系统都离不开认证和授权,而Keystone就是OpenStack里用来提供认证和授权服务的组件,主要负责OpenStack中用户的身份认证、令牌管理、提供OpenStack内部资源的服务目录和访问端点。提供基于角色的权限控制方式,也就是我们常说的RBAC权限控制。
在之前搭建实验环境的时候,在安装配置各个组件之前我们都需要先创建每个组件的用户,把它绑定到管理员角色,然后创建服务自身以及服务的访问端点(public、internal、admin共3个)。这个过程就是SOA架构中的服务发现和注册,各个组件的用户、角色、端点等资源都是由Keystone来进行管理的,下面我们来深入研究Keystone的基本架构,以及它的是怎样完成这样的管理工作的。
# Keystone体系结构
# Keystone体系核心概念
先来看一下Keystone里的核心概念,有很多,但是需要记住的不多:
- 地域(region)和可用区(az)
- 域(domain)和项目(Project)
- 用户(User)用户组(Group)和角色(Role)
- 策略(Policy)
- 服务(Service),例如nova、cinder、glance等,和端点(Endpoint)
- 令牌(Token)和凭据(Credentials)
# 地域(region)
在keystone中有两个域的概念,分布是地域(region)和域(domain),地域(region)更倾向于一个地理的概念,通常指的是某个地方的数据中心,是实际存在的物理介质,例如云厂商在北京、上海、广州等地都有不同的数据中心。不管是公有云还是私有云,不同的Region之间会共享同一套web面板和认证体系,但是目前OpenStack对于多Region的支持还并不是很友好,并不支持直接部署跨多地域的集群,在部署的OpenStack集群里默认只有一个RegionOne,如果我们想用OpenStack来做多地域机房管理,通常都需要进行一定程度的二次改造。
然后在region的下层还有可用区(Aavailability zone)的划分,例如广州数据中心的广州一区、广州二区。可用区通常是一组独立的机房,可用区之间的电力、网络、散热等基础设施是互相独立的,某个可用区的故障不会影响另外一个可用区。OpenStack支持多个可用区的管理,在前面部署的测试环境中,默认只有一个nova可用区。
# 域(domain)和项目(Project)
上面说到地域(Region)是实际物理资源的划分单位,而域(domain)和项目则是虚拟资源的划分,例如会定义这个域内或者这个项目内能够使用的资源数量,例如可以开启的虚拟机数量、CPU数量、内存大小、硬盘空间大小等。进行虚拟资源的配额管理。
通常一个域下可以有多个项目,项目属于域的子级别资源,而一个项目下可以配置项目管理员、项目成员、虚拟机数量、CPU数量、内存数量、硬盘大小等基本资源。
正常情况下OpenStack是支持多域资源管理的,但是在之前的部署过程中,从各个核心组件的配置文件中我们也看到了,核心组件的默认域全部都是Default,也就是说默认只有一个Default域。
地域、域和项目都是可以通过命令行手动创建的,但是OpenStack的Horizon组件上并没有直接提供地域和域的管理页面(支持切换),这也是为什么上面说OpenStack目前对这两者的支持不是那么友好,实际部署跨机房项目的时候需要二次改造的原因。
对应到我们公有云上的产品,单个域和项目更像是公有云上的账户,下面的角色、用户、用户组,在公有云上都能找 到对应的功能项。
# 角色(Role)、用户(User)和用户组(User Group)
OpenStack使用的是RBAC的权限控制机制,默认会将初始的权限直接关联到角色,然后把用户和角色关联到一起,用户就具备了对应的权限。默认角色只有3个admin、user和member,即项目管理员、项目用户、项目成员。
通过Keystone访问OpenStack服务的个人、系统或者服务,都可以被认为是keystone的用户,例如我们登录的时候使用的账号密码可以表示一个用户,Nova在创建虚拟机的时候向keystone请求Neutron服务的时候,也是一个用户。
用户组就是用户分组,引入用户组的概念,实际上还是方便权限管理,例如给一个组分配某个权限时,这个组的用户 同时具备这个权限。
# 服务(Service)和端点(Endpoint)
服务就是各个组件能够提供的服务,例如Cinder组件提供的存储服务、neutron组件提供的网络服务,glance组件提供的镜像服务、keystone组件提供的认证服务,nova组件提供的虚拟机管理服务等,所有的服务都需要在Keystone里注册,组成服务清单目录(catalog),供其他服务调用。例如Nova服务依赖于镜像、存储、网络等基本服务,那么 它在创建虚拟机时,就会去keystone查询可用的服务,如果有就向这个服务请求所需要的资源。
端点是提供服务的URL地址,通常在创建服务的时候必须携带这个参数,如果没有这个参数的话,服务就无法正常使用。端点分为3种类型:
- public,给公开用户使用的地址
- internal,内部组件使用的地址;
- admin,内部管理人员使用的地址
通常规模较小的集群并不会对这三个地址进行细分,都是使用一个地址,但是一些大型的数据中心或者云厂商,它们都会尽可能的对这些网络进行分离,以隔离不同环境的流量。在新的版本中已经把internal和admin版本的地址合并了,就是说只有public和internal两种类型的端点。
# 令牌(token)和凭据(Credentials)
令牌是访问特定资源的凭证,用户在keystone上认证通过后,keystone组件就会给用户颁发一个令牌,里面记录了这个用户的身份、有效期、可以访问的资源等信息。 而凭据通常是用来认证的账号和密码,例如我们通过Horizon面板,Glance、Cinder等各个组件配置文件里填写的账号等。都是它们到keystone认证的凭据。
根据这些核心概念,keystone提供了6个方面的核心服务,分别是:
- 身份服务(Identity),提供身份验证凭证和相关用户、用户组的数据。这个服务通常负责处理这些数据的所有CRUD操作,但是有其他后端组件提供用户管理时,身份服务就只负责对接,例如keystone和ldap对接时,用户是由ldap管理的,身份服务只负责在两者之间中转用户信息。
- 令牌服务(Token),确认用户的身份后,keystone会向用户提供一个令牌。keystone给用户会提供两种令牌:
- 无范围令牌(Unscoped Token),主要用来保存用户的凭据。可以基于这个令牌获取有范围令牌
- 有范围令牌(Scoped Token),这种令牌里指定可以访问的域或者项目范围。只有和特定域、项目绑定的令牌才能访问这个特定域、项目里的资源。
- 服务目录(Catalog),所有服务必须先到keystone里注册,服务目录里会记录在Keystone上注册的所有服务以及他们的访问端点信息供其他服务查询。
- 策略(Policy),是一个基于规则的身份验证引擎,通过配置文件来定义各种动作和用户角色的关联关系,常见路径是/etc/xxx/policy.json文件。
- 资源(Resource),这个服务提供关于域和项目的数据。
- 关联(Assignment),提供角色和角色关联的数据,负责角色授权。
通过这几个服务,把用户和OpenStack组件提供的服务关联起来。以常见的虚拟机创建流程为例:
- 用户发送凭据给Keystone服务(通过web登录或者命令行rc文件),keystone认证通过后给用户返回一个Token和服务目录;
- 用户通过Token向Keystone查询当前环境下的项目列表;
- 用户选择一个项目,申请范围Token;
- 用户通过范围Token和服务目录发送请求到计算服务的端点,创建虚拟机。
- 计算服务Nova向Keystone验证Token(是否在有效期内,是否有权限),验证成功后,开始创建虚拟机流程
实际上创建虚拟机流程还会调用存储服务、网络服务,这些服务都会到Keystone校验用户提供的Token,但是基本流 程都相似。
# Keystone架构
keystone的基本工作流程如下所示:
和上面示例流程一样,用户发送凭据(一般是账号密码)给keystone组件,然后keystone组件验证后给用户返回一个令牌,用户使用令牌向其他服务发送请求,其他服务在响应之前会到keystone上验证这个token的有效性,token有效的话才会响应用户的请求并返回对应的结果。那么在Keystone内部,是怎么完成这些基本的认证流程的呢,我们来看下keystone内部架构,架构图如下所示:
Keystone是一个python开发的web项目,熟悉一点Python web项目开发的同学可能有印象,后端的Server并不是直接响应请求,所有进来的请求必须经过多重中间件(Middleware)过滤,例如验证请求里的一些参数是否合法,经过多重中间件过滤后的请求才会由真正的服务端路由到后端的响应函数上。
Keystone也是这样,当一个请求进来的时候,基本流程是:
它会首先尝试获取请求里的token,
- 如果有token,则根据请求里的token id等信息到后端的memcache获取token,
- 如果可以获取到,那么就会检查注销列表里有没有这个token,如果有,则返回验证失败,如果没有,则返回认证成功;
- 如果后端获取不到token,那么调用cms对token进行验证,
- 验证成功则检查token是否超时,如果超时直接返回认证失败异常;
- cms验证失败则向keystone server验证,验证成功,将keystone server返回的token保存到后端的memcache里供下次使用。
- 如果请求里没有token,则中间件会通过keystoneauth组件调用keystone server的API验证用户提供的凭据(账号、密码)如果认证成功,则给用户返回token,并将token保存到缓存中供下次使用。
从上面整个流程中可以看到,整个流程的关键就是token的验证和校验,每个token里的基本属性有:
- id,token的编号;
- user_id,用户id;
- expires有效期,默认过期时间是600s,这个参数在keystone配置文件里配置,超过这个时间的token会被自动清理。
- 范围
- system,可以用来操作系统服务级别的资源,例如端点、服务、用户等;
- domain,域范围的操作权限;
- project,项目级别的操作权限;
- unscope,无范围token,可能包含服务目录、任意角色、项目或者域范围的令牌。一般组件第一次向keystone认证时都会返回这种类型的令牌,第二次会使用无范围token向keystone申请具体范围的token。
一个无范围示例token内容如下:
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Field | Value |
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| expires | 2023-10-22T14:42:29+0000 |
| id | gAAAAABlXgVFLplRAVPOTa-wRCSWgq34oMGVdFdfCQHpspO0_yK7TV7P1KupM1X8u_H25HVuPJFfnFVyZf2VXcHhUvdI8u4EbyqozGvJkX2XmjCiAv1j6_V1nDTPqUJsre5UKKTs8tMEiGxvyHzdTtFAMsNVI7sKuPzSggTUvNcWVS6YLfNNKs |
| project_id | c40deb5034004fd4a3dfe3f210154ab6 |
| user_id | c54a0581a6304c399fd14175ad24b176 |
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2
3
4
5
6
7
8
# Fernet组件
既然Token那么重要,那么Token是由谁提供的呢?
# Fernet_key
目前keystone里的默认token提供组件是Fernet,Fernet是一个安全消息格式,专门设计用于APItoken,Fernet token是非持久性的(即不需要存放到数据库)、轻量级(180-240bytes),并降低了运行一个云服务的操作。认证和认证元数据绑定到一个消息包的负载(payload)中,然后会被加密和签名作为Fernet token。
不像UUID、PKI和PKIZ token,fernet token不需要持久化,keystone token数据库不会再因为认证的问题而出现膨胀(即大量token导致数据库急剧增大)。使用fernet token时也不再需要从数据库里清除过期的token。因为Fernet token不是持久化的,因此也不需要副本。因为每个keystone节点共享相同的仓库,你可以跨节点创建和校验Fernet token。
和PKI、PKIZ token相比,fernet token更小,通常保证小于250 byte限制。对于PKI和PKIZ token更大的服务目录会导致更大的token长度,这种情况在Fernet token里不存在,因为加密的负载内容长度一直都保证最小化。
# Fernet key类型
Fernet使用3种类型的fernet 密钥,存放在/etc/keystone/fernet-keys目录下,密钥文件名称使用数字命名,最大的数字中包含当前主密钥(primary key),用来生成新的token并加密已有的fernet token。
- 0 包含暂存密钥,并一直是数字0,这里的密钥会在下一次密钥轮替的时候被提升为主密钥;
- 1和2,包含次密钥(secondary key)
- 3,包含主密钥(primary key),这个数字在密钥滚动时会增加,最大的数字总是作为主密钥提供。
根据这个规则我们可以知道,第一次初始化后,默认只有0和1两个密钥,这个时候还没有暂存密钥。
# 设置fernet key的最大数量
激活密钥的最大数量是根据下面的计算公式来计算的:
fernet-keys = token-validity(hours)/rotation-time(hours) + 2
例如,要启用24小时密钥校验周期,每12小时滚动更新一次密钥那就是24/12+2=4。你可以直接在keystone的配置文件里设置这个参数:
#max_active_keys = 3
默认值是3,即1个暂存密钥、1个主密钥和1个副本密钥,增加这个值,默认会增加一个副本密钥。
# 手动fernet token滚动更新
keystone提供了可以手动滚动fernet 密钥的命令,使用方式如下:
keystone-manage fernet_rotate --keystone-user keystone --keystone-group keystone
执行这个命令后,它会根据配置将当前的主密钥变为次密钥或者删除旧的次密钥,将暂存密钥提升为主密钥,并生成一个新的暂存密钥。
# keystone管理命令
keystone的管理命令是keystone-manage,它有多个子命令,这些子命令的作用分别是:
- fernet_setup,为fernet token设置密钥仓库和授权收据(auth receipts),默认仓库路径是/etc/keystone/fernetkeys。这个命令也会创建一个主密钥用于创建和校验fernet token和授权收据,为了提升安全性,你应该轮换密钥,使用key_rotate子命令。
- credentials_setup,为凭据加密设置一个fernet 密钥仓库,默认是/etc/keystone/fernet-keys,这个命令的作用类似于fernet_setup,只有仓库里的密钥是用来加密和解密凭据secrets,而不是token的负载。
# 签发令牌
当具备了用户的基本认证信息(账号和密码),就可以直接向keystone签发一个token,签发的命令是:
openstack token issue
如果使用这个命令签发token时,可以通过rc文件来提供用户的基本认证信息,就像我们在安装好keystone服务时提供的admin用户认证文件admin-rc。如果要为其他用户签发,则使用同样的rc文件来保存用户认证信息,只需要修改用户名和密码即可。 但是这种方式签发的令牌我们无法看到令牌的有效范围,OpenStack官方也没有提供其他的命令来给我们查看令牌的有效范围,我们可以使用curl方式,构建一个API请求。
# 构建一个无范围令牌签发请求
使用用户名和密码构建一个无范围令牌签发请求脚本如下:
通过 vim issue_unscope_token.sh 创建一个文件内容如下:
#!/bin/bash
curl -i \
-H "Content-Type: application/json" \
-d '
{ "auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "admin",
"domain": { "id": "default" },
"password": "CKD3VAQUSOFYMYVs"
}
}
}
}
}' \
"http://192.168.31.185:5000/v3/auth/tokens" ; echo
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
示例返回内容如下:
HTTP/1.1 201 CREATED
Date: Wed, 22 Nov 2023 14:16:26 GMT
Server: Apache/2.4.6 (CentOS) mod_wsgi/3.4 Python/2.7.5
X-Subject-Token:
gAAAAABlXg06cZiETWw_BoUfRC9q5ULj82mqBMYeD6LzXVoNugAuSDDXRXM1_rhKDczTMdlSfJ
ZHAz2T9Yzfj8iAfPDYgx2EW25dCcvAhmoN977PnD6wYl-I3o8DU0HZC3JjOQzqoobP0lMQRPbl5EiTTKdIsra_Q
Vary: X-Auth-Token
x-openstack-request-id: req-00e9d6fd-d31a-48c2-aa20-eb512ebba344
Content-Length: 312
Content-Type: application/json
{
"token": {
"issued_at": "2023-11-22T14:16:26.000000Z",
"audit_ids": [
"7U_t3-Y7RbSwinzKLSo8Bg"
],
"methods": [
"password"
],
"expires_at": "2023-11-22T15:16:26.000000Z",
"user": {
"password_expires_at": null,
"domain": {
"id": "default",
"name": "Default"
},
"id": "c54a0581a6304c399fd14175ad24b176",
"name": "admin"
}
}
}
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
实际的token id是放在X-Subject-Token头部字段里的,下面的是token的各种基本属性信息,比我们通过openstack token issue命令看到的更详细。
# 构建项目级别token签发请求
#!/bin/bash
curl -i \
-H "Content-Type: application/json" \
-d '
{ "auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "admin",
"domain": { "id": "default" },
"password": "CKD3VAQUSOFYMYVs"
}
}
},
"scope": {
"project": {
"name": "admin",
"domain": { "id": "default" }
}
}
}
}' \
"http://192.168.31.185:5000/v3/auth/tokens" ; echo
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
后面示例的返回结果都比较长,大家可以在自己的测试环境上尝试。
# 使用无范围令牌构建一个有范围令牌签发请求
下面的示例脚本使用一个无范围的token请求一个有范围token:
使用 vim issue_scope_token.sh 内容如下:
#!/bin/bash
OS_TOKEN=gAAAAABn2onBat5BAeeFA2xtnMZw2epd94t7yHVkwCF8PEZHliQDG1UR3C-j65DivnWXQN7BhwBPjZljzn-m3ARWubfk0BopyFJqdw80MNURvHbz4B7c_1vvtpd0md7x96MOfGdEoty2KR6zFmzhdDLqfd6Wvkregw
curl -i \
-H "Content-Type: application/json" \
-d '
{ "auth": {
"identity": {
"methods": ["token"],
"token": {
"id": "'$OS_TOKEN'"
}
},
"scope": {
"project": {
"name": "admin",
"domain": {"id": "default"}
}
}
}
}' \
"http://192.168.31.185:5000/v3/auth/tokens" ; echo
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
实际使用时,把里面的OS_TOKEN替换为实际的token id。上面请求的是一个项目范围token,示例返 回请求如下:
HTTP/1.1 201 CREATED
Date: Wed, 22 Nov 2023 14:52:48 GMT
Server: Apache/2.4.6 (CentOS) mod_wsgi/3.4 Python/2.7.5
X-Subject-Token:
gAAAAABlXhXAMG1eSvM3q6KzQgF_gIwFGBcbYXwIdOQ7dUFBwPrXB6nl0WoL6Vn4gLOxiYVmVH6GZ51Kfy8Zupw
o1oV9
xzxvVXTSmkDwf1XhMLs1COtpqNbrTrG_BGUBFU9AnBfSUcRf5cjyfqZV9bDnUD8wmLLfyyK9HIB3WnIpfFT11E3
bG_shbhe_l_QRsCAwnfN8CbA
Vary: X-Auth-Token
x-openstack-request-id: req-4078327e-10ad-4aa1-8530-1f9fe8717465
Content-Length: 4830
Content-Type: application/json
{
"token": {
"is_domain": false,
"methods": [
"token",
"password"
],
"roles": [
{
"id": "28045664dbd04401bca56571956071d0",
"name": "reader"
},
{
"id": "8efcaeaa04ee403f9af330d161a79d06",
"name": "admin"
},
{
"id": "9a016ae23f684e05adb1009a4e7e450e",
"name": "member"
}
],
"expires_at": "2023-11-22T15:16:26.000000Z",
"project": {
"domain": {
"id": "default",
"name": "Default"
},
"id": "c40deb5034004fd4a3dfe3f210154ab6",
"name": "admin"
},
"catalog": [
{
"endpoints": [
{
"url": "http://controller:5000/v3/",
"interface": "public",
"region": "RegionOne",
"region_id": "RegionOne",
"id": "1da89437bbcd4050855466a217e44ac9"
},
{
"url": "http://controller:5000/v3/",
"interface": "admin",
"region": "RegionOne",
"region_id": "RegionOne",
"id": "6dbc5ff694a5478fa532a91f25cf87f5"
},
{
"url": "http://controller:5000/v3/",
"interface": "internal",
"region": "RegionOne",
"region_id": "RegionOne",
"id": "fd7e7c789400424484101bf940dbb0d6"
}
],
"type": "identity",
"id": "400a5701e5a54c16b2896183b9bffaf4",
"name": "keystone"
},
....
],
"user": {
"password_expires_at": null,
"domain": {
"id": "default",
"name": "Default"
},
"id": "c54a0581a6304c399fd14175ad24b176",
"name": "admin"
},
"audit_ids": [
"8bpwWnqESN6WxdpBwcAj6Q",
"7U_t3-Y7RbSwinzKLSo8Bg"
],
"issued_at": "2023-11-22T14:52:48.000000Z"
}
}
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
为了节省篇幅,里面删除了不少endpoints信息,具体的可以查看实际操作演示。
# 发送请求
获取域列表
使用命令 vim request_domain.sh 创建文件,内容如下:
#!/bin/bash
OS_TOKEN=gAAAAABn23v5x77KPbQcypxulK3gIHL6B7gu6BrD3g-tmHM-eLiHUBVoVEk55qU8L_2wLRlqbFZpyAg0u_D3VJEuzcM7MUM52YOw8KATnycF-SG8TtVswzLU-aQ490KxLtDL-ixiO8S55Nm-L3HJcF8PdXae1xeFEc8SuAUx7i0bt4STPlFrrOc
curl -s \
-H "X-Auth-Token: $OS_TOKEN" \
"http://192.168.31.185:5000/v3/domains" | python -mjson.tool
2
3
4
5
6
7
8
获取项目列表 使用命令 vim request_project.sh 创建文件,内容如下:
#!/bin/bash
OS_TOKEN=gAAAAABn23v5x77KPbQcypxulK3gIHL6B7gu6BrD3g-tmHM-eLiHUBVoVEk55qU8L_2wLRlqbFZpyAg0u_D3VJEuzcM7MUM52YOw8KATnycF-SG8TtVswzLU-aQ490KxLtDL-ixiO8S55Nm-L3HJcF8PdXae1xeFEc8SuAUx7i0bt4STPlFrrOc
curl -s \
-H "X-Auth-Token: $OS_TOKEN" \
"http://192.168.31.185:5000/v3/projects" | python -mjson.tool
2
3
4
5
6
7
8
# 撤销令牌
要撤销令牌直接可以直接使用openstack的子命令,用法是:
openstack token revoke <token-id>
这个命令只能撤销已存在的令牌,且必须知道令牌ID,且执行成功后没有任何返回信息。当然也可以通过curl直接构建删除token的请求,示例用法如下:
curl -i -X DELETE \
-H "X-Auth-Token: $OS_TOKEN" \
-H "X-Subject-Token: $OS_TOKEN" \
"http://192.168.31.185:5000/v3/auth/tokens"
2
3
4
实际使用时,把里面的OS_TOKEN变量替换为实际的token id即可。 示例返回请求如下所示:
HTTP/1.1 204 NO CONTENT
Date: Wed, 22 Nov 2023 14:59:46 GMT
Server: Apache/2.4.6 (CentOS) mod_wsgi/3.4 Python/2.7.5
Vary: X-Auth-Token
x-openstack-request-id: req-89d4a065-dad5-463c-b2bf-2d6aedd921df
Content-Type: text/plain; charset=UTF-8
2
3
4
5
6
# 实战内容:Keystone对接LDAP
在实际公司内部部署OpenStack的时候,我们一般会把keystone组件和公司的LDAP对接,实现统一的权限认证和管理。根据下面的步骤来配置一个keystone和LDAP服务器的对接示例。
# 测试LDAP服务器搭建
# 前提条件
准备好一台安装好docker的linux服务器,如果资源有限,也可以在当前已有的机器上部署,后面需要注意避免端口冲突。我这里准备的机器ip地址是192.168.31.120,主机名是ldap.my.com
# 配置LDAP服务端
如果你自己的环境中有现成的LDAP服务器配置,那么可以跳过这个步骤。因为这里只是为了测试目的,我们这里直接使用docker来搭建一个测试使用的LDAP服务器,执行下面的命令拉取LDAP服务器所需的镜像:
docker pull osixia/openldap
docker pull osixia/phpldapadmin
2
先部署LDAP服务,启动容器的命令是:
docker run -d -p 389:389 -p 636:636 \
-v /usr/local/ldap:/usr/local/ldap \
-v /opt/ldap:/var/lib/ldap \
-v /opt/slapd.d:/etc/ldap/slapd.d \
--env LDAP_ORGANISATION="myhuihui" \
--env LDAP_DOMAIN="myhuihui.com" \
--env LDAP_ADMIN_PASSWORD="0tzoVvy8xIasFnla" \
--name openldap --hostname ldap.myhuihui.com \
osixia/openldap
2
3
4
5
6
7
8
9
上面配置中,你自己实验时,需要把里面的主机名、域名、组织名称都换成你自己对应的配置参数。
# PHPLdapAdmin Web管理工具配置
phpldapadmin是一个提供ldap Web管理页面的工具,现在也可以直接通过容器来部署,之前镜像已经拉取到本地,直接使用下面的命令来启动即可:
docker run -p 80:80 --privileged -d \
--name ldapweb --env PHPLDAPADMIN_HTTPS=false \
--env PHPLDAPADMIN_LDAP_HOSTS=192.168.31.120 \
osixia/phpldapadmin
2
3
4
启动成功后,直接访问这台机器的80端口即可,对应页面如下所示:
点击页面左侧login登录,默认的登录账号是cn=admin,dc=my,dc=com,默认密码就是启动ldap服务端时在环境变量里指定的密码。登录后完成LDAP组织配置,我们需要在my.com这个域下添加两个OU,分别是groups和users,添加完成后如下所示:
然后在groups这个OU下创建3个普通的用户组(posix group),分别是admin、deveopers和users,创建完成后如下所示:
点击ou,在ou里创建一个普通的用户账号,并将它分配到admin组,如下所示:
稍后,我们会在OpenStack上使用这个账号来登录。
# Keystone组件配置
对于Keystone来说,需要梳理清楚几个概念:
- eystone和LDAP对接的时候,Keystone里的域和LDAP的域是存在对应关系的,如果直接配置对接,那么会把openldap里配置的账号直接同步到keystone dfault域里,会和原有的账号混在一起,如果你愿意这样使用那下面就不需要开启多域支持。
- 如果要在keystone里创建一个openldap里的同名域,那么需要开启keystone的多域配置;default <=> myhuihui.com
# Keystone创建同名域
我们需要在keystone里创建一个同名域和LDAP里的域对应,命令是:
source admin-rc
openstack domain --description ""my.com" my
2
创建完成后在这个域下创建一个测试项目:
openstack project create --domain my demo1
因为当用户没有项目绑定时,通过keystone登录就会提示该用户没有绑定任何项目,无法调整到任何管理页面,因此这里要创建一个测试项目。
# keystone多域配置
首先打开/etc/keystone/keystone.conf,添加下面的配置启用多域支持:
[identity]
domain_specific_drivers_enabled = True
domain_config_dir = /etc/keystone/domains
2
3
配置完成后保存退出,然后创建上面配置项里用来保存不同域配置文件的目录,命令是:
mkdir /etc/keystone/domains
chown keystone.keystone /etc/keystone/domains
2
创建好以后,我们需要在这个目录下创建一个我们LDAP域相关的配置文件,我们LDAP里配置的域是my.com,那么对应的配置文件名就是keystone.my.conf,这个文件的内容如下:
[identity]
driver = ldap
[assignment]
driver = sql
[ldap]
# ldap连接信息
url = ldap://192.168.31.120:389
user = cn=admin,dc=myhuihui,dc=com
password = 0tzoVvy8xIasFnla
suffix = dc=myhuihui,dc=com
use_dumb_member = False
allow_subtree_delete = False
# 用户映射关系
query_scope = sub
user_tree_dn = ou=Users,dc=myhuihui,dc=com
user_objectclass = inetOrgPerson
user_id_attribute = uidNumber
user_name_attribute = sn
user_email_attribute = mail
user_enabled_attribute = userAccountControl
user_enabled_default = 512
user_enabled_mask = 2
user_enabled_emulation = False
# 群组映射关系
group_tree_dn = ou=Groups,dc=myhuihui,dc=com
group_objectclass = groupOfNames
group_id_attribute = cn
group_name_attribute = ou
group_member_attribute = member
# 日志debug级别
debug_level = -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
添加完成后,执行下面命令重启httpd服务
systemctl restart httpd
重启完成后,这个时候Keystone组件,重启完成后。keystone组件会自动把OpenLDAP上对应组里的 用户同步到本地,如下所示:
openstack user list --domain my
+------------------------------------------------------------------+-------+
| ID | Name |
+------------------------------------------------------------------+-------+
| b9d6eaba1424d742938cf2c7d04135a3e92b87b38e41d85dd9876e37b8624a3f | hands |
+------------------------------------------------------------------+-------+
2
3
4
5
6
我们就可以看到在OpenLDAP上创建的用户了,然后我们把这个用户和我们上面在myhuihui这个域里创建的测试项目绑定:
openstack role add --project demo1 \
--user b9d6eaba1424d742938cf2c7d04135a3e92b87b38e41d85dd9876e37b8624a3f \
admin
2
3
把这个用户设置为这个项目的管理员角色,然后就可以通过这个用户到keystone上去登录了。实际的登录效果如下所示:
通过多域配置,可以让default域的用户仍然以之前保存的账号密码登录,而使用LDAP登录的用户直接使用ldap配置的账号密码登录。通常default域是保留给管理员使用。