前言
去年一次生产事故让我至今记忆犹新:一个开发同事为了调试方便,给自己账号绑定了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 // 无意见(无法判断,让下一个鉴权器处理)
)
重要:
DecisionDeny和DecisionAllow都是明确的决策,会立即返回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
当所有鉴权器都返回DecisionDeny或DecisionNoOpinion时,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的鉴权机制:
- 鉴权vs认证:认证确认身份,鉴权确认权限
- 4种鉴权模式:Node、RBAC、ABAC、Webhook
- Union模式:按顺序执行,明确决策立即返回
- RBAC核心:Role/ClusterRole定义权限,RoleBinding/ClusterRoleBinding绑定用户
- 安全实践:最小权限、避免cluster-admin、定期审计
鉴权是K8s安全的核心,理解它才能做好权限管控。下一篇我们将深入解析Node鉴权模式的实现细节。

2132

被折叠的 条评论
为什么被折叠?



