认证
认证
开启 TLS 时, 所有的请求都需要首先认证. Kubernetes 支持多种认证机制, 并支持同时开启多个认证插件(只要有一个认证通过即可). 如果认证成功, 则用户的 username 会传入授权模块做进一步授权验证; 而对于认证失败的请求则返回 HTTP 401.
认证插件
- X509 证书(默认)
- 使用 X509 客户端证书只需要 API Server 启动时配置 --client-ca-file=SOMEFILE. 在证书认证时, 其 CN 域用作用户名, 而组织机构域则用作 group 名.
- 静态 Token 文件(最简单)
- 使用静态 Token 文件认证只需要 API Server 启动时配置 --token-auth-file=SOMEFILE.
- 该文件为 csv 格式, 每行至少包括三列 token, username, user id.
token, user, uid, "group1, group2, group3"
- 引导 Token
- 为了支持平滑地启动引导新的集群, Kubernetes 包含了一种动态管理的持有者令牌类型, 称作启动引导令牌(Bootstrap Token).
- 这些令牌以 Secret 的形式保存在 kube-system 名字空间中, 可以被动态管理和创建.
- 控制器管理器包含的 TokenCleaner 控制器能够在启动引导令牌过期时将其删除.
- 在使用 kubeadm 部署 Kubernetes 时, 可通过 kubeadmtoken list 命令查询.
- 静态密码文件(类似 "静态 Token 文件")
- 需要 API Server 启动时配置 --basic-auth-file=SOMEFILE, 文件格式为 csv , 每行至少三列 password, user, uid, 后面是可选的 group 名.
password, user, uid, "group1, group2, group3"
- 需要 API Server 启动时配置 --basic-auth-file=SOMEFILE, 文件格式为 csv , 每行至少三列 password, user, uid, 后面是可选的 group 名.
- ServiceAccount(系统账户, Kubernetes 自带的)
- ServiceAccount 是 Kubernetes 自动生成的, 并会自动挂载到容器的 /run/secrets/kubernetes.io/serviceaccount 目录中.
- /run/secrets/kubernetes.io/serviceaccount/{ca.crt,namespace,token}
- ServiceAccount 是 Kubernetes 自动生成的, 并会自动挂载到容器的 /run/secrets/kubernetes.io/serviceaccount 目录中.
- OpenID
- OAuth 2.0 的认证机制
- Webhook 令牌身份认证(集成现有的认证平台,应用场景比较广)
- --authentication-token-webhook-config-file 指向一个配置文件, 其中描述如何访问远程的 Webhook 服务.
- --authentication-token-webhook-cache-ttl 用来设定身份认证决定的缓存时间. 默认时长为 2 分钟.
- 匿名请求(不建议用)
- 如果使用 AlwaysAllow 以外的认证模式, 则匿名请求默认开启, 但可用 --anonymous-auth=false 禁止匿名请求.
X509 证书
/etc/kubernetes/admin.conf
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: (CA-data)
server: https://192.168.1.218:6443 (服务器地址)
name: kubernetes (集群名称)
contexts: (集群上下文)
- context:
cluster: kubernetes (连接哪个集群)
user: kubernetes-admin (用哪个用户连接这个集群)
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users: (用户信息)
- name: kubernetes-admin (用户名)
user:
client-certificate-data: (用户的 cert-data)
client-key-data: (用户的 key-data)
基于 webhook 的认证服务集成
https://github.com/kubeguard/guard
构建符合 Kubernetes 规范的认证服务
需要依照 Kubernetes 规范, 构建认证服务, 用来认证 tokenreview request
构建认证服务
- 认证服务需要满足如下Kubernetes的规范
- URL: https://authn.example.com/authenticate (webhook 服务地址)
- Method: POST
- Input:
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"spec":
{
"token": "(BEARERTOKEN)"
}
}
- Output:
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status":
{
"authenticated": true,
"user":
{
"username": "janedoe@example.com ",
"uid": "42",
"groups":
[
"developers",
"qa"
]
}
}
}
开发认证服务
解码认证请求
decoder := json.NewDecoder(r.Body)
var tr authentication.TokenReview
err := decoder.Decode(&tr)
if err != nil {
log.Println("[Error]", err.Error())
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(
map[string]interface{}{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": authentication.TokenReviewStatus{
Authenticated: false,
},
}
)
return
}
转发认证请求至认证服务器
// Check User
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: tr.Spec.Token},)
tc := oauth2.NewClient(oauth2.NoContext, ts)
client := github.NewClient(tc)
user, _, err := client.Users.Get(context.Background(), "")
if err != nil {
log.Println("[Error]", err.Error())
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(
map[string]interface{}{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": authentication.TokenReviewStatus{
Authenticated: false,
},
}
)
return
}
认证结果返回给APIServer
w.WriteHeader(http.StatusOK)
trs := authentication.TokenReviewStatus{
Authenticated: true,
User: authentication.UserInfo{
Username: *user.Login,
UID: *user.Login,
},
}
json.NewEncoder(w).Encode(map[string]interface{}{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": trs,
})
配置认证服务
{
"kind": "Config",
"apiVersion": "v1",
"preferences": {},
"clusters": [
{
"name": "github-authn",
"cluster": {
"server": "http://localhost:3000/authenticate"
}
}
],
"users": [
{
"name": "authn-apiserver",
"user": {
"token": "secret"
}
}
],
"contexts": [
{
"name": "webhook",
"context": {
"cluster": "github-authn",
"user": "authn-apiserver"
}
}
],
"current-context": "webhook"
}
配置apiserver
可以是任何认证系统
- 但在用户认证完成后, 生成代表用户身份的 token
- 该 token 通常是有失效时间的
- 用户获取该 token 以后以后, 将 token 配置进 kubeconfig 修改 apiserver 设置, 开启认证服务, apiserver 保证将所有收到的请求中的 token 信息, 发给认证服务进行验证
- --authentication-token-webhook-config-file, 该文件描述如何访问认证服务
- --authentication-token-webhook-cache-ttl, 默认 2 分钟
配置文件需要 mount 进 Pod
配置文件中的服务器地址需要指向 authService
{
"kind": "Config",
"apiVersion": "v1",
"preferences": {},
"clusters": [
{
"name": "github-authn",
"cluster": {
"server": "http://localhost:3000/authenticate"
}
}
],
"users": [
{
"name": "authn-apiserver",
"user": {
"token": "secret"
}
}
],
"contexts": [
{
"name": "webhook",
"context": {
"cluster": "github-authn",
"user": "authn-apiserver"
}
}
],
"current-context": "webhook"
}
生产系统中遇到的陷阱
基于 Keystone 的认证插件导致 Keystone 故障且无法恢复
Keystone 是企业关键服务
Kubernetes 以 Keystone 作为认证插件
Keystone 在出现故障后会抛出 401 错误
Kubernetes 发现 401 错误后会尝试重新认证
大多数 controller 都有指数级 back off, 重试间隔越来越慢
但 gophercloud 针对过期 token 会一直 retry
大量的 request 积压在 Keystone 导致服务无法恢复
Kubernetes 成为压死企业认证服务的最后一根稻草
解决方案?
- Circuit break
- Rate limit
一般用法
主要防止 api-server 启动时,webhook 没有启动成功。
- 运行成 api-server 的一个 sidecar
- 写在 /etc/kubernetes/manifests/webhook.yaml