暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Kubernetes通过Dex集成企业外部认证系统

int32bit 2021-03-12
1819

图片来源:https://itnext.io/implementing-ldap-authentication-for-kubernetes-732178ec2155

1 Dex简介

在之前的一篇文章浅聊Kubernetes的各种认证策略以及适用场景[1]介绍了Kubernetes的各种认证方式以及适用场景,并通过例子演示了通过OIDC集成Keyclock认证以及Webhook集成OpenStack Keystone认证。

Kubernetes通过OIDC对接企业已有的统一身份认证系统是企业推荐的认证集成方式,方便进行用户的统一管理和健全,实现Kubernetes的SSO单点登录。

但是企业的认证系统可能并不支持OIDC认证协议,企业大多数认证系统都是基于LDAP协议的,在此之前为了支持LDAP认证,企业需要自研开发Webhook,参考Implementing LDAP authentication for Kubernetes[2]

而CoreOS开源的身份认证服务项目Dex很好地解决了这个问题,它实现了一个标准的OpenID Connect的身份服务,但相对KeyCloak,Dex并没有实现复杂的认证功能,而主要强大之处在于支持连接后端多个外部认证服务器,从而使Kubernetes只需要与Dex交互,而不需要关心后端认证服务器协议。

可以把Dex当作一个轻量级的认证的代理入口(portal),应用APP只需要通过与Dex交互,由Dex负责与后端的上游认证服务器交互,从而屏蔽了后端认证服务器的协议差异。

dex-flow

目前Dex已经实现对接的外部认证系统如下:

  • LDAP
  • GitHub
  • SAML 2.0
  • GitLab
  • OpenID Connect
  • Google
  • LinkedIn
  • Microsoft
  • AuthProxy
  • Bitbucket Cloud
  • OpenShift
  • Atlassian Crowd
  • Gitea

当然其实实现了LDAP基本就实现一半以上企业的认证对接问题。

本文接下来一步步介绍如何通过Dex配置实现Kubernetes集成Github以及LDAP认证,后面也会介绍如何通过Gangway自动生成kubeconfig文件。

2 Dex部署

毫无疑问用户认证时必须通过https连接,由于我们仅做测试,因此使用自签证书代替,实际生产时应该使用企业证书。

Dex提供了一个样例gencert.sh[2]脚本实现自动生成证书的脚本,签发证书需要关联域名,因此需要配置alt_names

为了便于测试,可以把IP也添加到alt_names
中,否则无法通过IP访问,最后脚本如下:

#!/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.int32bit.me # 修改为测试域名
IP.1 = 192.168.193.172 # 修改为IP
EOF

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

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

生成的证书会放到ssl
目录中,Dex将会使用,我们把证书和私钥提前保存到Kubernetes Secret中:

kubectl create ns dex
kubectl create secret tls -n dex dex.int32bit.me.tls \
  --cert=ssl/cert.pem \
  --key=ssl/key.pem

Dex本质就是一个认证代理,Dex主要由前端的Clients以及后端的Connectors组成,Dex其本身并没有实现复杂的认证功能,而是通过Connectors级联上游的认证服务器(Upstream IDP)实现认证,如LADP、SAML、OpenShift、Github等。

当然最简单的静态用户名和密码校验是实现了的,这里我们暂时不使用Connectors,而是使用其自身实现的最简单的静态用户名密码认证方式。

用户列表可以在初始化Configmap配置中指定,后续也可以通过Kubernetes CRD创建,密码需要通过bcrypt算法[3]加密存储。

通过Python的bcrypt库可以实现文本的bcrypt加密:

#!/usr/bin/python3
import bcrypt
import sys
salt = bcrypt.gensalt(rounds=15)
for pwd in sys.argv[1:]:
    crypt_pwd = bcrypt.hashpw(pwd.encode(), salt).decode()
    print(f"{pwd}{crypt_pwd}")

Dex配置静态用户列表如下:

enablePasswordDB: true
staticPasswords:
- email: "admin@int32bit.me"
  hash: "$2b$15$9QbV9Vky5.MwFHY9ocae0OcX9lVlA7iaLP/VVoYekXodRT815sqzC"
  username: "admin"
  userID: "a7928eec-65e4-4397-b3b5-640cf62b54f2"

配置完用户后,接下来配置clients,client也可以在初始化Configmap配置中指定,后续也可以通过Kubernetes CRD创建:

staticClients:
- id: kubernetes
  redirectURIs:
  - 'http://localhost:8000'
  name: 'kubernetes'
  secret: "ZXhhbXBsZS1hcHAtc2VjcmV0"

其中client_id
kubernetes
client_secret
ZXhhbXBsZS1hcHAtc2VjcmV0
,这里均为自定义参数,后面会用到。

redirectURIs
为后续需要向Dex发起认证的可信任应用callback列表,由于我们应用尚未部署,这里随便写的一个demo(注:后面的kubelogin会使用这个URL)。

最后Dex的Configmap为:

kind: ConfigMap
apiVersion: v1
metadata:
  name: dex
  namespace: dex
data:
  config.yaml: |
    issuer: https://192.168.193.172: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
    oauth2:
      skipApprovalScreen: true
    staticClients:
    - id: kubernetes
      redirectURIs:
      - 'http://localhost:8000'
      name: 'kubernetes'
      secret: ZXhhbXBsZS1hcHAtc2VjcmV0
    enablePasswordDB: true
    staticPasswords:
    - email: "admin@int32bit.me"
      hash: "$2b$15$9QbV9Vky5.MwFHY9ocae0OcX9lVlA7iaLP/VVoYekXodRT815sqzC"
      username: "admin"
      userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"

配置完后,按照官方样例yaml部署Dex即可:

wget https://raw.githubusercontent.com/dexidp/dex/master/examples/k8s/dex.yaml
# 按照如上替换configmap dex内容
kubectl apply -f dex.yaml

注意Dex会用到前面配置的证书,通过Secret挂载,确保前面已经创建。

此时Dex服务通过Service NodePort 32000暴露。

(可选)如果通过前面指定的alt_names
域名访问,由于测试场景这个域名无法通过公网解析,因此需要修改本地/etc/hosts
文件:

192.168.193.172 dex.int32bit.me

(可选)如上只是实现了本地通过静态域名访问,容器内部也需要访问这个地址,因此需要实现容器能够解析这个域名,可以修改CoreDNS配置,增加hosts静态解析:

kubectl edit configmaps coredns -n kube-system
# 在cache 30前面增加如下hosts配置
hosts {
    192.168.193.172 dex.int32bit.me
    fallthrough
}

配置完后可以在容器中验证DNS是否能正常解析:

# kubectl exec -t -i dex-68844bbd4c-lxcbd -- nslookup dex.int32bit.me
Server:         10.96.0.10
Address:        10.96.0.10:53

Name:   dex.int32bit.me
Address: 192.168.193.172

如上有关域名的相关配置仅通过域名访问时需要,如果直接通过IP访问并且配置了证书的alt_names
参数可以忽略。

Dex部署完后需要修改apiserver配置OIDC服务地址,如果使用kubeadm部署的Kubernetes集群,需要修改/etc/kubernetes/manifests/kube-apiserver.yaml
文件,在kube-apiserver启动命令行中增加如下参数:

- --oidc-issuer-url=https://192.168.193.172:32000
- --oidc-client-id=kubernetes
- --oidc-ca-file=/etc/kubernetes/ssl/ca.pem
- --oidc-username-claim=email
- --oidc-groups-claim=groups

其中--oidc-issuer-url
为Dex的地址,可以是IP也可以是域名,如果配置域名需要按照前面的方法配置静态解析,--oidc-client-id
为Dex部署时初始化的client id。Kubernetes中有User和Group的概念,--oidc-username-claim
以及--oidc-groups-claim
两个参数分别指定ID Token中字段对应的User和Group。

最后一个参数--oidc-ca-file
很重要,由于Dex使用的自签证书,为了让Kubernetes信任这个证书链必须指定Dex CA根证书路径,这里我们通过hostpath方式挂载过去:

cp -r ssl /etc/kubernetes/

containers:
- image: k8s.gcr.io/kube-apiserver:v1.20.1
  volumeMounts:
  ...
  - mountPath: /etc/kubernetes/ssl
    name: dex
    readOnly: true
volumes:
...
- hostPath:
    path: /etc/kubernetes/ssl
    type: DirectoryOrCreate
  name: dex

这里特别需要注意的是,这里由于会修改/etc/kubernetes/manifests/kube-apiserver.yaml
文件,很多人自然会想到修改前先备份一下,但是一定需要注意不要把备份文件放到/etc/kubernetes/manifests
目录,一定要挪到其它路径位置,否则Kubernetes可能会运行不正常(认证时会报invalid bearer token
错误#issues/74[4])。

cp /etc/kubernetes/manifests/kube-apiserver.yaml ~/kube_backup/

配置完后重启Kubernetes Master节点的kubelet服务:

systemctl restart kubelet

为了验证Dex登录,使用kubelogin[5]工具进行Dex登录:

./kubelogin setup --insecure-skip-tls-verify \
  --oidc-issuer-url=https://192.168.193.172:32000 \
  --oidc-client-id=kubernetes \
  --oidc-client-secret=ZXhhbXBsZS1hcHAtc2VjcmV0

运行如上命令会自动调用本地默认浏览器打开Dex登录页面:

dex login

输入用户邮箱(admin@int32bit.me
)和密码(123456
)后:

会指引你如何配置kubeconfig:

kubelogin

其中红框的内容我们前面已经配置过了,可以忽略。

3 集成Github认证以及LADP

3.1 Github

根据前面的介绍Dex主要由后端的Connectors和前端的Clients两部分组成,其中Connectors对接上游认证服务器(IDP),如LADP、SAML、OpenShift、Github等。

前面的例子使用了Dex的本地静态用户名密码认证,这不是Dex推荐的认证方式,Dex最强大的功能在于可以级联其他认证系统IDP,如图所示,

k8s-dex+github+gangway

Dex相当于是一个认证代理,通过标准的OIDC接口为应用提供认证服务,但是它并不是自己去实现认证,而是通过调用后端的认证系统,后端的认证每一个IDP为一个Connector。

这里以Github为例,介绍如何配置Dex的Connector,从而实现Kubernetes对接Github认证。

相对Github为IDP,此时Dex相当于是Github的一个Client,Dex需要向Github索取用户信息,必须由Github授权。

因此在Github上先要登记备案从而信任这个Client,在https://github.com/settings/developers[6]页面上创建一个新的OAuth APP,

new github oauth app

其中Authorization callback URL
必须填写Dex的地址,创建完后会自动分配client id,点击"Generate a new client secret"生产client secret:

client secret

把生成的client id和client secret存储到Kubernetes Secret中:

#!/bin/bash
GITHUB_CLIENT_ID=905147246857f9820d7e
GITHUB_CLIENT_SECRET=*****
kubectl create secret \
    generic github-client \
    --from-literal=client-id=$GITHUB_CLIENT_ID \
    --from-literal=client-secret=$GITHUB_CLIENT_SECRET

修改Dex configmap,增加如下内容:

 connectors:
 - type: github
   id: github
   name: GitHub
   config:
     clientID: $GITHUB_CLIENT_ID
     clientSecret: $GITHUB_CLIENT_SECRET
     redirectURI: https://192.168.193.172:32000/callback

其中redirectURI
为Dex地址,必须和Github创建OAuth APP填写的一致,clientID
clientSecret
为刚刚生成的Github client id和client secret,为了安全起见,我们通过环境变量传递,环境变量引用Secret,修改Dex Deployment:

env:
- name: GITHUB_CLIENT_ID
  valueFrom:
    secretKeyRef:
      name: github-client
      key: client-id
- name: GITHUB_CLIENT_SECRET
  valueFrom:
    secretKeyRef:
      name: github-client
      key: client-secret

修改完后重新启动Dex,再次运行kubelogin:

# ./kubelogin setup --insecure-skip-tls-verify \
  --oidc-issuer-url=https://192.168.193.172:32000 \
  --oidc-client-id=kubernetes \
  --oidc-client-secret=ZXhhbXBsZS1hcHAtc2VjcmV0
authentication in progress...

此时Dex登录页会多出一个登录方式:

login dex

选择Log in with Github

此时如果没有登录过Github会弹出Github的登录页面,如果已经登录过Github则无需登录。

最后Github会弹出页面询问你是否同意Dex登录:

authorized to dex

点击同意后即完成认证,按照前面的方法配置kubeconfig即可。

3.1 LDAP

为了验证LDAP,本地部署了一个测试的openldap,admin用户如下:

# int32bit.me
dn: dc=int32bit,dc=me
objectClass: top
objectClass: dcObject
objectClass: organization
o: int32bit.me
dc: int32bit

# admin, int32bit.me
dn: cn=admin,dc=int32bit,dc=me
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: e1N...lhMdFNnUnI=

创建了用户dn: cn=int32bit_admin,dc=int32bit,dc=me
:

# int32bit_admin, int32bit.me
dn: cn=int32bit_admin,dc=int32bit,dc=me
objectClass: top
objectClass: inetOrgPerson
givenName: int32bit_admin
sn: int32bit_admin
ou: dev
cn: int32bit_admin
userPassword:: aW...s==
mail: int32bit_admin@int32bit.me

# Groups, int32bit.me
dn: ou=Groups,dc=int32bit,dc=me
objectClass: organizationalUnit
ou: Groups

# admin_group, Groups, int32bit.me
dn: cn=admin_group,ou=Groups,dc=int32bit,dc=me
objectClass: groupOfNames
cn: admin_group
member: cn=int32bit_admin,dc=int32bit,dc=me

由如上ldif输出,用户int32bit_admin
隶属于admin_group
组。

增加新的ldap connecor:

connectors:
- type: github
  ...
- type: ldap
  id: ldap
  name: OpenLDAP
  config:
    host: 192.168.193.172:389
    insecureNoSSL: true
    redirectURI: https://192.168.193.172:32000/callback
    bindDN: cn=admin,dc=int32bit,dc=me #管理员的DN
    bindPW: **** # 管理员密码
    usernamePrompt: "Email Mail"
    userSearch:
      baseDN: dc=int32bit,dc=me
      filter: "(objectClass=person)"
      username: mail
      idAttr: DN
      emailAttr: mail
      nameAttr: cn
    groupSearch:
      baseDN: ou=Groups,dc=int32bit,dc=me
      filter: "(objectClass=groupOfNames)"
      userMatchers:
      - userAttr: DN
        groupAttr: member
      nameAttr: cn

修改完后重新启动Dex,再次运行kubelogin,发现多了一个LDAP登录方式:

login with ldap

4 使用Gangway自动生成kubeconfig文件

前面通过kubelogin完成Dex登录认证,但是每次需要手动配置kubeconfig,并且需要在客户端本地安装kubelogin,有点麻烦。

开源项目Gangway解决了这个问题,实现kubeconfig自动生成。通过复用同一个client id实现单点登录,共享AccessToken,从而只要在Gangway登录认证即相当于完成了Kubernetes认证。

这里Gangway也相当于是Dex的一个client,Dex使用的是自签证书,因此也需要把Dex的CA证书配置到Gangway的信任证书列表中,先把CA存到ConfigMap中:

kubectl create configmap dex-cacert --from-file=ssl/ca.pem

生成一个随机的sessionkey并保存到Secret中:

kubectl create secret generic gangway-key \
  --from-literal=sessionkey=$(openssl rand -base64 32)

获取Dex认证地址:

curl -k https://192.168.193.172:32000/.well-known/openid-configuration

dex openid config

Gangway配置如下:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: gangway
  namespace: dex
data:
  gangway.yaml: |
    host: "0.0.0.0"
    port: 8080
    clusterName: "int32bit-gangway-cluster"
    serveTLS: false
    authorizeURL: "https:/192.168.193.172:32000/auth"
    tokenURL: "https://192.168.193.172:32000/token"
    redirectURL: "http://192.168.193.172:32001/callback"
    clientID: "kubernetes"
    clientSecret: "ZXhhbXBsZS1hcHAtc2VjcmV0"
    usernameClaim: "name"
    apiServerURL: "https://192.168.193.172:6443"
    trustedCAPath: "/cacerts/rootca.crt/ca.pem"

其中authorizeURL
以及tokenURL
根据如上Dex的配置填入,redirectURL
为Gangway的地址,目前Gangway其实还没有部署,提前分配了一个NodePort 32001,clientID
以及clientSecret
为之前Dex配置的。

前面Dex的静态clients redirectURI只注册了”http://localhost:8000“,需要增加Gangway的地址,配置如下:

staticClients:
- id: kubernetes
  redirectURIs:
  - 'http://192.168.193.172:32001/callback'
  - 'http://localhost:8000'
  name: 'kubernetes'
  secret: ZXhhbXBsZS1hcHAtc2VjcmV0

更新Dex Configmap后需要重新启动Dex服务。

部署Gangway下载官方的Deployment yaml即可:

wget https://raw.githubusercontent.com/heptiolabs/gangway/master/docs/yaml/03-deployment.yaml

注意官方yaml会为Gangwa创建一个新的Namespace,所有资源会创建在这个新Namespace中。为了方便,我把Gangway与Dex放到了同一个namespace dex,因此需要对应修改yaml文件的namespace字段。

Dex使用的是自签证书,刚刚我们已经把Dex证书保存到ConfigMap中了,我们需要把Dex CA证书通过Configmap挂载过去:

...
volumeMounts:
- name: gangway
  mountPath: /gangway/
- name: dex-cacert
  mountPath: /cacerts/rootca.crt
...
volumes:
- name: gangway
  configMap:
    name: gangway
- name: dex-cacert
  configMap:
    name: dex-cacert

Service我们使用NodePort类型,并指定NodePort为32001,与前面配置保持一致:

---
kind: Service
apiVersion: v1
metadata:
  name: gangway-service
  namespace: dex
  labels:
    app: gangway
spec:
  type: NodePort
  ports:
    - name: "http"
      protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 32001
  selector:
    app: gangway

部署完成后,我们就可以通过NodePort访问Gangway服务了:

gangway index

点击SIGN IN跳转到Dex登录页面:

login with ldap

使用Github登录后,跳回Gangway页面:

gangway kubeconfig

这个页面会告诉你怎么下载kubectl命令行工具以及下载kubeconfig文件。

--auth-provider-arg
参数会有本次申请的id-token,格式为JWT,JWT解码如下(在线JWT解码[7]):

jwt decode

根据前面kube-apiserver的配置,对应User为int32bit_admin@int32bit.me
,Group为admin_group

当然通过Dex只是完成了认证,默认没有任何权限,我们可以通过clusterrolebinding或者rolebing授权,前面我们配置的时候使用email映射Kubernetes的User,因此使用邮箱作为Kubernetes授权User对象,如果要授权cluster_admin的权限,可配置如下clusterrolebinging:

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: steve-admin
subjects:
- kind: User
  name: int32bit_admin@int32bit.me # 填Github的邮箱
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

kubectl gangway

5 总结

本文首先介绍了Dex项目的背景和意义,然后详细介绍了Dex的配置部署过程以及集成Github和LDAP认证的方法,最后通过Gangway实现了kubeconfig文件自动生成和配置。

轻量级Dex认证服务器作为企业对接统一身份认证系统方案,不仅支持常规的LADP协议,还支持现有的基于OAuth的认证方式,很好的解决了企业容器平台的认证统一管理问题。

当然认证只是第一步,通过不同的企业角色和项目完成容器平台的授权是企业需要解决的另一个问题,配合Dex的Group信息可以实现基于group的rolebanding关联,从而实现不同角色的授权。

参考资料[1] 浅聊Kubernetes的各种认证策略以及适用场景: https://int32bit.sh/2019/12/16/%E6%B5%85%E8%81%8AKubernetes%E7%9A%84%E5%90%84%E7%A7%8D%E8%AE%A4%E8%AF%81%E7%AD%96%E7%95%A5%E4%BB%A5%E5%8F%8A%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF/
[2] Implementing LDAP authentication for Kubernetes: https://itnext.io/implementing-ldap-authentication-for-kubernetes-732178ec2155
[3] 样例gencert.sh: https://raw.githubusercontent.com/dexidp/dex/master/examples/k8s/gencert.sh
[4] bcrypt算法: https://zh.wikipedia.org/wiki/Bcrypt
[5] #issues/74: https://github.com/heptiolabs/gangway/issues/74
[6] kubelogin: https://github.com/int128/kubelogin
[7] https://github.com/settings/developers: https://github.com/settings/developers
[8] 在线JWT解码: https://www.jstoolset.com/jwt

文章转载自int32bit,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论