通过Dex和openLDAP进行Kubernetes身份验证

Dex是一种身份服务,它使用OpenID Connect(简称OIDC)来驱动其他应用程序的身份验证。

Dex通过“connectors.”充当其他身份提供商的门户。这使得dex可以将身份验证延迟(找不到很好的词来形容,只能硬翻了)到LDAP服务器、SAML提供程序或已建立的身份提供程序(如GitHub,Google和Active Directory)。客户端编写一次身份验证逻辑与dex通信,然后dex处理给定后端的协议。

OAuth2

OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。

我相信大家都使用过类似“使用QQ登录”诸如此类的按钮来登录一些第三方的应用或者网站。在这些情况下,第三方应用程序(网站)选择让外部提供商(在这种情况下为QQ)证明您的身份,而不是让您使用应用程序本身设置用户名和密码。

服务器端应用程序的一般流程是:

  • 新用户访问应用程序。
  • 用户点击网站上的登录按钮(诸如“使用QQ登录”),该应用程序将用户重定向到QQ。
  • 用户登录QQ,然后QQ会提示该应用程序会获取的相应权限。
  • 如果用户单击“授权并登录”,则QQ会连同获取的Access Token使用代码将用户重定向回该应用程序。
  • 应用程序通过Access Token获取用户的OpenID;调用OpenAPI,来请求访问或修改用户授权的资源。

在这些情况下,dex充当QQ(在OpenID Connect中称为“provider”),而客户端应用程序重定向到它以获得最终用户的身份。

关于OAuth: https://oauth.net/2/

ID Tokens

ID Tokens
是OpenID Connect和dex主要功能引入的OAuth2扩展。ID Tokens
是由dex签名的JSON Web令牌(JWT),作为OAuth2响应的一部分返回,用于证明最终用户的身份。

OpenID Connect的OAuth2主要扩展名是令牌响应中返回的额外令牌,称为ID Tokens。此令牌是由OpenID Connect服务器签名的JSON Web令牌,具有用户ID,名称,电子邮件等众所周知的字段。

Connectors

当用户通过dex登录时,用户的身份通常存储在另一个用户管理系统中:LDAP目录,GitHub组织等。Dex充当客户端应用程序和上游身份提供者之间的中间人。客户端只需要了解OpenID Connect来查询dex,而dex实现了一组用于查询其他用户管理系统的协议。

OpenID Connect Tokens

OpenID Connect 1.0是OAuth 2.0协议之上的简单身份层。它允许客户端根据授权服务器执行的身份验证来验证最终用户的身份,以及以可互操作和类似REST的方式获取有关最终用户的基本配置文件信息。

OpenID Connect允许所有类型的客户端(包括基于Web,移动和JavaScript客户端)请求和接收有关经过身份验证的会话和最终用户的信息。规范套件是可扩展的,允许参与者在对它们有意义时使用可选功能,例如身份数据加密,OpenID提供程序的发现和会话管理。

协议的OAuth2的主要扩展是返回的附加字段,其中访问令牌称为ID Token。此令牌是JSON Web令牌(JWT),具有由服务器签名的众所周知的字段,例如用户的电子邮件。

为了识别用户,验证者使用OAuth2 令牌响应中的id_token(而不是access_token) 作为承载令牌。

来自OpenID Connect提供商的令牌响应包括一个称为ID令牌的签名JWT。ID令牌包含名称,电子邮件,唯一标识符,在dex的情况下,包含一组可用于标识用户的组。像dex这样的OpenID Connect提供程序发布公钥; Kubernetes API服务器了解如何使用它们来验证ID令牌。

关于OpenID Connect: https://openid.net/connect/

身份验证流程如下所示:

  • OAuth2客户端通过dex登录用户。
  • 在与Kubernetes API通信时,该客户端使用返回的ID令牌作为承载令牌。
  • Kubernetes使用dex的公钥来验证ID令牌。
  • 指定为用户名(以及可选的组信息)的声明将与该请求相关联。

用户名和组信息可以与Kubernetes 授权插件(例如基于角色的访问控制(RBAC))结合使用以实施策略。

dex有自己的用户概念,但它允许它们以不同的方式进行身份验证,称为connectors。目前,dex提供两种类型的连接器:local连接器和OIDC连接器。使用local连接器进行身份验证时,用户使用电子邮件和密码登录,并使用dex本身提供的可自定义UI。使用OIDC连接器,用户可以通过登录到另一个OIDC身份提供商(如Google或Salesforce)进行身份验证。

从dex请求ID令牌

直接使用dex对用户进行身份验证的应用程序使用OAuth2代码流来请求令牌响应。采取的确切步骤是:

  • 用户访问客户端应用。
  • 客户端应用程序通过OAuth2请求将用户重定向到dex。
  • Dex确定用户的身份。
  • Dex使用代码将用户重定向到客户端。
  • 客户端使用dex为id_token交换代码。

部署 openLDAP

配置PVC

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
# vim openldap-pvc.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ldap-data
namespace: default
labels:
app: ldap
spec:
storageClassName: default-rbd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "5Gi"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ldap-config
namespace: default
labels:
app: ldap
spec:
storageClassName: default-rbd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "1Gi"

# kubectl create -f openldap-pvc.yaml
persistentvolumeclaim/ldap-data created
persistentvolumeclaim/ldap-config created

部署openLDAP

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
# vim openldap.yaml 
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ldap
labels:
app: ldap
spec:
replicas: 1
template:
metadata:
labels:
app: ldap
spec:
containers:
- name: ldap
image: 192.168.100.100/library/openldap:1.2.2
volumeMounts:
- name: ldap-data
mountPath: /var/lib/ldap
- name: ldap-config
mountPath: /etc/ldap/slapd.d
- name: ldap-certs
mountPath: /container/service/slapd/assets/certs
ports:
- containerPort: 389
name: openldap
env:
- name: LDAP_LOG_LEVEL
value: "256"
- name: LDAP_ORGANISATION
value: "zhi"
- name: LDAP_DOMAIN
value: "flywzj.com"
- name: LDAP_ADMIN_PASSWORD
value: "admin"
- name: LDAP_CONFIG_PASSWORD
value: "config"
- name: LDAP_READONLY_USER
value: "false"
- name: LDAP_READONLY_USER_USERNAME
value: "readonly"
- name: LDAP_READONLY_USER_PASSWORD
value: "readonly"
- name: LDAP_RFC2307BIS_SCHEMA
value: "false"
- name: LDAP_BACKEND
value: "mdb"
- name: LDAP_TLS
value: "true"
- name: LDAP_TLS_CRT_FILENAME
value: "ldap.crt"
- name: LDAP_TLS_KEY_FILENAME
value: "ldap.key"
- name: LDAP_TLS_CA_CRT_FILENAME
value: "ca.crt"
- name: LDAP_TLS_ENFORCE
value: "false"
- name: LDAP_TLS_CIPHER_SUITE
value: "SECURE256:+SECURE128:-VERS-TLS-ALL:+VERS-TLS1.2:-RSA:-DHE-DSS:-CAMELLIA-128-CBC:-CAMELLIA-256-CBC"
- name: LDAP_TLS_VERIFY_CLIENT
value: "demand"
- name: LDAP_REPLICATION
value: "false"
- name: LDAP_REPLICATION_CONFIG_SYNCPROV
value: "binddn=\"cn=admin,cn=config\" bindmethod=simple credentials=$LDAP_CONFIG_PASSWORD searchbase=\"cn=config\" type=refreshAndPersist retry=\"60 +\" timeout=1 starttls=critical"
- name: LDAP_REPLICATION_DB_SYNCPROV
value: "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"
- name: LDAP_REPLICATION_HOSTS
value: "#PYTHON2BASH:['ldap://ldap-one-service', 'ldap://ldap-two-service']"
- name: KEEP_EXISTING_CONFIG
value: "false"
- name: LDAP_REMOVE_CONFIG_AFTER_SETUP
value: "true"
- name: LDAP_SSL_HELPER_PREFIX
value: "ldap"
volumes:
- name: ldap-data
persistentVolumeClaim:
claimName: ldap-data
#hostPath:
# path: "/data/ldap/db"
- name: ldap-config
persistentVolumeClaim:
claimName: ldap-config
#hostPath:
# path: "/data/ldap/config"
- name: ldap-certs
hostPath:
path: "/data/ldap/certs"
---
apiVersion: v1
kind: Service
metadata:
labels:
app: ldap
name: ldap
spec:
type: NodePort
ports:
- port: 389
nodePort: 38989
selector:
app: ldap
# kubectl create -f openldap.yaml
deployment.extensions/ldap created
service/ldap created
# kubectl get pods -l app=ldap
NAME READY STATUS RESTARTS AGE
ldap-65f5786ff8-xrtkk 1/1 Running 0 28d

部署openldapadmin

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
# vim phpldapadmin.yaml 
apiVersion: v1
kind: ReplicationController
metadata:
name: phpldapadmin-controller
labels:
app: phpldapadmin
spec:
replicas: 1
selector:
app: phpldapadmin
template:
metadata:
labels:
app: phpldapadmin
spec:
containers:
- name: phpldapadmin
image: 192.168.100.100/library/phpldapadmin:0.7.2
volumeMounts:
- name: phpldapadmin-certs
mountPath: /container/service/phpldapadmin/assets/apache2/certs
- name: ldap-client-certs
mountPath: /container/service/ldap-client/assets/certs
ports:
- containerPort: 443
env:
- name: PHPLDAPADMIN_LDAP_HOSTS
#value: "#PYTHON2BASH:[{'ldap-service': [{'server': [{'tls': 'true'}]}]}]"
value: "ldap"
- name: PHPLDAPADMIN_SERVER_ADMIN
value: "wangzhijiansd@qq.com"
- name: PHPLDAPADMIN_SERVER_PATH
value: "/phpldapadmin"
- name: PHPLDAPADMIN_HTTPS
value: "true"
- name: PHPLDAPADMIN_HTTPS_CRT_FILENAME
value: "cert.crt"
- name: PHPLDAPADMIN_HTTPS_KEY_FILENAME
value: "cert.key"
- name: PHPLDAPADMIN_HTTPS_CA_CRT_FILENAME
value: "ca.crt"
- name: PHPLDAPADMIN_LDAP_CLIENT_TLS
value: "true"
- name: PHPLDAPADMIN_LDAP_CLIENT_TLS_REQCERT
value: "demand"
- name: PHPLDAPADMIN_LDAP_CLIENT_TLS_CRT_FILENAME
value: "cert.crt"
- name: PHPLDAPADMIN_LDAP_CLIENT_TLS_KEY_FILENAME
value: "cert.key"
- name: PHPLDAPADMIN_LDAP_CLIENT_TLS_CA_CRT_FILENAME
value: "ca.crt"
volumes:
- name: phpldapadmin-certs
hostPath:
path: "/data/phpldapadmin/ssl/"
- name: ldap-client-certs
hostPath:
path: "/data/phpldapadmin/ldap-client-certs/"
---
apiVersion: v1
kind: Service
metadata:
labels:
app: phpldapadmin
name: phpldapadmin
spec:
type: NodePort
ports:
- port: 443
nodePort: 32001
selector:
app: phpldapadmin
# kubectl create -f phpldapadmin.yaml
replicationcontroller/phpldapadmin-controller created
service/phpldapadmin created
# kubectl get pods -l app=phpldapadmin
NAME READY STATUS RESTARTS AGE
phpldapadmin-controller-4cwlb 1/1 Running 0 28d

关于 openLDAP 的相关镜像详见:

配置 openLDAP

使用 phpldapadmin 进行配置管理

  • login:
  • Login DN: cn=admin,dc=flywzj,dc=com
  • Password: admin
  • 点击 Authenticate 登录

登录LDAP容器进行配置管理

注1:Dex目前允许不安全的连接,但是Dex官方强烈建议使用TLS,通过使用端口636而不是389来实现。这里使用的是不安全的389端口来实现,请知悉。

注2:这里配置两个组,组k8s关联用户wang,组test关联用户zhi

查看当前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
# kubectl exec -it ldap-65f5786ff8-xrtkk /bin/bash
root@ldap-65f5786ff8-xrtkk:/# ldapsearch -x -H ldap:// -b dc=flywzj,dc=com -D "cn=admin,dc=flywzj,dc=com" -w admin
# extended LDIF
#
# LDAPv3
# base <dc=flywzj,dc=com> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# flywzj.com
dn: dc=flywzj,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: zhi
dc: flywzj

# admin, flywzj.com
dn: cn=admin,dc=flywzj,dc=com
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: e1NTSEF9NFpVZU5IaGhDSzZ4OWF2KzBCSjlZOUY4SzRhWTdpWUk=

# search result
search: 2
result: 0 Success

# numResponses: 3
# numEntries: 2
配置新建OU和组
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
root@ldap-65f5786ff8-xrtkk:/# cat <<EOF > container/service/slapd/assets/groups.ldif
dn: ou=Groups,dc=flywzj,dc=com
ou: Groups
objectClass: organizationalUnit
objectClass: top

dn: ou=People,dc=flywzj,dc=com
ou: People
objectClass: organizationalUnit
objectClass: top

dn: cn=k8s,ou=Groups,dc=flywzj,dc=com
cn: k8s
gidNumber: 500
objectClass: posixGroup
objectClass: top
memberuid: wang

dn: cn=test,ou=Groups,dc=flywzj,dc=com
cn: test
gidNumber: 501
objectClass: posixGroup
objectClass: top
memberuid: zhi
EOF
root@ldap-65f5786ff8-xrtkk:/# ldapadd -x -D "cn=admin,dc=flywzj,dc=com" -w admin -f /container/service/slapd/assets/groups.ldif -H ldap:///
adding new entry "ou=Groups,dc=flywzj,dc=com"

adding new entry "ou=People,dc=flywzj,dc=com"

adding new entry "cn=k8s,ou=Groups,dc=flywzj,dc=com"

adding new entry "cn=test,ou=Groups,dc=flywzj,dc=com"

如上可以看出,这里新建了两个OU:

  • ou=Groups,dc=flywzj,dc=com
  • ou=People,dc=flywzj,dc=com

同时新建了两个组:

  • cn=k8s,ou=Groups,dc=flywzj,dc=com
  • cn=test,ou=Groups,dc=flywzj,dc=com

特别说明,所有配置是都可以放在一个 ldif 文件中来进行配置的,这里分成两个 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
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
root@ldap-65f5786ff8-xrtkk:/# ldapsearch -x -H ldap:// -b dc=flywzj,dc=com -D "cn=admin,dc=flywzj,dc=com" -w admin
# extended LDIF
#
# LDAPv3
# base <dc=flywzj,dc=com> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# flywzj.com
dn: dc=flywzj,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: zhi
dc: flywzj

# admin, flywzj.com
dn: cn=admin,dc=flywzj,dc=com
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: e1NTSEF9NHNid2tMZCtnSS9LazJieGRsdWdhZFN1OGR0ZE4wVjA=

# Groups, flywzj.com
dn: ou=Groups,dc=flywzj,dc=com
ou: Groups
objectClass: organizationalUnit
objectClass: top

# People, flywzj.com
dn: ou=People,dc=flywzj,dc=com
ou: People
objectClass: organizationalUnit
objectClass: top

# k8s, Groups, flywzj.com
dn: cn=k8s,ou=Groups,dc=flywzj,dc=com
cn: k8s
gidNumber: 500
objectClass: posixGroup
objectClass: top
memberUid: wang

# test, Groups, flywzj.com
dn: cn=test,ou=Groups,dc=flywzj,dc=com
cn: test
gidNumber: 501
objectClass: posixGroup
objectClass: top
memberUid: zhi

# search result
search: 2
result: 0 Success

# numResponses: 9
# numEntries: 8
配置用户
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
# cat <<EOF > /container/service/slapd/assets/users.ldif 
dn: uid=wang,ou=People,dc=flywzj,dc=com
cn: wang
gidnumber: 500
givenname: wang
homedirectory: /home/users/wang
loginshell: /bin/sh
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: top
objectClass: shadowAccount
objectClass: organizationalPerson
mail: wangzhijiansd@qq.com
sn: wang
uid: wang
uidnumber: 1000
userpassword: wangzhijian

dn: uid=zhi,ou=People,dc=flywzj,dc=com
homedirectory: /home/users/zhi
loginshell: /bin/sh
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: top
objectClass: shadowAccount
objectClass: organizationalPerson
mail: zhijiansd@163.com
cn: zhi
givenname: zhi
sn: zhi
uid: zhi
uidnumber: 1001
gidnumber: 501
userpassword: zhijian
EOF
root@ldap-65f5786ff8-xrtkk:/# ldapadd -x -D "cn=admin,dc=flywzj,dc=com" -w admin -f /container/service/slapd/assets/users.ldif -H ldap:///
adding new entry "uid=wang,ou=People,dc=flywzj,dc=com"

adding new entry "uid=zhi,ou=People,dc=flywzj,dc=com"
查看配置文件
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
root@ldap-65f5786ff8-xrtkk:/# ldapsearch -x -H ldap:// -b dc=flywzj,dc=com -D "cn=admin,dc=flywzj,dc=com" -w admin
# extended LDIF
#
# LDAPv3
# base <dc=flywzj,dc=com> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# flywzj.com
dn: dc=flywzj,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: zhi
dc: flywzj

# admin, flywzj.com
dn: cn=admin,dc=flywzj,dc=com
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: e1NTSEF9NHNid2tMZCtnSS9LazJieGRsdWdhZFN1OGR0ZE4wVjA=

# Groups, flywzj.com
dn: ou=Groups,dc=flywzj,dc=com
ou: Groups
objectClass: organizationalUnit
objectClass: top

# People, flywzj.com
dn: ou=People,dc=flywzj,dc=com
ou: People
objectClass: organizationalUnit
objectClass: top

# k8s, Groups, flywzj.com
dn: cn=k8s,ou=Groups,dc=flywzj,dc=com
cn: k8s
gidNumber: 500
objectClass: posixGroup
objectClass: top
memberUid: wang

# test, Groups, flywzj.com
dn: cn=test,ou=Groups,dc=flywzj,dc=com
cn: test
gidNumber: 501
objectClass: posixGroup
objectClass: top
memberUid: zhi

# wang, People, flywzj.com
dn: uid=wang,ou=People,dc=flywzj,dc=com
cn: wang
gidNumber: 500
givenName: wang
homeDirectory: /home/users/wang
loginShell: /bin/sh
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: top
objectClass: shadowAccount
objectClass: organizationalPerson
mail: wangzhijiansd@qq.com
sn: wang
uid: wang
uidNumber: 1000
userPassword:: d2FuZ3poaWppYW4=

# zhi, People, flywzj.com
dn: uid=zhi,ou=People,dc=flywzj,dc=com
homeDirectory: /home/users/zhi
loginShell: /bin/sh
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: top
objectClass: shadowAccount
objectClass: organizationalPerson
mail: zhijiansd@163.com
cn: zhi
givenName: zhi
sn: zhi
uid: zhi
uidNumber: 1001
gidNumber: 501
userPassword:: emhpamlhbg==

# search result
search: 2
result: 0 Success

# numResponses: 9
# numEntries: 8

这时候你也可以使用 phpldapadmin 登录查看,特别说明,如果登录进去出现了一些”?”提示,那是因为某些模板没有导入导致的,可忽略。

部署 Dex

Dex详见:https://github.com/dexidp/dex

生成证书并配置secret

生成dex server和login application相关证书和secret

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
# vim gencert.sh
#!/bin/bash

mkdir -p ssl

cat << EOF > ssl/req.cnf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name

[req_distinguished_name]

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = dex
DNS.2 = dex.svc.cluster.local
DNS.3 = loginapp
DNS.4 = loginapp.svc.cluster.local
DNS.5 = login.flywzj.com
IP.1 = 192.168.100.181
IP.2 = 192.168.100.182
IP.3 = 192.168.100.183
EOF

openssl genrsa -out ssl/dex-ca-key.pem 2048
openssl req -x509 -new -nodes -key ssl/dex-ca-key.pem -days 1000 -out ssl/dex-ca.pem -subj "/CN=kube-ca"

openssl genrsa -out ssl/dex-app-key.pem 2048
openssl req -new -key ssl/dex-app-key.pem -out ssl/dex-app-csr.pem -subj "/CN=kube-ca" -config ssl/req.cnf
openssl x509 -req -in ssl/dex-app-csr.pem -CA ssl/dex-ca.pem -CAkey ssl/dex-ca-key.pem -CAcreateserial -out ssl/dex-app.pem -days 1000 -extensions v3_req -extfile ssl/req.cnf

kubectl create secret tls dex --cert=ssl/dex-app.pem --key=ssl/dex-app-key.pem
kubectl create secret tls loginapp --cert=ssl/dex-app.pem --key=ssl/dex-app-key.pem

# ./gencert.sh
Generating RSA private key, 2048 bit long modulus
........................+++
.......................................+++
e is 65537 (0x10001)
Generating RSA private key, 2048 bit long modulus
...........................................+++
........................+++
e is 65537 (0x10001)
Signature ok
subject=/CN=kube-ca
Getting CA Private Key
secret/dex created
secret/loginapp created

# kubectl get secret dex
NAME TYPE DATA AGE
dex kubernetes.io/tls 2 8h
# kubectl get secret loginapp
NAME TYPE DATA AGE
loginapp kubernetes.io/tls 2 8h

复制证书

用于为Dex签署SSL证书的CA文件需要复制到apiserver可以读取的位置

1
# cp ssl/dex-ca.pem /etc/kubernetes/ssl/

部署 Dex

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
# wget https://raw.githubusercontent.com/dexidp/dex/master/examples/k8s/dex.yaml
# vim dex.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: dex
name: dex
namespace: default
spec:
replicas: 1
template:
metadata:
labels:
app: dex
spec:
serviceAccountName: dex # This is created below
containers:
- image: 192.168.100.100/coreos/dex:v2.10.0
name: dex
command: ["/usr/local/bin/dex", "serve", "/etc/dex/cfg/config.yaml"]

ports:
- name: https
containerPort: 5556

volumeMounts:
- name: config
mountPath: /etc/dex/cfg
- name: tls
mountPath: /etc/dex/tls

volumes:
- name: config
configMap:
name: dex
items:
- key: config.yaml
path: config.yaml
- name: tls
secret:
secretName: dex
---
kind: ConfigMap
apiVersion: v1
metadata:
name: dex
namespace: default
data:
config.yaml: |
issuer: https://192.168.100.185:32000
storage:
type: kubernetes
config:
inCluster: true
web:
https: 0.0.0.0:5556
tlsCert: /etc/dex/tls/tls.crt
tlsKey: /etc/dex/tls/tls.key

logger:
level: "debug"
format: text

connectors:
- type: ldap
id: ldap
name: LDAP
config:
host: ldap:389
insecureNoSSL: true
insecureSkipVerify: true
bindDN: cn=admin,dc=flywzj,dc=com
bindPW: admin
userSearch:
baseDN: ou=People,dc=flywzj,dc=com
filter: "(objectClass=posixAccount)"
username: mail
idAttr: uid
emailAttr: mail
nameAttr: uid
groupSearch:
baseDN: ou=Groups,dc=flywzj,dc=com
filter: "(objectClass=posixGroup)"
userAttr: uid
groupAttr: memberUid
nameAttr: cn
oauth2:
skipApprovalScreen: true

staticClients:
- id: login
redirectURIs:
- 'https://192.168.100.185:32002/callback'
name: 'Login App'
secret: 4TORGiNV9M54BTk1v7dNuFSaI6hUjfjr

enablePasswordDB: true
staticPasswords:
- email: "wangzhijiansd@qq.com"
# bcrypt hash of the string "password"
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: "admin"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
---
apiVersion: v1
kind: Service
metadata:
name: dex
namespace: default
spec:
type: NodePort
ports:
- name: dex
port: 5556
protocol: TCP
targetPort: 5556
nodePort: 32000
selector:
app: dex
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: dex
name: dex
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: dex
rules:
- apiGroups: ["dex.coreos.com"] # API group created by dex
resources: ["*"]
verbs: ["*"]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["create"] # To manage its own resources, dex must be able to create customresourcedefinitions
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: dex
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: dex
subjects:
- kind: ServiceAccount
name: dex # Service account assigned to the dex pod, created above
namespace: default # The namespace dex is running in

# kubectl create -f dex.yaml
deployment.extensions/dex created
configmap/dex created
service/dex created
serviceaccount/dex created
clusterrole.rbac.authorization.k8s.io/dex created
clusterrolebinding.rbac.authorization.k8s.io/dex created
# kubectl get pod --show-labels -l app=dex
# kubectl get services dex

注意1: yaml文件虽然是从官方的,但是做了一些改动,特别是这里将官方的3个副本更改为了1个副本,因为使用3个副本事会产生错误,我在issue中翻阅到有说是NTP时钟不同步造成的。

注意2: 这里的 connectors 是LDAP,详细文档见:https://github.com/dexidp/dex/blob/master/Documentation/connectors/ldap.md

查看 OpenID Connect 发现

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
# curl -k https://192.168.100.185:32000/.well-known/openid-configuration
{
"issuer": "https://192.168.100.185:32000",
"authorization_endpoint": "https://192.168.100.185:32000/auth",
"token_endpoint": "https://192.168.100.185:32000/token",
"jwks_uri": "https://192.168.100.185:32000/keys",
"response_types_supported": [
"code"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid",
"email",
"groups",
"profile",
"offline_access"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic"
],
"claims_supported": [
"aud",
"email",
"email_verified",
"exp",
"iat",
"iss",
"locale",
"name",
"sub"
]
}

查看 JSON Web Key

1
2
3
4
5
6
7
8
9
10
11
12
# curl -k https://192.168.100.185:32000/keys
{
"keys": [
{
"use": "sig",
"kty": "RSA",
"kid": "a813de5c6100949abc59317714e3b09abecf8641",
"alg": "RS256",
"n": "u7G_RoZEuDwiW7kLBCMjjJMm1NgnHIXiTznxABe3uW8GsdASqRhUsDH2zFceZZObKchHWrKpkPZS4SjvcThF785xoJ4-FlAcrsUd4agyN9uwrAeL_luOrXvl-i0QAUKIHlqbTfZmzBIaFhHnG0yXKgqkXzTarQxDeynWVrVTdWsm7P_BYjQ5dnIlZu1xeRzw-NWf5UAi9Csh1x82XMtlAbMgWlJoWI36yVCCGUdJYintSp-tOfjkPBUghIO7ju8fb22X5uOgRFMq_RkIpXs2asf5FapVQMpcX_WAK3vUhmfH5F0lQZ9Cv9U__k3rHKRS7XwkcSQ4OKf7Vxrx4LQEcQ",
"e": "AQAB"
}
}

配置 Login App

一旦启动并运行dex,下一步就是编写使用dex驱动身份验证的应用程序。具体详见:https://github.com/dexidp/dex/blob/master/Documentation/using-dex.md。

这里我们使用 loginapp 来配置:https://github.com/fydrah/loginapp。

配置 Configmap

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
# cat ssl/dex-ca.pem 
-----BEGIN CERTIFICATE-----
MIIC9zCCAd+gAwIBAgIJAJPzlo1fzzxqMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
BAMMB2t1YmUtY2EwHhcNMTkwMTA0MDYyNzM5WhcNMjEwOTMwMDYyNzM5WjASMRAw
DgYDVQQDDAdrdWJlLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
xnFM6sykmKdmr1Z4DADujeIUZXvz5ajy+xdjpfjrNhnr3002GsthAlN6lWjJP6dn
uVN20M1W/oozF7OzMuSRNO6zLepAG2TrRl/1DZG9EFSN7m65HtxK0DA3RyZc4CuD
j3ADT899yziyaVTvUjR8MxLWHeibgAanZ2Bdc8icd4zt7QCGmy3hYbZZMw6UhSlg
KUT24m6hw+W167knbjG/U64x1Qzik0DCx0fqY26jdJVZN6AnOpAFoIJ6RNIS1X/f
bPa5kzUeTNSbgCW64LS0uOtXBh9uICxaxhHthgKtcOXFn7UrLWVnMfmd0fVi3nV3
Tu5j8swrXlDVX+vaTJvDawIDAQABo1AwTjAdBgNVHQ4EFgQUv7Ta12WZE11NQhjp
FOWxHG+wcZUwHwYDVR0jBBgwFoAUv7Ta12WZE11NQhjpFOWxHG+wcZUwDAYDVR0T
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAsixLWA/28uVQMPfsWAv2cfdJMMCC
vI6Y7uXsLUrNiONG6Ay/pU+6Qc/2kmZJlo0bHLx7P8ncKNHyTPzO1IzMDvvu65TU
FdNAnKkqK90IRfjwY+RJv2J2NEKXmWRCxaJG522Uc0aGaYo6BPfrrYInr1fTh6i+
/ovF4smyCo2bMX1dI5i7TlhAD9qsOA36XVYg+w5w7jXoJTYh14oKsE23pXpTiuSa
h6KdnvrjilwuBpV/SG40TsnITwyeaQFWMwbzJkhbvMgRK2crRruUhcIumIE5TTfL
VgKzJ5oisjgjrZ3oFw871L2HTPFI3C6Xpp6yV6LNpFGOizT8HTLp63CwQw==
-----END CERTIFICATE-----

# vim ca-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ca
namespace: default
data:
ca.pem: |
-----BEGIN CERTIFICATE-----
MIIC9zCCAd+gAwIBAgIJAJPzlo1fzzxqMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
BAMMB2t1YmUtY2EwHhcNMTkwMTA0MDYyNzM5WhcNMjEwOTMwMDYyNzM5WjASMRAw
DgYDVQQDDAdrdWJlLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
xnFM6sykmKdmr1Z4DADujeIUZXvz5ajy+xdjpfjrNhnr3002GsthAlN6lWjJP6dn
uVN20M1W/oozF7OzMuSRNO6zLepAG2TrRl/1DZG9EFSN7m65HtxK0DA3RyZc4CuD
j3ADT899yziyaVTvUjR8MxLWHeibgAanZ2Bdc8icd4zt7QCGmy3hYbZZMw6UhSlg
KUT24m6hw+W167knbjG/U64x1Qzik0DCx0fqY26jdJVZN6AnOpAFoIJ6RNIS1X/f
bPa5kzUeTNSbgCW64LS0uOtXBh9uICxaxhHthgKtcOXFn7UrLWVnMfmd0fVi3nV3
Tu5j8swrXlDVX+vaTJvDawIDAQABo1AwTjAdBgNVHQ4EFgQUv7Ta12WZE11NQhjp
FOWxHG+wcZUwHwYDVR0jBBgwFoAUv7Ta12WZE11NQhjpFOWxHG+wcZUwDAYDVR0T
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAsixLWA/28uVQMPfsWAv2cfdJMMCC
vI6Y7uXsLUrNiONG6Ay/pU+6Qc/2kmZJlo0bHLx7P8ncKNHyTPzO1IzMDvvu65TU
FdNAnKkqK90IRfjwY+RJv2J2NEKXmWRCxaJG522Uc0aGaYo6BPfrrYInr1fTh6i+
/ovF4smyCo2bMX1dI5i7TlhAD9qsOA36XVYg+w5w7jXoJTYh14oKsE23pXpTiuSa
h6KdnvrjilwuBpV/SG40TsnITwyeaQFWMwbzJkhbvMgRK2crRruUhcIumIE5TTfL
VgKzJ5oisjgjrZ3oFw871L2HTPFI3C6Xpp6yV6LNpFGOizT8HTLp63CwQw==
-----END CERTIFICATE-----

# kubectl create -f ca-cm.yaml
configmap/ca created
# kubectl get configmap ca
NAME DATA AGE
ca 1 27d

配置 Login App Configmap

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
# vim loginapp-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: loginapp
namespace: default
data:
config.yaml: |
debug: false
client_id: "login"
client_secret: 4TORGiNV9M54BTk1v7dNuFSaI6hUjfjr
issuer_url: "https://192.168.100.185:32000"
issuer_root_ca: "/etc/ssl/ca.pem"
redirect_url: "https://192.168.100.185:32002/callback"
tls_enabled: true
tls_cert: "/etc/loginapp/tls/tls.crt"
tls_key: "/etc/loginapp/tls/tls.key"
listen: "https://0.0.0.0:5555"
disable_choices: false
extra_scopes: "groups"
name: "Kubernetes Auth"

# kubectl create -f loginapp-cm.yaml
configmap/loginapp created
# kubectl get configmap loginapp
NAME DATA AGE
loginapp 1 24d

部署 Login App

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
# vim loginapp-deploy.yml 
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: loginapp
namespace: default
spec:
replicas: 3
template:
metadata:
labels:
app: loginapp
spec:
containers:
- image: 192.168.100.100/library/login-app:latest
name: loginapp
ports:
- name: https
containerPort: 5555
volumeMounts:
- name: ca
mountPath: /etc/ssl/
- name: config
mountPath: /app/
- name: tls
mountPath: /etc/loginapp/tls
volumes:
- name: ca
configMap:
name: ca
items:
- key: ca.pem
path: ca.pem
- name: config
configMap:
name: loginapp
items:
- key: config.yaml
path: config.yaml
- name: tls
secret:
secretName: loginapp
---
apiVersion: v1
kind: Service
metadata:
name: loginapp
namespace: default
spec:
type: NodePort
ports:
- name: loginapp
port: 5555
protocol: TCP
targetPort: 5555
nodePort: 32002
selector:
app: loginapp

# kubectl create -f loginapp-deploy.yml
# kubectl get pod --show-labels -l app=loginapp
# kubectl get service loginapp

配置 kubernetes

配置K8s Apiserver以使用OpenID Connect 身份验证插件,详见:https://github.com/dexidp/dex/blob/master/Documentation/kubernetes.md

配置 kube-apiserver

1
2
3
4
5
--oidc-issuer-url=https://192.168.100.185:32000
--oidc-client-id=loginapp
--oidc-ca-file=/etc/kubernetes/ssl/dex-ca.pem
--oidc-username-claim=email
--oidc-groups-claim=groups

配置 RBAC

赋予 k8s 组 cluster-admin 角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# vim k8s.yml 
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: ldap-cluster-admin
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: Group
name: k8s
# kubectl create -f k8s.yml
clusterrolebinding.rbac.authorization.k8s.io/ldap-cluster-admin created

赋予 test 组相应权限

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
# vim test.yaml 
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ldap-test
namespace: test
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ldap-test
namespace: test
subjects:
- kind: Group
name: test
apiGroup: ""
roleRef:
kind: Role
name: ldap-test
apiGroup: ""
# kubectl create -f test.yaml
role.rbac.authorization.k8s.io/ldap-test created
rolebinding.rbac.authorization.k8s.io/ldap-test created

登录 Dex 获取 ID Tokens

  • Authentication for clients : login(login 对应之前 dex 和 Login App Configmap 的配置)
    dex01
  • 点击 “Request Token” 进行登录
  • 点击 “Log in with LDAP”
  • 输入 Username 和 Password ,点击 “Login” 登录生成 id-token
    dex03
  • 根据提示将文件复制至~/.kube/config
    dex04

ID Tokens 简单解释

id-token 实际上有三个部分,每个部分都是Base64编码的JSON,以”.”来分割。第一部分提供令牌的元数据。第二部分提供身份信息,这称为有效负载。第三部分是签名,用于验证令牌是否由可信方发出。

安装包以使用 jq 命令

1
# yum -y install jq

解码第一部分

1
2
3
4
5
# echo eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg1MzQ0ZTZlYjk4N2Y5ODA2MjRhODY2MTM2ZWFmOTFmNjFkNDNlYWEifQ | base64 -d | jq
{
"alg": "RS256",
"kid": "85344e6eb987f980624a866136eaf91f61d43eaa"
}

解码第二部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# echo eyJpc3MiOiJodHRwczovLzE5Mi4xNjguMTAwLjE4NTozMjAwMCIsInN1YiI6IkNnUjNZVzVuRWdSc1pHRnciLCJhdWQiOiJsb2dpbiIsImV4cCI6MTU0Nzc5MTAzMCwiaWF0IjoxNTQ3NzA0NjMwLCJhenAiOiJsb2dpbiIsImF0X2hhc2giOiJvTzBLcTBTYy1qdE9ybVpUbTJpbG5RIiwiZW1haWwiOiJ3YW5nemhpamlhbnNkQHFxLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJncm91cHMiOlsiazhzIl0sIm5hbWUiOiJ3YW5nIn0 | base64 -d | jq
{
"iss": "https://192.168.100.185:32000",
"sub": "CgR3YW5nEgRsZGFw",
"aud": "login",
"exp": 1547791030,
"iat": 1547704630,
"azp": "login",
"at_hash": "oO0Kq0Sc-jtOrmZTm2ilnQ",
"email": "wangzhijiansd@qq.com",
"email_verified": true,
"groups": [
"k8s"
],
"name": "wang"
}

检查用户权限

根据之前配置的 RBAC 来测试用户拥有的权限

测试用户 wang

1
2
3
4
5
# kubectl get nodes --user=wang
NAME STATUS ROLES AGE VERSION
node01 Ready <none> 174d v1.13.0
node02 Ready <none> 174d v1.13.0
node03 Ready <none> 174d v1.13.0

测试用户 zhi

1
2
3
4
5
6
# kubectl get nodes --user=zhi
Error from server (Forbidden): nodes is forbidden: User "zhijiansd@163.com" cannot list resource "nodes" in API group "" at the cluster scope

# kubectl get pod -n test --user=zhi
NAME READY STATUS RESTARTS AGE
test-nginx-75677f8b58-p8w6d 1/1 Running 0 35h

最后要感谢如下文章及其作者:

ZhiJian wechat
欢迎您扫一扫上面的二维码,订阅我的微信公众号!
-------------本文结束,感谢您的阅读-------------