前言
去年处理过一次安全事件:攻击者通过某个泄露的服务账号token,成功连接到了我们的K8s集群并创建了恶意Pod。事后复盘时,我发现自己对K8s的认证机制理解太浅------只知道用kubectl配置token,却不清楚认证是如何进行的、有哪些防护手段。
这次事件让我下定决心,必须深入理解kube-apiserver的认证机制。今天就把学到的知识分享给大家,从源码角度剖析K8s的认证流程。
为什么需要认证?
在K8s中,apiserver是所有API请求的入口。如果没有认证机制,任何人都可以随意操作集群资源,这显然是不可接受的。
认证的目的很简单:确认"你是你是谁"。当客户端(kubectl、kubelet、controller等)向apiserver发起请求时,apiserver需要验证客户端的身份,确认它声称的身份是否真实有效。
认证后的身份信息包括:
┌─────────────────────────────────────────────────────────┐
│ 认证身份信息 │
├─────────────────────────────────────────────────────────┤
│ 用户名(Username) - 人类可读的标识,如"admin" │
│ 用户ID(UID) - 系统唯一标识 │
│ 用户组(Groups) - 所属的逻辑集合,如"system:masters" │
│ 附加字段(Extra) - 额外的键值对信息 │
└─────────────────────────────────────────────────────────┘
K8s认证方式概览
K8s支持多种认证方式,可以同时启用多种,形成认证链:
| 认证方式 | 适用场景 | 安全性 | 配置复杂度 |
|---|---|---|---|
| X.509客户端证书 | 组件间通信(kubelet、controller-manager等) | 高 | 中 |
| Bearer Token | 服务账号、静态Token文件 | 中 | 低 |
| Webhook Token | 对接外部认证系统(OIDC、LDAP等) | 高 | 高 |
| HTTP Basic Auth | 测试环境(已废弃) | 低 | 低 |
| Request Header | 认证代理场景 | 高 | 中 |
实际使用中,通常同时启用多种:
- X.509证书用于集群组件间的双向TLS认证
- Bearer Token用于服务账号
- Webhook用于对接企业SSO系统
认证流程总览
客户端请求
│
▼
┌─────────────────────────────────────────────────────────────┐
│ apiserver │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Authentication Chain │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ X.509 │ → │ Token │ → │ Webhook │ → ... │ │
│ │ │证书认证 │ │令牌认证 │ │钩子认证 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ ↓ ↓ ↓ │ │
│ │ 任何一个认证成功 → 返回用户信息 → 进入鉴权阶段 │ │
│ │ │ │
│ │ 所有认证都失败 → 返回401 Unauthorized │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
关键特点:
- 支持多种认证方式同时启用
- 认证是链式执行,只要有一个通过就算认证成功
- 认证不保证顺序
- 所有通过认证的用户都会被自动添加到
system:authenticated组
源码解析:认证器的初始化
初始化入口
认证器的初始化发生在buildGenericConfig中:
go
// cmd/kube-apiserver/app/server.go
if lastErr = s.Authentication.ApplyTo(
&genericConfig.Authentication,
genericConfig.SecureServing,
genericConfig.EgressSelector,
genericConfig.OpenAPIConfig,
clientgoExternalClient,
versionedInformers,
); lastErr != nil {
return
}
认证配置结构
go
// pkg/kubeapiserver/options/authentication.go
type BuiltInAuthenticationOptions struct {
ClientCert *ClientCertAuthenticationOptions // X.509客户端证书
RequestHeader *RequestHeaderAuthenticationOptions // Request Header认证
WebHook *WebHookAuthenticationOptions // Webhook认证
BootstrapToken *BootstrapTokenAuthenticationOptions // Bootstrap Token
OIDC *OIDCAuthenticationOptions // OIDC认证
PasswordFile *PasswordFileAuthenticationOptions // 静态密码文件(已废弃)
ServiceAccounts *ServiceAccountAuthenticationOptions // 服务账号
TokenFile *TokenFileAuthenticationOptions // 静态Token文件
}
创建认证器
go
// pkg/kubeapiserver/authenticator/config.go
func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
var authenticators []authenticator.Request
var tokenAuthenticators []authenticator.Token
// 1. X.509客户端证书认证
if config.ClientCAContentProvider != nil {
certAuth := x509.NewDynamic(
config.ClientCAContentProvider.VerifyOptions,
x509.CommonNameUserConversion,
)
authenticators = append(authenticators, certAuth)
}
// 2. Bearer Token认证(包括静态Token文件和服务账号)
if len(tokenAuthenticators) > 0 {
tokenAuth := tokenunion.New(tokenAuthenticators...)
authenticators = append(authenticators,
bearertoken.New(tokenAuth),
websocket.NewProtocolAuthenticator(tokenAuth),
)
}
// 3. Request Header认证(用于认证代理)
if config.RequestHeaderConfig != nil {
requestHeaderAuth := headerrequest.NewDynamicVerifyOptionsSecure(
config.RequestHeaderConfig.VerifyOptions,
)
authenticators = append(authenticators, requestHeaderAuth)
}
// 4. 创建Union认证器
authenticator := union.New(authenticators...)
// 5. 包装为Group添加器
authenticator = group.NewAuthenticatedGroupAdder(authenticator)
return authenticator, nil
}
Union认证模式:多种认证方式的组合
K8s的认证采用Union模式(也称为链式认证),核心思想是:按顺序尝试多种认证方式,只要有一种通过就返回成功。
Union模式的实现
go
// staging/src/k8s.io/apiserver/pkg/authentication/request/union/union.go
type unionAuthRequestHandler struct {
Handlers []authenticator.Request
FailOnError bool // 是否在第一个错误时就失败
}
// AuthenticateRequest 遍历所有认证器,只要有一个通过就返回
func (authHandler *unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
var errlist []error
for _, currAuthRequestHandler := range authHandler.Handlers {
resp, ok, err := currAuthRequestHandler.AuthenticateRequest(req)
if err != nil {
// 如果配置了FailOnError,遇到错误直接返回
if authHandler.FailOnError {
return resp, ok, err
}
// 否则记录错误,继续尝试下一个认证器
errlist = append(errlist, err)
continue
}
// 认证成功,直接返回
if ok {
return resp, ok, nil
}
}
// 所有认证器都失败了
return nil, false, utilerrors.NewAggregate(errlist)
}
认证规则总结
认证链执行规则:
对于每个认证器:
如果返回 error:
如果 FailOnError = true:直接返回错误(认证失败)
否则:记录错误,继续下一个
如果返回 ok = true:
认证成功,返回用户信息(停止执行后续认证器)
如果返回 ok = false:
认证未通过,继续下一个
如果所有认证器都执行完,没有一个返回ok:
认证失败,返回401 Unauthorized
认证方式详解
1. X.509客户端证书认证
最安全的认证方式,用于集群组件间通信。
工作原理
go
// staging/src/k8s.io/apiserver/pkg/authentication/request/x509/x509.go
func NewDynamic(verifyOptionsFn VerifyOptionFunc, user UserConversion) *Authenticator {
return &Authenticator{
verifyOptionsFn: verifyOptionsFn,
user: user,
}
}
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
// 从TLS连接中获取客户端证书
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
return nil, false, nil
}
// 验证证书链
opts := a.verifyOptionsFn()
opts.KeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
// 使用CA证书验证客户端证书
verifiedChains, err := req.TLS.PeerCertificates[0].Verify(opts)
if err != nil {
return nil, false, err
}
// 从证书中提取用户信息
user, ok, err := a.user.User(verifiedChains)
if err != nil || !ok {
return nil, ok, err
}
return &authenticator.Response{
User: user,
}, true, nil
}
用户信息提取
X.509认证从证书中提取用户名:
go
// CommonNameUserConversion 使用证书的CommonName作为用户名
func CommonNameUserConversion(chain [][]*x509.Certificate) (user.Info, bool, error) {
if len(chain) == 0 || len(chain[0]) == 0 {
return nil, false, nil
}
return &user.DefaultInfo{
Name: chain[0][0].Subject.CommonName, // 用户名 = CommonName
Groups: chain[0][0].Subject.Organization, // 用户组 = Organization
}, true, nil
}
证书示例:
bash
# 创建客户端证书
openssl req -new -key admin.key -out admin.csr \
-subj "/CN=admin/O=system:masters"
# CN (Common Name) = 用户名
# O (Organization) = 用户组(可以是多个)
配置apiserver
bash
kube-apiserver \
--client-ca-file=/etc/kubernetes/pki/ca.crt \
--tls-cert-file=/etc/kubernetes/pki/apiserver.crt \
--tls-private-key-file=/etc/kubernetes/pki/apiserver.key
2. Bearer Token认证
最常用的认证方式,用于服务账号和外部用户。
Bearer Token是一种简单的令牌认证方式,客户端在HTTP Header中携带token:
Authorization: Bearer <token>
Token认证链
go
// Token认证也是Union模式
type unionAuthTokenHandler struct {
Handlers []authenticator.Token
}
func (authHandler *unionAuthTokenHandler) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
var errlist []error
for _, currAuthRequestHandler := range authHandler.Handlers {
info, ok, err := currAuthRequestHandler.AuthenticateToken(ctx, token)
if err != nil {
errlist = append(errlist, err)
continue
}
if ok {
return info, ok, err
}
}
return nil, false, utilerrors.NewAggregate(errlist)
}
服务账号Token认证
go
// 服务账号token的JWT验证
func (a *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
// 解析JWT token
public := &jwt.Claims{}
private := &privateClaims{}
// 验证签名
_, err := jwt.ParseSigned(token)
if err != nil {
return nil, false, nil
}
// 提取用户信息
return &authenticator.Response{
User: &user.DefaultInfo{
Name: public.Subject, // sub字段作为用户名
Groups: private.Kubernetes.Groups,
UID: private.Kubernetes.UID,
},
}, true, nil
}
服务账号Token的特点:
- 以JWT格式存储
- 包含用户名、用户组、有效期等信息
- 挂载到Pod的
/var/run/secrets/kubernetes.io/serviceaccount/token
3. Webhook Token认证
对接外部认证系统(如OIDC、LDAP、企业SSO)。
当K8s内置的认证方式不能满足需求时,可以通过Webhook将认证委托给外部系统。
工作原理
客户端请求
│
▼
apiserver ──POST──→ Webhook Server
│ (外部认证服务)
│←────JSON───────
│ {authenticated: true, user: {...}}
▼
认证成功/失败
Webhook配置
yaml
# webhook-config.yaml
apiVersion: v1
kind: Config
clusters:
- name: webhook-server
cluster:
certificate-authority: /path/to/ca.crt
server: https://auth-webhook.example.com/authenticate
users:
- name: apiserver
user:
client-certificate: /path/to/client.crt
client-key: /path/to/client.key
current-context: webhook
contexts:
- context:
cluster: webhook-server
user: apiserver
name: webhook
bash
kube-apiserver \
--authentication-token-webhook-config-file=/path/to/webhook-config.yaml \
--authentication-token-webhook-cache-ttl=2m
Webhook请求/响应格式
apiserver发送的请求:
json
{
"apiVersion": "authentication.k8s.io/v1",
"kind": "TokenReview",
"spec": {
"token": "<bearer token>"
}
}
Webhook服务的响应:
json
{
"apiVersion": "authentication.k8s.io/v1",
"kind": "TokenReview",
"status": {
"authenticated": true,
"user": {
"username": "janedoe@example.com",
"uid": "42",
"groups": ["developers", "qa"],
"extra": {
"foo": ["bar"]
}
}
}
}
认证失败处理
HTTP 401 Unauthorized
当所有认证器都无法识别请求时,apiserver返回401:
http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/json
{
"kind": "Status",
"apiVersion": "v1",
"status": "Failure",
"message": "Unauthorized",
"reason": "Unauthorized",
"code": 401
}
匿名访问
如果启用了匿名访问(--anonymous-auth=true),未通过认证的用户会被映射为system:anonymous用户:
go
// 在Union认证器外再包装一层匿名认证
if config.Anonymous {
// 如果所有认证都失败,返回匿名用户
authenticator = anonymous.NewAuthenticator()
}
// 匿名用户信息
&user.DefaultInfo{
Name: "system:anonymous",
Groups: []string{"system:unauthenticated"},
}
安全最佳实践
1. 启用mTLS双向认证
bash
kube-apiserver \
--client-ca-file=/etc/kubernetes/pki/ca.crt \
--requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
效果:
- 只有持有有效客户端证书的组件才能连接apiserver
- 防止中间人攻击
2. 禁用不安全的认证方式
bash
# 禁用匿名访问(除非有特殊需求)
--anonymous-auth=false
# 不使用静态密码文件(已废弃,不安全)
# --basic-auth-file (不要设置)
# 不使用静态Token文件(难以管理)
# --token-auth-file (不要设置)
3. 使用Webhook对接企业SSO
bash
kube-apiserver \
--authentication-token-webhook-config-file=/etc/kubernetes/webhook.yaml \
--oidc-issuer-url=https://accounts.google.com \
--oidc-client-id=kubernetes \
--oidc-username-claim=email \
--oidc-groups-claim=groups
优点:
- 统一身份管理
- 支持MFA多因素认证
- 自动处理员工离职
4. 定期轮换证书和Token
bash
# 证书轮换
kubeadm certs renew all
# 服务账号Token自动轮换(K8s 1.24+默认启用)
5. 启用审计日志
bash
kube-apiserver \
--audit-policy-file=/etc/kubernetes/audit-policy.yaml \
--audit-log-path=/var/log/kubernetes/audit.log
踩坑实录:认证常见问题
坑1:证书过期
现象 :组件无法连接apiserver,报错x509: certificate has expired
解决方案:
bash
# 检查证书有效期
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates
# 使用kubeadm轮换证书
kubeadm certs renew all
systemctl restart kubelet
坑2:Token权限过大
现象:服务账号被授予了过高权限,导致安全隐患
解决方案:
yaml
# 遵循最小权限原则
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: minimal-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"] # 只给读权限,不给写权限
坑3:Webhook认证失败
现象:配置了Webhook认证,但apiserver启动失败或认证不生效
排查步骤:
bash
# 1. 检查Webhook配置
kubectl get --raw /api/v1/namespaces/default
# 2. 查看apiserver日志
journalctl -u kube-apiserver -f
# 3. 测试Webhook服务
curl -X POST https://auth-webhook.example.com/authenticate \
-H "Content-Type: application/json" \
-d '{"spec":{"token":"test"}}'
坑4:认证冲突
现象:同时启用了多种认证,导致权限混乱
解决方案:
- 明确每种认证方式的用途
- 避免同一用户使用多种认证方式
- 使用
system:authenticated组统一管理
总结
通过今天的分析,我们深入理解了kube-apiserver的认证机制:
- 认证目的:确认客户端身份,防止未授权访问
- 多种认证方式:X.509证书、Bearer Token、Webhook、Request Header等
- Union模式:多种认证方式链式执行,只要一个通过即可
- 认证信息:用户名、UID、用户组、附加字段
- 安全实践:mTLS、禁用不安全方式、定期轮换、审计日志
认证只是安全的第一步,认证通过的用户还需要经过**鉴权(Authorization)**才能操作资源。下一篇我们将深入解析K8s的鉴权机制。