配置证书

由于之后要将keycloak接入k8s,所以Keycloak提供的服务必须也是HTTPS的,需要先生成crt和key文件

不过之前已经配置过Dex的HTTPS,所以可以直接将dex.crtdex.key拿来用,这里直接将两个文件设置为kubectlsecret资源:

kubectl create secret generic keycloak-tls-secret --from-file=tls.crt=./dex.crt   --from-file=tls.key=./dex.key -n keycloak

如此配置后,只要注明secret名称:keycloak-tls-secrettls.crttls.key可以随时被Pod挂载至目录中供引用

安装、运行Keycloak

首先获取Keycloak的部署配置文件,执行:

wget https://gh-proxy.com/raw.githubusercontent.com/keycloak/keycloak-quickstarts/refs/heads/main/kubernetes/keycloak.yaml

接着对文件作出修改:

  1. 使用volumesvolumeMountskeycloak-tls-secret挂载至容器目录/etc/x509/https

    volumes:
      - name: keycloak-tls
        secret:
          secretName: keycloak-tls-secret
    ...
    volumeMounts:
       - name: keycloak-tls
         mountPath: /etc/x509/https
         readOnly: true
    
  2. 设置环境变量,开启HTTPS并设置端口8443,引用挂载的证书和秘钥文件

    env:
      - name: KC_HTTPS_CERTIFICATE_FILE
        value: /etc/x509/https/tls.crt
      - name: KC_HTTPS_CERTIFICATE_KEY_FILE
        value: /etc/x509/https/tls.key
      - name: KC_HTTPS_PORT
        value: "8443"
      - name: KC_HTTP_ENABLED
        value: "false"
    ...
    ports:
      - name: https
        containerPort: 8443
    
  3. 设置NodePort将服务暴露,可以映射为:8443 -> 30443

    apiVersion: v1
    kind: Service
    metadata:
      name: keycloak
      labels:
        app: keycloak
    spec:
      ports:
        - protocol: TCP
          port: 8443
          targetPort: 8443
          name: https
          nodePort: 30443
      selector:
        app: keycloak
      type: NodePort
    

最终的keycloak.yaml文件:

apiVersion: v1
kind: Service
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  ports:
    - protocol: TCP
      port: 8443
      targetPort: 8443
      name: https
      nodePort: 30443
  selector:
    app: keycloak
  type: NodePort
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: keycloak
  name: keycloak-discovery
spec:
  selector:
    app: keycloak
  publishNotReadyAddresses: true
  clusterIP: None
  type: ClusterIP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  serviceName: keycloak-discovery
  replicas: 1
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
        - name: keycloak
          image: quay.io/keycloak/keycloak:26.2.5
          args: ["start"]
          env:
            - name: KC_HTTPS_CERTIFICATE_FILE
              value: /etc/x509/https/tls.crt
            - name: KC_HTTPS_CERTIFICATE_KEY_FILE
              value: /etc/x509/https/tls.key
            - name: KC_HTTPS_PORT
              value: "8443"
            - name: KC_HTTP_ENABLED
              value: "false"
            - name: KC_BOOTSTRAP_ADMIN_USERNAME
              value: "admin"
            - name: KC_BOOTSTRAP_ADMIN_PASSWORD
              value: "admin"
            - name: KC_PROXY_HEADERS
              value: "xforwarded"
            - name: KC_HOSTNAME_STRICT
              value: "false"
            - name: KC_HEALTH_ENABLED
              value: "true"
            - name: 'KC_CACHE'
              value: 'ispn'
            - name: 'KC_CACHE_STACK'
              value: 'kubernetes'
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: JAVA_OPTS_APPEND
              value: '-Djgroups.dns.query="keycloak-discovery" -Djgroups.bind.address=$(POD_IP)'
            - name: 'KC_DB_URL_DATABASE'
              value: 'keycloak'
            - name: 'KC_DB_URL_HOST'
              value: 'postgres'
            - name: 'KC_DB'
              value: 'postgres'
            - name: 'KC_DB_PASSWORD'
              value: 'keycloak'
            - name: 'KC_DB_USERNAME'
              value: 'keycloak'
          ports:
            - name: https
              containerPort: 8443
          volumeMounts:
            - name: keycloak-tls
              mountPath: /etc/x509/https
              readOnly: true
          startupProbe:
            httpGet:
              scheme: HTTPS
              path: /health/started
              port: 9000
          readinessProbe:
            httpGet:
              scheme: HTTPS
              path: /health/ready
              port: 9000
          livenessProbe:
            httpGet:
              scheme: HTTPS
              path: /health/live
              port: 9000
          resources:
            limits:
              cpu: 2000m
              memory: 2000Mi
            requests:
              cpu: 500m
              memory: 1700Mi
      volumes:
        - name: keycloak-tls
          secret:
            secretName: keycloak-tls-secret
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  labels:
    app: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          # 使用国内镜像源
          image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/postgres:17
          env:
            - name: POSTGRES_USER
              value: "keycloak"
            - name: POSTGRES_PASSWORD
              value: "keycloak"
            - name: POSTGRES_DB
              value: "keycloak"
            - name: POSTGRES_LOG_STATEMENT
              value: "all"
          ports:
            - name: postgres
              containerPort: 5432
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data
      volumes:
        - name: postgres-data
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: postgres
  name: postgres
spec:
  selector:
    app: postgres
  ports:
    - protocol: TCP
      port: 5432
      targetPort: 5432
  type: ClusterIP

执行kubectl apply -f keycloak.yaml -n keycloak部署即可

keycloakpostgres的Pod全部Running后,访问https://192.168.92.128:30443即可看到前端页面(已设置语言本地化):

登录页

输入在配置中设置的账户名admin和密码admin进入管理页面:

管理页

配置Keycloak

成功访问Keycloak前端页面后,需要对其进行各项配置,以将其连接至K8s的OIDC认证

创建一个专用于进行K8s认证的领域(Realm),命名为k8s-auth(Keycloak官方并不推荐用户直接在master领域进行各项应用设置),创建后点击切换到该领域中,之后的各项管理设置操作都将在这个领域中进行

导入LDAP用户

在较新版本的Keycloak侧边导航栏中的【配置】主标签下存在【用户联盟】(User Federation)子标签,点击后选择添加LDAP供应商,即可开始将LDAP服务器中存储的用户信息导入至Keycloak中

属性
连接地址 ldap://192.168.92.128:389
绑定DN cn=admin,dc=example,dc=com
绑定凭据 647252
编辑模式 WRITABLE
用户DN ou=People,dc=example,dc=com
用户名LDAP属性 cn
RDN LDAP属性 cn
UUID LDAP属性 cn
用户对象类 inetOrgPerson
  • 这里的【绑定DN】和【绑定凭据】分别指LDAP服务器中管理员用户的DN和密码
  • 原本的LDAP数据库中不存在UUID等字段,因此为了方便,这里将用户名、RDN、UUID都设置为cn

在设置连接参数时Keycloak提供了【测试连接】、【测试认证】的功能,可以随时查看填入的参数是否正确

完成后,进行LDAP到Keycloak用户属性映射关系的设置,根据LDAP中的字段设置即可,例如username字段设置为cn,亦即将LDAP数据库中的cn映射至Keycloak用户的username属性

因为Keycloak的用户默认没有groups属性,因此需要在映射处新增一个映射器

用户groups映射

全部完成后,可以在【用户管理】标签页看到导入的LDAP用户:

导入用户成功

点击用户,在详细信息的【群组管理】中也可以确认用户所在群组已经被正确读取:

用户群组信息

创建连接的客户端

为了将Keycloak连接至K8s,需要创建一个客户端用于管理连接,创建时,设置:

属性
客户端类型 OpenID Connect
客户端ID keycloak-k8s-auth
客户端认证
认证流程 标准流程、直接访问授权
根网址 https://192.168.92.128:30443
主页URL https://192.168.92.128:30443
有效的重定向URI http://localhost:8000

这里的认证流程原本只有标准流程,开放【直接访问授权】是为了在之后的测试过程中可以直接通过用户名-密码的形式获取token,方便开发

客户端创建完成后,从理论上说,只要再将K8s处的API-Server配置好,就可以实现OIDC认证了

但需要注意一点,我们先前在集群中配置的是根据用户所在用户组确定权限的RBAC策略,而Keycloak进行OIDC认证时返回的JWT默认不包含groups字段,这就需要在客户端设置中添加必要的JWT字段

点击进入客户端后,进入【客户端范围】,选择“此客户端的专用范围和映射器”对应的范围,然后根据配置添加映射器

客户端groups映射

注意这里最好关闭【Full group path】,否则JWT中的groups字段将成为/g-admin的形式

另外还可以增加name映射等,根据实际需要自由添加

至此,有关Keycloak的配置告一段落

  • 进入【领域设置】,在页面最下方找到【OpenID Endpoint Configuration】点击进入,可以查看此领域的issuerauthorization_endpointtoken_endpoint等属性
  • 进入【客户端】、【客户端详情】、【凭证】处,可以查看并复制客户端密码client_secret

可以在终端运行:

curl -ks -X POST <token_endpoint> -d grant_type=password -d client_id=keycloak-k8s-auth -d username=jack -d password=123456 -d scope=openid -d client_secret=<client_secret>

即通过用户名-密码的方式得到Keycloak客户端给出的refresh_tokenid_token等身份参数(这就是开放【直接访问授权】的目的),如果将获得的各参数填入身份文件config中,即可完成身份的认证

但很明显现在还不行,因为当前还没有配置API-Server,集群并不识别Keycloak给出的身份参数

配置API-Server

API-Server的配置与之前Dex的十分相似,只需要修改oidc-issuer-urloidc-client-id即可,这两个字段都已经在之前的操作中获取过,直接填入即可

注意之前的Dex配置了oidc-username-claim=name,所以确实需要在客户端中新增一个映射字段name

API-Server配置完成并成功重启后,Keycloak已经能够正常接入K8s集群并提供OIDC认证服务

测试

将Keycloak先前提供的refresh_tokenid_token等粘贴至config文件、并正确设置文件中的各项参数后,赶在失效时限之前执行kubectl get nodes成功,而执行kubectl get pods -n keycloak失败,失败原因为无权限:

ficn@master:~$ kubectl get no
NAME     STATUS   ROLES           AGE     VERSION
master   Ready    control-plane   54d     v1.28.2
node01   Ready    <none>          5d22h   v1.28.2
ficn@master:~$ kubectl get pods -n keycloak
Error from server (Forbidden): pods is forbidden: User "https://192.168.92.128:30443/realms/k8s-auth#jack Bai" cannot list resource "pods" in API group "" in the namespace "keycloak"

引入kubelogin

当前的Keycloak已经能为集群提供认证服务,但不同于Dex,Keycloak并不会为用户生成一段身份配置代码,当前仅能通过命令得到必要身份参数,然后手动将参数填入身份文件,略显不便

因此可以考虑引入kubelogin,它可以作为kubectl插件发挥作用:在用户执行kubectl指令时自动唤起浏览器,并重定向至Keycloak认证服务,用户在浏览器完成认证后,终端执行的kubectl指令即可成功运行;本次认证后的身份数据经过一段时间后自动过期,过期后的下次kubectl指令将再次唤起浏览器进行认证

安装kubelogin

可以直接下载Github的Release版本并手动解压:

ficn@master:~$ curl -Lo kubelogin.zip https://github.com/int128/kubelogin/releases/download/v1.32.4/kubelogin_linux_amd64.zip
ficn@master:~$ unzip kubelogin.zip

解压完成后,将kubelogin移动到/usr/local/bin/sudo mv kubelogin /usr/local/bin/,然后检查运行:

ficn@master:~$ kubelogin --version
kubelogin version v1.32.4

config格式修改

当前的config是通过auth-provider的方式配合OIDC完成认证的,但如果要引入kubelogin,这种方式就过时了,需要使用exec配置来启动kubelogin插件:

...
users:
- name: jack-my-cluster
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      command: kubelogin
      args:
        - get-token
        - --oidc-issuer-url=<issuer>
        - --oidc-client-id=<client-id>
        - --oidc-client-secret=<client-secret>
        - --token-cache-dir=~/.kube/cache/kubelogin
        - --insecure-skip-tls-verify
...

可以看到,新版的config写法省略了先前的refresh-tokenid-token等硬编码参数,这些参数实际上是交由kubelogin去管理了,此时只需要关心OIDC如何提供即可

由于使用了自签名证书,系统默认并不信任,需要配置--insecure-skip-tls-verify参数,或者将证书加入操作系统信任列表

测试

以上全部配置完成后,执行kubectl get nodes命令,将自动唤起浏览器进行认证:

kubelogin唤起

输入用户名jack和密码123456完成登录后回到终端,可以看到成功获取节点信息:

ficn@master:~$ kubectl get no
NAME     STATUS   ROLES           AGE     VERSION
master   Ready    control-plane   54d     v1.28.2
node01   Ready    <none>          5d22h   v1.28.2

参考

【官方】在 Kubernetes 上开始使用 Keycloak

在 Kubernetes 中使用 Keycloak OIDC Provider 对用户进行身份验证