Kubernetes 与 OpenID 集成 SSO 登录测试_python



文多图预警

简介



本文是《Kubernetes 权限管理  上 》中,OpenID Connect Tokens 部分的展开。

利用之前 Kind 搭建的 K8s 环境和公司的已有的 IdP,测试 OpenID SSO 用户在 K8s 中的授权鉴权过程。

在上篇文章中我们提到过,K8s 利用第三方 IdP 进行用户验证时,是利用 IdP 返回的 ID token 做为 bear token 进行用户验证和鉴权。

所以我们首先要获得用户的有效 ID token,也就是下图中的第一步“Login to IdP”中的 ID token

Kubernetes 与 OpenID 集成 SSO 登录测试_json_02

直接获取用户 ID token 的过程比较复杂,这里我们会借助 github 上的 curityio/example-python-openid-connect-client 项目来帮我们获取 ID token。

本文以公司现有的 IdP 进行集成测试,OpenID 配置上与其它 IdP 大体相同,不同之处会在文中指出。

本文把整个验证过程拆解成了手工执行的几个步骤,我们可以通过这种直观的方式了解验证过程。

目录



  • - 环境(配置)

  • - 实战步骤

  •   1. 配置 example-python-openid-connect-client 环境

  •      - 从 github 下载项目

  •      - 创建镜像

  •      - 运行容器

  •        - 修改 app.py(可选)

  •      - 获取 ID token

  •   2. 配置 K8s Apiserver 支持 OpenID

  •      - 导出 IdP 公共证书

  •      - 配置 Apiserver Flag

  •   3. 通过 ID token 连接 K8s

  •      - 常见错误

  •   4. 在 K8s 中为 SSO 用户增加权限

  • - 总结

  • - 资源下载

  • - 后记

环境(配置)



  • Win10 + WSL2
  • Docker desktop 3.3.3 或以上版本
  • kind
  • 支持 OpenId 的 IdP 和有效的 SSO
  • curityio/example-python-openid-connect-client 环境
  • git

实战步骤


1. 配置 example-python-openid-connect-client 环境

curityio/example-python-openid-connect-client 项目是一个展示 OpenID Connect 工作流程的 web 应用,我们这里利用它来获取 ID token。

在下面的测试中,我们会为 example-python-openid-connect-client 这个应用配置 OpenID SSO 保护,在登录应用后,页面会打印出登录用户的 ID token。

这个项目有两种运行方式,一种是利用本地 Python 环境直接运行代码,第二种是在本地生成一个镜像,以容器的方式运行。

我们既然之前学了 Docker,就采用第二种方法。

从 github 下载项目

运行以下命令下载 example-python-openid-connect-client 项目

git clone https://github.com/curityio/example-python-openid-connect-client.git
#加代理的情况下
git clone https://github.com/curityio/example-python-openid-connect-client.git --config "http.proxy=http://YOUR_PROXY_SERVER:PORT"

下载完成Kubernetes 与 OpenID 集成 SSO 登录测试_docker_03

创建镜像

进入 example-python-openid-connect-client 目录,运行以下命令创建镜像

docker build -t curityio/openid-python-example .
#加代理的情况下
docker build -t curityio/openid-python-example --build-arg HTTP_PROXY=http://YOUR_PROXY_SERVER:PORT --build-arg HTTPS_PROXY=http://YOUR_PROXY_SERVER:PORT .

说明:这个镜像本身需要从 internet 上安装文件,所以如果在公司内网环境下,需要加上--build-arg 参数指定代理

运行结果Kubernetes 与 OpenID 集成 SSO 登录测试_python_04Kubernetes 与 OpenID 集成 SSO 登录测试_docker_05

可以查看到新建好的镜像了

docker images

Kubernetes 与 OpenID 集成 SSO 登录测试_docker_06

运行容器

下面我们用新镜像运行 example-python-openid-connect-client 这个应用。

在 example-python-openid-connect-client 文件夹中,开打 settings.json 文件,修改到连接到 IdP 的信息,下面列出的 5 项是我们需要修改的地方

{
  #1
  "issuer": "https://YOUR_IdP_ISSURE",
  "api_endpoint": "https://localhost:3000",
  "verify_ssl_server": false,
  "debug": true,
  #2
  "scope": "openid profile",
  "send_parameters_via": "query",
  #3
  "client_id": "YOUR_CLIENT_ID",
  #4
  "client_secret": "YOUR_CLIENT_PASSWORD",
  #5
  "redirect_uri": "https://localhost:5443/callback",
  "request_object_key": {
    "kty": "RSA",
    ...
}

说明:

  • #1 issuer:IdP 的 issuer 信息,由 IdP 提供。现在一般 IdP 提供了.well-known 的元数据接口,我们可以用浏览器在这个接口里找到与 IdP 有关的各种信息。

一般 IdP 的元数据接口为如下链接

https://YOUR_IdP_server/.well-known/openid-configuration

我们在浏览器中打开此链接,可以看到如下内容,其中“issuer”后面的就是我们在#1 中要添的信息Kubernetes 与 OpenID 集成 SSO 登录测试_docker_07


  • #2 scope:IdP 支持的 scope,一般默认是“openid”,但我们公司是“openid profile”,所以这个也需要和 IdP 具体确认下
  • #3 client_id:在 IdP 中注册的被 SSO 保护的应用的信息,一般是自己去 IdP 中注册,或者由 IdP 的管理人员提供
  • #4 client_secret:在 IdP 注册 client_id 时设定的密码,或者由 IdP 的管理人员提供
  • #5 redirect_uri: 被 SSO 保护的“网站的地址”加“/callback”,但不同的 IdP 可能加不同的返回路径,比如我们公司就是被 SSO 保护的“网站的地址”加“/redirect_uri”,这个也需要和 IdP 确认,另外在注册 client_id 时,也需要提供这个地址,IdP 在通过用户验证之后,会把这个地址返回给浏览器。这里我们保持不变即可。

修改 app.py(可选)

注意:#5 redirect_uri 中配置的是“/redirect_uri”,那么我们还需要修改一下 app.py 的代码。

打开 app.py 文件进行如下替换

#把
@_app.route('/callback')
#替换成
@_app.route('/redirect_uri')

说明:因为 IdP 返回的是“/redirect_uri”,所以我们也要把代码里这里改成“redirect_uri”。

修改之后的结果Kubernetes 与 OpenID 集成 SSO 登录测试_json_08

其实只改动这里会影响 example-python-openid-connect-client 这个应用的功能,但是不影响我们获取 ID token

下面我们生成容器,运行 example-python-openid-connect-client 这个应用。

确保本地 5443 端口没有被其它应用占用,然后运行以下命令

docker run -ti -p 5443:5443 -v $PWD/settings.json:/oidc-example/settings.json curityio/openid-python-example

说明:


  • -p 5443:5443:把容器里应用的端口 5443 映射到本地 5443
  • -v:“$PWD/settings.json”是指当前目录下的 settings.json 文件,这里通过“-v”参数把本地文件 settings.json 映射到容器内部


在另一个 WSL 终端窗口,可以看到这个容器已经运行了Kubernetes 与 OpenID 集成 SSO 登录测试_json_09

获取 ID token

下面我们用这个应用获取用户的 ID token。

在浏览器中打开网址“https://localhost:5443”,点击“Advanced”Kubernetes 与 OpenID 集成 SSO 登录测试_json_10说明:没有证书,所以出现告警,可忽略

点击“Proceed to localhost (unsafe)”Kubernetes 与 OpenID 集成 SSO 登录测试_json_11

应用登录界面出现,点击“Sign in”Kubernetes 与 OpenID 集成 SSO 登录测试_docker_12

这时会出现 SSO 登录界面,这个界面由 IdP 提供。添写 SSO 和密码后,点击登录Kubernetes 与 OpenID 集成 SSO 登录测试_docker_13

如果出现以下报错,则可能是在 settings.json 文件写错了 redirct 地址;或者是需要修改 app.py。Kubernetes 与 OpenID 集成 SSO 登录测试_python_14注意:修改 settings.json 文件后,需要重启容器。如果是需要修改 app.py 文件,则需要重建镜像并重新生成容器

正常登录后会出现如下界面,红框中的部分就是由 IdP 返回的用户 ID tokenKubernetes 与 OpenID 集成 SSO 登录测试_python_15

我们把这一串字符复制下来,粘贴到https://jwt.io/网站的Encode部分,网站会帮我们解码成可读的信息Kubernetes 与 OpenID 集成 SSO 登录测试_json_16说明:右边解码出来信息中的“sub”内容,就是下面用在 K8s 中的用户信息

2. 配置 K8s Apiserver 支持 OpenID

导出 IdP 公共证书

我们先导出 IdP 公共证书备用

在浏览器中打开 IdP 的链接,比如打开https://YOUR_IdP_server/.well-known/openid-configuration,然后点击地址框中的锁型标志Kubernetes 与 OpenID 集成 SSO 登录测试_docker_17

在弹出的界面中,点击“Certificate”Kubernetes 与 OpenID 集成 SSO 登录测试_json_18

在 Certificate 界面中,选择“Details”,然后点击“Copy to File...”Kubernetes 与 OpenID 集成 SSO 登录测试_json_19

点击“Next”Kubernetes 与 OpenID 集成 SSO 登录测试_docker_20

选择第二项“Base-64 encoded X.509(.CER)”,然后点击“Next”Kubernetes 与 OpenID 集成 SSO 登录测试_json_21

选择保存路径和文件名,点击“Next”Kubernetes 与 OpenID 集成 SSO 登录测试_json_22

点击“Finish”,导出 IdP 公共证书Kubernetes 与 OpenID 集成 SSO 登录测试_json_23

配置 Apiserver Flag

下面我们配置 K8s Apiserver 来支持 OpenID,这里配置起到的作用是使 Apiservers 可以连接到 IdP 服务器,并验证用户传来的 ID token 是否有效。

由于 K8s 有多种部署方式,所以更改 Apiserver 配置的方法也略有不同。

我们在 Kind 创建的 K8s 集群中,Apiserver 以 Pod 的形式运行,这里修改的是映射到 Pod 里的控制文件,以 kubeadm 部署的 K8s 也可以用此方法修改。

运行以下命令,进入 K8s 容器内部,编辑/etc/kubernetes/manifests/kube-apiserver.yaml 文件

docker exec -it tsk8s-control-plane bash
vi /etc/kubernetes/manifests/kube-apiserver.yaml

Kubernetes 与 OpenID 集成 SSO 登录测试_python_24

在 containers 的 command 部分,增加以下 OIDC 内容

apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 172.18.0.2:6443
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=172.18.0.2
    ...
    #增加部分
    - --oidc-issuer-url=https://YOUR_IdP_ISSURE
    - --oidc-client-id=YOUR_CLIENT_ID
    - "--oidc-username-prefix=oidc:"
    - --oidc-ca-file=/etc/kubernetes/pki/YOUR_IdP_WEBSITE_CERTIFICATE

说明:


  • --oidc-issuer-url:与在 settings.json 文件中配置的相同
  • --oidc-client-id:与在 settings.json 文件中配置的相同
  • --oidc-username-prefix:K8s 在 ID token 获得的用户信息前加的前缀,防止和 K8s 内部其它同名用户冲突,这里前缀设定为 “oidc:”,因为“:”在 yaml 文件中为特殊字符,所以这段要用引号
  • --oidc-ca-file:上面导出的 IdP 网站的证书,可以用 docker cp 命令复制到容器的/etc/kubernetes/pki 路径下

这里还有其它参数可以设置,比如用--oidc-username-claim 指定用户信息从 ID token 的 email 项获取,不指定的话默认从 sub 项获得

保存更改后退出,现在我们删除运行中的 apiserver Pod,K8s 就会自动用新的配置再创建一个 apiserver Pod

用高权限用户运行以下命令,删除 apiserver Pod

#切换到高权限用户
kubectl config use-context kind-tsk8s
#删除Pod
kubectl delete pods/kube-apiserver-tsk8s-control-plane -n kube-system

运行结果Kubernetes 与 OpenID 集成 SSO 登录测试_json_25

K8s 自动启动新的 Apiserver Pod

kubectl get pods/kube-apiserver-tsk8s-control-plane -n kube-system

可以看到,新的 Pod 已经启动Kubernetes 与 OpenID 集成 SSO 登录测试_json_26

用下列命令,查看新 Apiserver 的配置

kubectl describe pods/kube-apiserver-tsk8s-control-plane -n kube-system

可以看到 oidc 配置以生效Kubernetes 与 OpenID 集成 SSO 登录测试_json_27Kubernetes 与 OpenID 集成 SSO 登录测试_python_28

3. 通过 ID token 连接 K8s

下面我们用 ID token 测试连接 K8s。

用浏览器的隐身模式再次打开https://localhost:5443/网站,重复SSO登录过程,然后复制ID token

打开~/.kube/config 文件,修改 myuser 部分如下

- name: myuser
  user:
    auth-provider:
      config:
        client-id: YOUR_CLIENT_ID
        client-secret: YOUR_CLIENT_PASSWORD
        id-token: eyJhbGciO***JutA3JXD5FfRSebX6h6Q_zWc9v45oi1ck-sAra8pZ7bg
        idp-certificate-authority: YOUR_IdP_WEBSITE_CERTIFICATE
        idp-issuer-url: https://YOUR_IdP_ISSURE
        refresh-token: XmhjLHDrCIdQeCWe1TOUmSziqIV3nTg1x0zHIAJymY
      name: oidc

说明:

之前我们在 myuser 中用的是证书的认证方式,现在改为 OIDC 的认证方式,这里有两个参数需要说明一下。


  • id-token:“获取 ID token”步骤中获得的 ID token
  • refresh-token:“获取 ID token”步骤中的“Back-end Refresh Token”Kubernetes 与 OpenID 集成 SSO 登录测试_docker_29

保存后,我们测试一下用 myuser 用户查看 K8s 资源

#切换成myuser用户
kubectl config use-context myuser
#查看kube-system空间中的Pod
kubectl get pods -n kube-system

我们还没有给 OIDC 用户“OIDC:50XXXX”添加任何权限,所以报下列错误Kubernetes 与 OpenID 集成 SSO 登录测试_json_30说明:


  1. 虽然报了没有权限的错误,但这说明我们的用户“OIDC:50XXXX”已经可以向 Apiserver 发送请求,即我们的 OIDC 用户已经成功登录 K8s
  2. 我们在 apiserver 中配置的 OIDC 信息有效,K8s 可以从 IdP 获取必要的信息来验证 OIDC 用户有效

常见错误

下面我们模拟一下无效 ID Token 登录会出现的错误

打开~/.kube/config 文件,修改 myuser 部分的 id-token,把 id-token 的字符串随便改动一个字母,然后再运行查看 Pod 的命令

#查看kube-system空间中的Pod
kubectl get pods -n kube-system

这时会出现如下错误

error: You must be logged in to the server (Unauthorized)

Kubernetes 与 OpenID 集成 SSO 登录测试_python_31

同时用高权限用户查看 apiserver 的日志

#切换成myuser用户
kubectl config use-context kind-tsk8s
kubectl logs pods/kube-apiserver-tsk8s-control-plane -n kube-system

可以看到“invalid bearer token”的错误,因为改乱了 ID tokenKubernetes 与 OpenID 集成 SSO 登录测试_python_32

提示:

出现 Unauthorized 的错误,还有可能是 ID token 已经过期,或者 Apiserver 中 OIDC 相关配置不正确,比如 IdP 的证书有问题

4. 在 K8s 中为 SSO 用户增加权限

下面我们为 OIDC 用户(即 SSO 用户)增加访问 K8s 的权限

我们用《Kubernetes 权限管理 实战 下》文章中的方法,通过创建 Role 和 Rolebinding 给 ODIC 用户增加权限。

新建文件 role.yaml,并添加以下内容

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: oidcrole
  namespace: kube-system
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
  - list

说明:这个 Role 允许列出 kube-system 空间中的 Pod

新建文件 rolebinding.yaml,并添加以下内容

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: oidcrolebinding
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: oidcrole
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: "oidc:50XXXX"

说明:这个 Rolebinding 把上面创建的 Role 和我们 OIDC 用户“oidc:50XXXX”绑定,使得用户可以列出 kube-system 空间中的 Pod

用高权限用户运行下列命令创建 Role 和 Rolebinding

kubectl config use-context kind-tsk8s
kubectl apply -f role.yaml
kubectl apply -f rolebinding.yaml

运行结果Kubernetes 与 OpenID 集成 SSO 登录测试_json_33

我们再测试一下,列出 kube-system 空间和 default 空间中的 Pod

kubectl config use-context myuser
kubectl get pods -n kube-system
kubectl get pods -n default

结果如下Kubernetes 与 OpenID 集成 SSO 登录测试_python_34说明:可以看到 OIDC 用户可以正常查看 ube-system 空间中的 Pod,但没有权限查看 default 空间中的 Pod,这与我们设置的权限相符合

注意:在重新测试时,如果上次的 ID Token 过期了,我们则需要用登录 SSO 应用的方式,重新获取一次 ID Token

最后,我们 Ctl+C 停止 example-python-openid-connect-client 应用的容器后,可以运行下列命令删除容器

docker rm ts-oidc-example

总结


K8s 与 OIDC 集成的时候,用户利用 SSO 登录第三方 IdP 验证 SSO 有效,然后利用从 IdP 获得的 ID Token 登录 K8s。

K8s Apiserver 则需要配置到 IdP 的信息,以验证 ID Token 的有效性。K8s 不负责用户 SSO 的注册、认证等工作,只是检验用户获得 ID token 的有效性。

ID token 中包括了用户的各种信息,我们在 Apiserver 的 OIDC 相关配置中指定使用哪个信息作为 K8s 用户,默认使用 sub,也可以使用 email 或其它字段。

为了防止 OIDC 用户信息与 K8s 内部信息重名,我们一般会指定一个用户名前缀。

最后,我们一般通过 Role 和 Rolebinding 来对 OIDC 用户进行权限管理。

资源下载


“curityio/example-python-openid-connect-client”项目网址https://curity.io/resources/learn/python-openid-connect-client/

后记


这篇文章是我写过的最花时间的文章,光获取 ID Token 这块就花了好长时间去试不同的软件。

希望通过这篇文章,能让读者朋友对 K8s 和 OIDC 集成这一块的工作原理有个直观的了解,方便以后工作,反正我自己是花了挺长时间才慢慢搞清楚。。

写的累死我了,随手转发,点个赞,收个藏啥的呗。


喜欢请点赞,禁止转载,转发请标明出处

关注 B 站 UP 主“我是手拉面” 观看更多视频

微信公众号“全是 AWS 干货”


Kubernetes 与 OpenID 集成 SSO 登录测试_python_35