Hook k8s的authentication实现自定义token认证

k8s版本1.20.2

首先,我们需要写一个webhook服务:

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
package main

import (
"context"
"encoding/json"
"log"
"net/http"

"github.com/google/go-github/github"
"golang.org/x/oauth2"
authentication "k8s.io/api/authentication/v1beta1"
)

// 处理认证
func handleAuthentication(w http.ResponseWriter, r *http.Request) {
// 解码认证请求
var tr authentication.TokenReview
decoder := json.NewDecoder(r.Body)
decoder.Decode(&tr)

// 认证token身份
user, err := checkToken(tr.Spec.Token)
if err != nil { // 认证失败了,返回失败的响应给api server
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]interface{}{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": authentication.TokenReviewStatus{
Authenticated: false,
},
})
return
}

// 返回认证成功的响应给api server
w.WriteHeader(http.StatusOK)
trs := authentication.TokenReviewStatus{
Authenticated: true,
User: authentication.UserInfo{
Username: user.Username,
UID: user.UID,
},
}
json.NewEncoder(w).Encode(map[string]interface{}{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": trs,
})
}

func main() {
http.HandleFunc("/authentication", func(w http.ResponseWriter, r *http.Request) {
handleAuthentication(w, r)
})

log.Println(http.ListenAndServe(":3000", nil))
}

然后创建一个文件,指明我们的这个webhook server的地址,文件内容如下:

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
cat authentication-webhook-config.json
{
"kind": "Config",
"apiVersion": "v1",
"preferences": {},
"clusters": [
{
"name": "custom-authentication",
"cluster": {
"server": "http://{{改成你的webhook server的地址}}:3000/authentication"
}
}
],
"users": [
{
"name": "authentication-apiserver",
"user": {
"token": "secret"
}
}
],
"contexts": [
{
"name": "webhook",
"context": {
"cluster": "custom-authentication",
"user": "authentication-apiserver"
}
}
],
"current-context": "webhook"
}

接着,修改/etc/kubernetes/manifests/api-server.yaml文件,指明我们的authentication-webhook-config.json文件路径:

1
- --authentication-token-webhook-config-file=/path/to/authentication-webhook-config.json

注意,因为api-server是以pod的方式运行的,所以你这个authentication-webhook-config.json文件要挂载到容器里面,挂载方式很基础,就不说了。并且,需要注意的一点就是,如果你在修改api-server.yaml文件之前,恰好备份了一个api-server.yaml文件放在/etc/kubernetes/manifests/api-server.yaml.backup的位置,那么很不幸,你会发现无论你怎么修改api-server.yaml,发现api-server都没有成功的重启。原因就是/etc/kubernetes/manifests这个目录很特殊,具体怎么特殊法,可以看k8s的文档。所以,如果你要备份api-server.yaml文件的话,请备份到其他地方,例如~/api-server.yaml.backup不要备份在/etc/kubernetes/manifests目录下面

保存api-server.yaml文件文件后,api-server这个static pod会自动重启。验证webhook是否配置成功。

先用ps查看api-server的启动参数,例如:

1
ps -ef | grep api-server

输出的内容里面,一定要带有你配置的authentication-token-webhook-config-file

然后,使用kubectl -n kube-system get pods | grep api-serverpod的运行状态是否是Running

最后,再验证请求是否会被转发到你的webhook server。测试方法如下:

先配置.kube/config,加一段使用token认证的配置:

1
2
3
4
5
vim ~/.kube/config

- name: codinghuang
user:
token: 你的token

然后请求:

1
kubectl get pods --user codinghuang

正常情况下,会出现这个用户没有获取pod的权限。因为我们当前的webhook server仅仅是处理认证,鉴权是没有去hook的,hook鉴权也很简单,也是需要改api-server的配置:

1
2
3
- --authorization-mode=Node,RBAC,Webhook
- --runtime-config=authorization.k8s.io/v1beta1=true
- --authorization-webhook-config-file=/etc/kubernetes/config/authorization-webhook-config.json

authorization-webhook-config.json的配置可以抄authentication-webhook-config.json,只需要改一下serverurl,然后加一个处理鉴权的路由,例如/authorization。

如果你不想hook鉴权,那么可以直接配置RoleBinding,赋予这个用户权限,例如,我给这个用户赋予一个最高的权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: default-admin
namespace: default
subjects:
- kind: User
name: codinghuang
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io

然后这个用户就不会报权限问题了。