深入kube-apiserver鉴权机制:从RBAC到Node的4种鉴权模式全解析

前言

去年一次生产事故让我至今记忆犹新:一个开发同事为了调试方便,给自己账号绑定了cluster-admin角色,结果误操作删除了整个namespace,导致线上服务中断2小时。事后复盘时,我发现自己对K8s鉴权机制理解太浅——只知道用RBAC,却不清楚鉴权是如何进行的、如何设计最小权限策略。

这次事件让我意识到,认证只是安全的第一步,鉴权才是真正的防护墙。今天就带大家深入kube-apiserver源码,剖析K8s的鉴权机制。

认证 vs 鉴权:傻傻分不清楚?

很多人(包括以前的我)经常把认证和鉴权搞混。简单说:

阶段问题结果
认证 (Authentication)你是谁?确认身份 → 401 Unauthorized
鉴权 (Authorization)你能做什么?确认权限 → 403 Forbidden

举例说明

  • 认证:门禁刷卡确认你是公司员工
  • 鉴权:刷卡后确认你能进哪些房间(研发区可以进,财务区不能进)

K8s中的流程

客户端请求
    │
    ▼
┌─────────────────────────────────────────────────────────────┐
│  Authentication(认证)                                      │
│  - X.509证书/Bearer Token/Webhook                            │
│  - 验证身份,获取用户信息(用户名、用户组)                    │
└──────────────────┬──────────────────────────────────────────┘
                   │ 认证失败 → 401 Unauthorized
                   ▼
┌─────────────────────────────────────────────────────────────┐
│  Authorization(鉴权)                                       │
│  - Node/RBAC/ABAC/Webhook                                    │
│  - 检查用户是否有权限执行请求的操作                            │
└──────────────────┬──────────────────────────────────────────┘
                   │ 鉴权失败 → 403 Forbidden
                   ▼
┌─────────────────────────────────────────────────────────────┐
│  Admission Control(准入控制)                                │
│  - 进一步校验和修改请求                                       │
└─────────────────────────────────────────────────────────────┘

K8s的4种鉴权模式

K8s支持4种鉴权模式,可以同时启用多种:

鉴权模式说明适用场景推荐度
Node专为kubelet设计的鉴权模式kubelet访问控制⭐⭐⭐⭐⭐
RBAC基于角色的访问控制通用权限管理⭐⭐⭐⭐⭐
ABAC基于属性的访问控制简单静态策略(已不推荐)⭐⭐
Webhook外部鉴权服务对接企业权限系统⭐⭐⭐⭐

推荐组合--authorization-mode=Node,RBAC

  • Node模式:限制kubelet只能操作自己节点上的Pod
  • RBAC模式:通用的细粒度权限控制

鉴权执行流程

鉴权的核心接口

// staging/src/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go

// Authorizer 鉴权接口
type Authorizer interface {
    // Authorize 对请求进行鉴权
    // 返回值:
    //   - Decision: 鉴权结果(Allow/Deny/NoOpinion)
    //   - reason: 原因说明(拒绝时有用)
    //   - err: 错误
    Authorize(ctx context.Context, a Attributes) (Decision, string, error)
}

// Attributes 鉴权属性(请求上下文信息)
type Attributes interface {
    GetUser() user.Info              // 认证后的用户信息
    GetVerb() string                 // 操作类型(get/list/create/update/delete)
    GetNamespace() string            // 命名空间
    GetResource() string             // 资源类型(pods/services等)
    GetSubresource() string          // 子资源(status/log等)
    GetName() string                 // 资源名称
    GetAPIGroup() string             // API组
    GetAPIVersion() string           // API版本
    IsResourceRequest() bool         // 是否是资源请求(vs 非资源如/metrics)
    GetPath() string                 // 请求路径
}

鉴权决策类型

// 鉴权结果决策类型
type Decision int

const (
    DecisionDeny Decision = iota     // 拒绝(明确无权限)
    DecisionAllow                    // 允许(明确有权限)
    DecisionNoOpinion               // 无意见(无法判断,让下一个鉴权器处理)
)

重要

  • DecisionDenyDecisionAllow都是明确的决策,会立即返回
  • DecisionNoOpinion表示"我不知道,请让下一个鉴权器判断"

Union鉴权模式:多种鉴权的组合

K8s的鉴权采用Union模式(与认证类似),按顺序执行多个鉴权器:

// staging/src/k8s.io/apiserver/pkg/authorization/union/union.go

type unionAuthzHandler []authorizer.Authorizer

func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer {
    return unionAuthzHandler(authorizationHandlers)
}

func (authzHandler unionAuthzHandler) Authorize(
    ctx context.Context, 
    a authorizer.Attributes,
) (authorizer.Decision, string, error) {
    var (
        errlist    []error
        reasonlist []string
    )
    
    for _, currAuthzHandler := range authzHandler {
        decision, reason, err := currAuthzHandler.Authorize(ctx, a)
        
        if err != nil {
            errlist = append(errlist, err)
        }
        if len(reason) != 0 {
            reasonlist = append(reasonlist, reason)
        }
        
        switch decision {
        case authorizer.DecisionAllow, authorizer.DecisionDeny:
            // 明确决策,立即返回
            return decision, reason, err
        case authorizer.DecisionNoOpinion:
            // 无意见,继续下一个
        }
    }
    
    // 所有鉴权器都无意见,默认拒绝
    return authorizer.DecisionNoOpinion, 
           strings.Join(reasonlist, "\n"), 
           utilerrors.NewAggregate(errlist)
}

鉴权执行规则

鉴权链执行规则:

对于每个鉴权器:
    如果返回 DecisionAllow:
        鉴权通过,允许请求(返回200 OK)
    
    如果返回 DecisionDeny:
        鉴权拒绝,禁止请求(返回403 Forbidden)
    
    如果返回 DecisionNoOpinion:
        无法判断,继续下一个鉴权器

如果所有鉴权器都返回 DecisionNoOpinion:
    默认拒绝(返回403 Forbidden)

注意:与认证不同,鉴权链的执行顺序很重要!

初始化入口:BuildAuthorizer

鉴权器的初始化在buildGenericConfig中完成:

// cmd/kube-apiserver/app/server.go
genericConfig.Authorization.Authorizer, 
genericConfig.Authorization.RuleResolver, 
err = BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers)

BuildAuthorizer实现

// pkg/kubeapiserver/authorizer/config.go
func BuildAuthorizer(s *options.ServerRunOptions, ...) (authorizer.Authorizer, authorizer.RuleResolver, error) {
    authorizationConfig := authorizer.AuthorizationConfig{
        AuthorizationModes:          s.Authorization.Modes,
        VersionedInformerFactory:    versionedInformers,
        // ... 其他配置
    }
    
    return authorizationConfig.New()
}

func (config AuthorizationConfig) New() (authorizer.Authorizer, authorizer.RuleResolver, error) {
    var authorizers []authorizer.Authorizer
    var ruleResolvers []authorizer.RuleResolver
    
    // 遍历配置的鉴权模式,创建对应的鉴权器
    for _, authorizationMode := range config.AuthorizationModes {
        switch authorizationMode {
        case modes.ModeNode:
            // 创建Node鉴权器
            nodeAuthorizer := buildNodeAuthorizer(config)
            authorizers = append(authorizers, nodeAuthorizer)
            ruleResolvers = append(ruleResolvers, nodeAuthorizer)
            
        case modes.ModeRBAC:
            // 创建RBAC鉴权器
            rbacAuthorizer := buildRBACAuthorizer(config)
            authorizers = append(authorizers, rbacAuthorizer)
            ruleResolvers = append(ruleResolvers, rbacAuthorizer)
            
        case modes.ModeABAC:
            // 创建ABAC鉴权器
            abacAuthorizer := buildABACAuthorizer(config)
            authorizers = append(authorizers, abacAuthorizer)
            
        case modes.ModeWebhook:
            // 创建Webhook鉴权器
            webhookAuthorizer := buildWebhookAuthorizer(config)
            authorizers = append(authorizers, webhookAuthorizer)
        }
    }
    
    // 创建Union鉴权器和RuleResolver
    return union.New(authorizers...), 
           union.NewRuleResolvers(ruleResolvers...), 
           nil
}

4种鉴权模式详解

1. Node鉴权模式

专门为kubelet设计的鉴权模式

为什么需要Node模式?

kubelet需要操作Pod,但如果给它cluster-admin权限,它可以操作整个集群的所有Pod,这太危险了。Node模式限制kubelet只能操作自己节点上的Pod

Node鉴权器的工作原理
// pkg/auth/node/node_authorizer.go

type NodeAuthorizer struct {
    graph             *Graph              // Pod-Node关系图
    identifier        nodeidentifier.NodeIdentifier  // 节点标识器
    rules             []rbacv1.PolicyRule // 节点权限规则
}

func (r *NodeAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
    // 1. 获取节点名称
    nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser())
    if !isNode {
        // 不是kubelet用户,无法判断,返回NoOpinion
        return authorizer.DecisionNoOpinion, "", nil
    }
    
    // 2. 获取请求的属性
    verb := attrs.GetVerb()
    resource := attrs.GetResource()
    subresource := attrs.GetSubresource()
    name := attrs.GetName()
    
    // 3. 根据资源类型进行不同的权限检查
    switch resource {
    case "pods":
        return r.authorizePodOperation(nodeName, verb, subresource, name, attrs)
    case "services", "endpoints":
        return r.authorizeReadOnly(nodeName, verb)
    case "configmaps", "secrets":
        return r.authorizeConfigMapSecretOperation(nodeName, verb, name, attrs)
    // ... 其他资源类型
    }
    
    return authorizer.DecisionNoOpinion, "", nil
}
Node鉴权的规则
// Node权限规则(简化版)
var NodeRules = []rbacv1.PolicyRule{
    // 可以读取所有Service和Endpoints(用于服务发现)
    {
        APIGroups: []{""},
        Resources: []{"services", "endpoints"},
        Verbs:     []{"get", "list", "watch"},
    },
    // 可以读取绑定到本节点的ConfigMap和Secret
    {
        APIGroups: []{""},
        Resources: []{"configmaps", "secrets"},
        Verbs:     []{"get"},
    },
    // 可以操作绑定到本节点的Pod
    {
        APIGroups: []{""},
        Resources: []{"pods"},
        Verbs:     []{"get", "list", "watch", "create", "update", "patch", "delete"},
    },
    // ... 其他规则
}
Pod-Node关系图

Node鉴权器内部维护了一个Pod-Node关系图,用于快速判断Pod是否属于某个节点:

type Graph struct {
    lock        sync.RWMutex
    nodes       map[types.UID]node
    pods        map[types.UID]pod
    configmaps  map[types.UID]configmap
    secrets     map[types.UID]secret
}

type pod struct {
    namespace     string
    name          string
    uid           types.UID
    nodeUID       types.UID        // 绑定的Node
    serviceAccount string
    secrets       []types.UID      // 引用的Secret
    configmaps    []types.UID      // 引用的ConfigMap
}

关系图的作用

  • Pod调度到Node时,更新关系图
  • kubelet请求时,快速检查Pod是否属于该节点
  • 避免每次都查询etcd

2. RBAC鉴权模式

基于角色的访问控制(Role-Based Access Control),K8s最主要的鉴权方式。

RBAC的核心概念
┌─────────────────────────────────────────────────────────────────┐
│                         RBAC模型                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   用户(User) ──绑定──→ 角色(Role) ──定义──→ 权限(Permission)     │
│                         │                                        │
│                         │                                        │
│                   ┌─────┴─────┐                                  │
│                   ↓           ↓                                  │
│               Role      ClusterRole                             │
│            (namespace级)  (集群级)                               │
│                   │           │                                  │
│                   ↓           ↓                                  │
│            RoleBinding   ClusterRoleBinding                     │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
RBAC鉴权器的实现
// pkg/registry/rbac/authorizer/rbac.go

type RBACAuthorizer struct {
    roleGetter             roleLister
    roleBindingLister      roleBindingLister
    clusterRoleGetter      clusterRoleLister
    clusterRoleBindingLister clusterRoleBindingLister
}

func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
    // 获取用户和规则
    user := requestAttributes.GetUser()
    namespace := requestAttributes.GetNamespace()
    
    // 1. 获取用户绑定的所有角色
    rules, err := r.GetEffectiveRules(ctx, user, namespace)
    if err != nil {
        return authorizer.DecisionNoOpinion, "", err
    }
    
    // 2. 遍历规则,检查是否有匹配的
    for _, rule := range rules {
        if RuleMatchesRequest(rule, requestAttributes) {
            return authorizer.DecisionAllow, "", nil
        }
    }
    
    // 没有匹配的规则,返回NoOpinion
    return authorizer.DecisionNoOpinion, "", nil
}
规则匹配逻辑
// RuleMatchesRequest 检查规则是否匹配请求
func RuleMatchesRequest(rule rbacv1.PolicyRule, requestAttributes authorizer.Attributes) bool {
    // 1. 检查Verb
    if !VerbMatches(rule, requestAttributes.GetVerb()) {
        return false
    }
    
    // 2. 检查APIGroup
    if !APIGroupMatches(rule, requestAttributes.GetAPIGroup()) {
        return false
    }
    
    // 3. 检查Resource
    if !ResourceMatches(rule, requestAttributes.GetResource(), requestAttributes.GetSubresource()) {
        return false
    }
    
    // 4. 检查ResourceName(如果指定了)
    if !ResourceNameMatches(rule, requestAttributes.GetName()) {
        return false
    }
    
    // 5. 检查NonResourceURL(非资源请求)
    if !requestAttributes.IsResourceRequest() {
        if !NonResourceURLMatches(rule, requestAttributes.GetPath()) {
            return false
        }
    }
    
    return true
}
Role和RoleBinding示例
# Role定义权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]

---
# RoleBinding绑定用户和角色
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User
  name: jane
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

3. ABAC鉴权模式

基于属性的访问控制(Attribute-Based Access Control),已不推荐使用的鉴权方式。

ABAC配置示例
// abac-policy.jsonl
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "admin", "namespace": "*", "resource": "*", "apiGroup": "*", "nonResourcePath": "*"}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "kubelet", "namespace": "*", "resource": "pods", "readonly": true}}

缺点

  • 配置文件难以管理
  • 不支持动态更新
  • 粒度不够细

推荐使用RBAC替代ABAC

4. Webhook鉴权模式

将鉴权委托给外部HTTP服务

工作原理
apiserver ──POST──→ Webhook Server
            │         (外部鉴权服务)
            │←────JSON───────
            │   {allowed: true, reason: ""}
            ▼
        允许/拒绝
Webhook请求格式
{
  "apiVersion": "authorization.k8s.io/v1",
  "kind": "SubjectAccessReview",
  "spec": {
    "user": "jane",
    "group": ["group1", "group2"],
    "resourceAttributes": {
      "namespace": "default",
      "verb": "create",
      "group": "",
      "version": "v1",
      "resource": "pods"
    }
  }
}
Webhook响应格式
{
  "apiVersion": "authorization.k8s.io/v1",
  "kind": "SubjectAccessReview",
  "status": {
    "allowed": true,
    "reason": "user has permission"
  }
}
配置Webhook鉴权
kube-apiserver \
  --authorization-webhook-config-file=/etc/kubernetes/webhook-authz.yaml \
  --authorization-webhook-cache-authorized-ttl=5m \
  --authorization-webhook-cache-unauthorized-ttl=30s

鉴权失败处理

HTTP 403 Forbidden

当所有鉴权器都返回DecisionDenyDecisionNoOpinion时,apiserver返回403:

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "pods is forbidden: User \"jane\" cannot create resource \"pods\" in API group \"\" in the namespace \"default\"",
  "reason": "Forbidden",
  "details": {
    "kind": "pods"
  },
  "code": 403
}

安全最佳实践

1. 启用Node和RBAC模式

kube-apiserver \
  --authorization-mode=Node,RBAC

原因

  • Node模式限制kubelet只能操作自己的Pod
  • RBAC提供细粒度的权限控制

2. 遵循最小权限原则

# 不好的做法:给所有权限
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]

# 好的做法:只给需要的权限
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]  # 只读权限

3. 避免使用cluster-admin

# 不要这样做!
subjects:
- kind: User
  name: developer
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin  # 太危险了!

4. 定期审计权限

# 查看谁有cluster-admin权限
kubectl get clusterrolebindings -o yaml | grep cluster-admin

# 查看ServiceAccount的权限
kubectl auth can-i --list --as=system:serviceaccount:default:my-sa

5. 使用Webhook对接企业IAM

# 对接企业统一身份管理
kube-apiserver \
  --authorization-mode=Node,RBAC,Webhook \
  --authorization-webhook-config-file=/etc/kubernetes/webhook.yaml

踩坑实录:鉴权常见问题

坑1:权限不生效

现象:配置了Role和RoleBinding,但用户仍然无法访问资源

排查步骤

# 1. 检查RoleBinding是否正确
kubectl get rolebinding -n default -o yaml

# 2. 使用auth can-i检查
kubectl auth can-i get pods -n default --as=jane

# 3. 检查用户身份
kubectl auth whoami  # 1.20+支持

# 4. 查看apiserver日志
journalctl -u kube-apiserver | grep jane

坑2:Node模式导致kubelet无法访问

现象:kubelet报错cannot get pod,但RBAC已配置

根因:没有启用Node模式,或者Node模式在RBAC之后

解决方案

# Node模式必须在RBAC之前
--authorization-mode=Node,RBAC  # 正确
--authorization-mode=RBAC,Node  # 可能有问题

坑3:ClusterRole在namespace级资源上不生效

现象:配置了ClusterRole,但在特定namespace中不生效

根因:ClusterRole需要通过ClusterRoleBinding或RoleBinding绑定

解决方案

# ClusterRole + RoleBinding(在特定namespace生效)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: my-binding
  namespace: production  # 只在production namespace生效
roleRef:
  kind: ClusterRole
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: User
  name: jane

坑4:Webhook鉴权延迟高

现象:开启Webhook鉴权后,API响应变慢

解决方案

# 增加缓存时间
--authorization-webhook-cache-authorized-ttl=10m
--authorization-webhook-cache-unauthorized-ttl=1m

坑5:system:authenticated权限过大

现象:所有认证用户都能执行某些操作

根因:RoleBinding绑定了system:authenticated

解决方案

# 避免这样做
subjects:
- kind: Group
  name: system:authenticated  # 所有认证用户!太宽泛了

总结

通过今天的分析,我们深入理解了kube-apiserver的鉴权机制:

  1. 鉴权vs认证:认证确认身份,鉴权确认权限
  2. 4种鉴权模式:Node、RBAC、ABAC、Webhook
  3. Union模式:按顺序执行,明确决策立即返回
  4. RBAC核心:Role/ClusterRole定义权限,RoleBinding/ClusterRoleBinding绑定用户
  5. 安全实践:最小权限、避免cluster-admin、定期审计

鉴权是K8s安全的核心,理解它才能做好权限管控。下一篇我们将深入解析Node鉴权模式的实现细节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加倍巴巴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值