侧边栏壁纸
博主头像
汪洋

即使慢,驰而不息,纵会落后,纵会失败,但一定可以达到他所向的目标。 - 鲁迅

  • 累计撰写 220 篇文章
  • 累计创建 83 个标签
  • 累计收到 271 条评论

Kubernetes - Authentication Webhook

汪洋
2025-08-20 / 0 评论 / 0 点赞 / 16 阅读 / 7,579 字

在 Kubernetes 生态中,除了最常见的 Validating Webhook(验证型准入 Webhook)和 Mutating Webhook(变异型准入 Webhook),还有其他几种基于 Webhook 机制的扩展方式,主要用于 Kubernetes 的认证、授权或特定资源的生命周期管理。以下是主要相关类型:

1、Authentication Webhooks(认证 Webhook)

作用:用于验证用户身份(替代传统的客户端证书、密码等认证方式)。当 Kubernetes API Server 收到请求时,会调用外部 Webhook 服务验证请求的身份合法性(例如检查令牌是否有效)。

场景:集成外部身份系统(如 LDAP、OAuth2、企业单点登录 SSO 等),让 Kubernetes 支持自定义身份认证逻辑。

流程:请求到达 API Server 后,首先进入认证阶段,认证 Webhook 返回用户信息(如用户名、UID、组),API Server 根据返回结果判断是否通过认证。

2、Authorization Webhooks(授权 Webhook)

作用:用于判断已认证用户是否有权限执行某个操作(例如 “用户 A 是否能删除 namespace B 中的 Pod”)。

场景:当 Kubernetes 内置的授权策略(如 RBAC、Node 授权)不足以满足需求时,通过外部 Webhook 实现复杂的权限逻辑(如基于属性的访问控制 ABAC、多租户权限隔离等)。

流程:认证通过后,API Server 进入授权阶段,调用授权 Webhook 检查操作权限,Webhook 返回 “允许” 或 “拒绝” 的决策。

3、Conversion Webhooks(转换 Webhook)

作用:用于自定义资源(CRD)的版本转换。当 CRD 存在多个 API 版本(如 v1alpha1 升级到 v1beta1)时,转换 Webhook 可以定义不同版本之间的字段映射逻辑,确保资源在版本切换时正确转换。

场景:CRD 多版本管理,避免手动处理版本兼容问题。例如,当用户提交 v1alpha1 版本的 CR 时,Webhook 自动转换为 v1beta1 版本供后端处理。

4、Dynamic Admission Control 中的其他细分场景
虽然核心是 Mutating 和 Validating Webhook,但它们可以针对不同资源类型(如 Pod、Deployment、自定义 CR 等)或操作(创建、更新、删除)进行细分,例如:

  • 针对 Pod 调度前验证 的 Webhook(检查资源是否满足调度条件);
  • 针对 Secret 变更审计 的 Webhook(记录 Secret 修改操作);
  • 针对 命名空间生命周期 的 Webhook(限制特定命名空间的创建)。

总结:不同 Webhook 的阶段与用途

Kubernetes 对请求的处理流程分为三个核心阶段,对应不同的 Webhook 类型:

  • 认证(Authentication):确认 “谁在请求”→ 由 Authentication Webhook 处理;
  • 授权(Authorization):确认 “是否允许操作”→ 由 Authorization Webhook 处理;
  • 准入控制(Admission):确认 “操作的资源是否合法 / 是否需要修改”→ 由 Mutating Webhook(修改资源)和 Validating Webhook(验证资源)处理;
  • CRD 版本转换:处理自定义资源的版本兼容→ 由 Conversion Webhook 处理。

这些 Webhook 共同构成了 Kubernetes 的扩展机制,允许用户通过外部服务自定义集群的安全策略、资源管理逻辑和身份体系。

下面我将实现一个 Authentication Webhook 案例,用于验证 Kubernetes API 请求中的 Bearer Token(令牌)是否有效。这个 Webhook 会检查令牌是否在预设的 “允许列表” 中,若有效则返回对应的用户信息(用户名、组),否则拒绝认证。

一、案例实现

1、webhook 服务代码

package main

import (
        "encoding/json"
        "log"
        "net/http"

        authenticationv1 "k8s.io/api/authentication/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// 预设的有效令牌列表(key: token, value: 对应的用户信息)
var validTokens = map[string]struct {
        Username string
        Groups   []string
}{
        "valid-token-123": {
                Username: "admin",
                Groups:   []string{"system:masters"}, // 管理员组(拥有最高权限)
        },
        "valid-token-456": {
                Username: "developer",
                Groups:   []string{"system:developers"}, // 开发者组
        },
}

func main() {
        // 处理认证请求的端点
        http.HandleFunc("/authenticate", authenticateHandler)
        log.Println("Starting authentication webhook server on :443")

        // 生产环境需使用合法 TLS 证书
        if err := http.ListenAndServeTLS(":443", "/etc/tls/tls.crt", "/etc/tls/tls.key", nil); err != nil {
                log.Fatalf("Failed to start server: %v", err)
        }
}

// 处理认证请求
func authenticateHandler(w http.ResponseWriter, r *http.Request) {
        var review authenticationv1.TokenReview
        if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
                log.Printf("Error decoding request: %v", err)
                http.Error(w, "Invalid request body", http.StatusBadRequest)
                return
        }

        // 解析请求中的 Bearer Token
        token := review.Spec.Token
        if token == "" {
                sendAuthResponse(w, review.Spec, false, "", nil)
                return
        }

        // 验证令牌是否在有效列表中
        userInfo, isValid := validTokens[token]
        if !isValid {
                log.Printf("Invalid token: %s", token)
                sendAuthResponse(w, review.Spec, false, "", nil)
                return
        }

        // 令牌有效,返回用户信息
        log.Printf("Valid token for user: %s", userInfo.Username)
        sendAuthResponse(w, review.Spec, true, userInfo.Username, userInfo.Groups)
}

// 发送认证响应
func sendAuthResponse(
        w http.ResponseWriter,
        spec authenticationv1.TokenReviewSpec,
        authenticated bool,
        username string,
        groups []string,
) {
        response := authenticationv1.TokenReview{
                TypeMeta: metav1.TypeMeta{
                        APIVersion: "authentication.k8s.io/v1",
                        Kind:       "TokenReview",
                },
                Spec: spec,
                Status: authenticationv1.TokenReviewStatus{
                        Authenticated: authenticated,
                        User: authenticationv1.UserInfo{
                                Username: username,
                                Groups:   groups,
                        },
                },
        }

        w.Header().Set("Content-Type", "application/json")
        if err := json.NewEncoder(w).Encode(response); err != nil {
                log.Printf("Error encoding response: %v", err)
                http.Error(w, "Error sending response", http.StatusInternalServerError)
        }
}

2、webhook 容器化

FROM docker.cnb.cool/wangyanglinux/docker-images-chrom/alpine:3.17

WORKDIR /root/

# 从构建阶段复制二进制文件

COPY ./authen /root

RUN mkdir  /etc/tls

# 暴露端口
EXPOSE 443

# 运行应用
CMD ["./authen"]

本项目镜像已经保存至阿里云镜像平台:registry.cn-hangzhou.aliyuncs.com/wangyangshare/share:authwebhook

3、生成 API Server 与 Webhook 通信需要的 HTTPS 证书

创建 openssl.cnf 配置文件(确保包含 SAN)

[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext

[dn]
CN = auth-webhook-svc.default.svc

[req_ext]
subjectAltName = DNS:auth-webhook-svc.default.svc

生成证书

# 生成 CA 和服务器证书
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -days 3650 -out ca.crt -subj "/CN=auth-ca"
openssl genrsa -out tls.key 2048
openssl req -new -key tls.key -out tls.csr -config openssl.cnf
openssl x509 -req -in tls.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out tls.crt -days 3650 -extensions req_ext -extfile openssl.cnf

创建 Secret 存储证书

kubectl create secret tls auth-webhook-tls --cert=tls.crt --key=tls.key

4、部署服务至集群

apiVersion: apps/v1
kind: Deployment
metadata:
  name: auth-webhook
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: auth-webhook
  template:
    metadata:
      labels:
        app: auth-webhook
    spec:
      containers:
      - name: auth-webhook
        image: k8s-auth-webhook:latest  # 替换为你的镜像
        ports:
        - containerPort: 443
        volumeMounts:
        - name: tls-cert
          mountPath: /etc/tls
          readOnly: true
      volumes:
      - name: tls-cert
        secret:
          secretName: auth-webhook-tls
---
apiVersion: v1
kind: Service
metadata:
  name: auth-webhook-svc
  namespace: default
spec:
  selector:
    app: auth-webhook
  ports:
  - port: 443
    targetPort: 443

5、配置 API Server 使用 Authentication Webhook

Kubernetes API Server 通过启动参数或配置文件启用 Authentication Webhook。以下是具体步骤

1)建 Webhook 配置文件(auth-config.yaml)

apiVersion: v1
kind: Config
preferences: {}
clusters:
- name: auth-webhook-cluster
  cluster:
    certificate-authority: /etc/kubernetes/pki/auth-ca.crt  # 挂载的 CA 证书路径
    server: https://auth-webhook-svc.default.svc:443/authenticate  # Webhook 服务地址

users:
- name: apiserver
  user:
    client-certificate: /etc/kubernetes/pki/apiserver.crt  # API Server 证书(可选,用于双向认证)
    client-key: /etc/kubernetes/pki/apiserver.key

contexts:
- name: auth-webhook-context
  context:
    cluster: auth-webhook-cluster
    user: apiserver

current-context: auth-webhook-context

2)挂载配置到 API Server

  • 将 ca.crt 复制到 API Server 节点的证书目录(如 /etc/kubernetes/pki/auth-ca.crt)
  • 将 auth-config.yaml 复制到 API Server 配置目录(如 /etc/kubernetes/auth-config.yaml)
  • 修改 API Server 的启动参数(通常在 /etc/kubernetes/manifests/kube-apiserver.yaml),添加
spec:
  containers:
  - command:
    - kube-apiserver
    # 新增以下参数
    - --authentication-token-webhook-config-file=/etc/kubernetes/auth-config.yaml
    - --authentication-token-webhook-version=v1
volumeMounts:
- name: auth-config
  mountPath: /etc/kubernetes/auth-config.yaml  # 直接挂载文件到容器内的路径
  readOnly: true  # 建议添加只读权限(配置文件无需写入)
- name: auth-ca
  mountPath: /etc/kubernetes/pki/auth-ca.crt  # 同理,删除 subPath
  readOnly: true

# volumes 部分保持不变(确保主机上的文件存在)
volumes:
- name: auth-config
  hostPath:
    path: /etc/kubernetes/auth-config.yaml
    type: File
- name: auth-ca
  hostPath:
    path: /etc/kubernetes/pki/auth-ca.crt
    type: File

3)重启 API Server(修改 kube-apiserver.yaml 后会自动重启)

6、测试

1)使用有效令牌访问 API

# 用有效令牌(如 valid-token-123)发送请求
curl -k -H "Authorization: Bearer valid-token-123" https://<API-Server-IP>:6443/api/v1/pods

预期结果:请求成功,返回 Pod 列表(因为 admin 属于 system:masters 组,有权限)。

2)使用无效令牌访问 API

curl -k -H "Authorization: Bearer invalid-token" https://<API-Server-IP>:6443/api/v1/pods

预期结果:返回 401 Unauthorized 错误,认证失败。

3)如果不能显示预期结果,可能原因如下

  • 确定是否出现了域名解析不了的情况,有可能是因为 master 节点没有采用集群内的 DNS 实现解析导致的,因为 apiserver 采用的是主机网络实现访问,会使用物理机对应的 DNS 配置 。 可以通过:kubectl logs -n kube-system kube-apiserver-k8s-master01 | grep -i "auth-webhook-svc" 查看
    • 添加 DNS 解析记录至 master 节点的 /etc/hosts 文件中:10.97.77.91 auth-webhook-svc.default.svc( IP 为对应 svc 的 CLUSTERIP 地址)
    • 修改 master DNS 备为集群内 DNS 服务器

4)通过 kubectl 测试

配置 kubectl 使用令牌:

创建一个使用有效令牌的 kubeconfig
kubectl config set-credentials test-user --token=valid-token-123
kubectl config set-context test-context --cluster=kubernetes --user=test-user
kubectl config use-context test-context

执行命令(应成功)

kubectl get pods
0

评论区