深入kube-apiserver认证机制:从Bearer Token到mTLS的完整认证链解析

前言

去年处理过一次安全事件:攻击者通过某个泄露的服务账号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的认证机制:

  1. 认证目的:确认客户端身份,防止未授权访问
  2. 多种认证方式:X.509证书、Bearer Token、Webhook、Request Header等
  3. Union模式:多种认证方式链式执行,只要一个通过即可
  4. 认证信息:用户名、UID、用户组、附加字段
  5. 安全实践:mTLS、禁用不安全方式、定期轮换、审计日志

认证只是安全的第一步,认证通过的用户还需要经过**鉴权(Authorization)**才能操作资源。下一篇我们将深入解析K8s的鉴权机制。

相关推荐
森G2 小时前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt
阿米亚波2 小时前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm
张飞飞飞飞飞2 小时前
Tmux命令使用教程
linux·服务器·ubuntu
Fcy6482 小时前
Linux下 可重入函数、volatile关键字和SIGCHLD信号
linux·可重入函数·volatile关键字·sigchld
程序员老赵2 小时前
服务器没有桌面?Docker 跑个 Chrome,浏览器就能远程用
docker·容器·devops
難釋懷2 小时前
Nginx反向代理中的容错机制
运维·nginx
杨浦老苏2 小时前
轻量级Docker仪表板Servedash
运维·docker·监控·群晖·仪表板
正经教主2 小时前
【docker基础】 第八周:容器监控与应用更新策略
运维·docker·容器
志栋智能2 小时前
超自动化巡检:如何选择适合你的起点?
运维·自动化