理解K8s动态准入控制器-基于Admission Webhook实现Sidecar自动注入检验等

K8s 的准入控制器

准入控制器是 Kubernetes API Server 中的一组插件,在对象持久化到 etcd 之前拦截 API 请求,用于强制执行策略和修改对象。

🔵 流程步骤详解
1.API请求接收与初步处理​

Kubernetes API Server接收到创建/修改资源的请求(如Pod创建)。请求先经过身份认证(Authentication)和权限校验(Authorization),失败则立即拒绝。

2.Mutating Admission阶段​

  • API Server根据配置的MutatingWebhookConfiguration,筛选匹配的Webhook,并构建AdmissionReview请求(包含资源对象、操作类型、用户信息等)。
  • 请求通过HTTP POST发送至Webhook服务。Webhook返回AdmissionReview响应,其中可能包含JSONPatch用于修改资源(如注入Sidecar容器)。
  • 关键特性:此阶段可串行调用多个Webhook,需确保处理逻辑的幂等性。

3.Schema校验与Validating Admission阶段​

  • 资源修改后,API Server执行内置的Object Schema Validation,验证资源结构的合法性。
  • 进入Validating Admission阶段,API Server根据ValidatingWebhookConfiguration构建新的AdmissionReview请求,发送至验证型Webhook。Webhook检查业务规则(如资源配额、安全策略),并返回允许/拒绝结论。
  • Validating Webhook不能修改资源,且调用是并行的。

4.最终决策与持久化​

  • 所有Validating Webhook均通过后,资源被持久化到ETCD,请求成功完成。
  • 任意阶段拒绝:若Mutating或Validating阶段中任一Webhook返回拒绝,请求立即终止,错误返回给用户。

🔵准入控制中的流程图


🔵 典型应用场景

  • 自动Sidecar注入(如Istio):Mutating Webhook通过AdmissionReview向Pod添加容器。
  • 安全策略强制:Validating Webhook检查Pod是否禁止特权模式,违规则拒绝请求。
  • 资源默认值设置:Mutating Webhook为未声明CPU需求的Pod添加默认值。

AdmissionReview​ 是 Kubernetes 准入控制 Webhook 中的核心 API 对象,用于在 API 服务器和外部 Webhook 之间传递准入请求和响应。它是 Kubernetes 动态准入控制流程的标准数据结构。

当 Kubernetes API 服务器需要外部 Webhook 对资源操作(CREATE/UPDATE/DELETE)做准入决策时,会将请求包装成 AdmissionReview 发送给 WebhookWebhook 处理完成后,也必须返回一个 AdmissionReview对象作为响应。

AdmissionReview 的数据结构

yaml 复制代码
apiVersion: admission.k8s.io/v1    # API 版本
kind: AdmissionReview              # 固定资源类型
request:                           # 请求部分(API Server → Webhook)
  uid: string                      # 唯一请求标识,必须原样返回
  kind: GroupVersionKind           # 资源类型信息
  resource: GroupVersionResource   # 资源详情
  namespace: string                # 请求的命名空间
  operation: string                # 操作类型:CREATE/UPDATE/DELETE
  object: RawExtension             # 新资源对象(JSON)
  oldObject: RawExtension          # 旧资源对象(更新/删除时)
  options: RawExtension            # 操作选项
  userInfo: UserInfo               # 请求用户信息
response:                          # 响应部分(Webhook → API Server)
  uid: string                      # 必须与 request.uid 一致
  allowed: boolean                 # 是否允许操作
  patchType: string                # 补丁类型,如 JSONPatch
  patch: []byte                    # 补丁内容(Base64编码)
  status: Status                   # 错误信息(当 allowed=false 时)

实践

1.自动Sidecar注入

Sidecar-inject.go

go 复制代码
package main

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

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

type AdmissionReview struct { 
	APIVersion string  `json:"apiVersion"`
	Kind string		`json:"kind"`
	Request *AdmissionRequest `json:"request,omitempty"`
	Response *AdmissionResponse `json:"response,omitempty"`
}
type AdmissionResponse struct {
	UID string `json:"uid"`
	Allowed bool `json:"allowed"`
	Patch   []byte `json:"patch,omitempty"`
	PatchType *string `json:"patchType,omitempty"`
	Status *Status `json:"status,omitempty"`
}
type AdmissionRequest struct {
	UID       string      `json:"uid" yaml:"uid"`
	Kind      metav1.GroupVersionKind `json:"kind" yaml:"kind"`
	Resource  metav1.GroupVersionResource `json:"resource" yaml:"resource"`
	Name      string      `json:"name,omitempty" yaml:"name,omitempty"`
	Namespace string      `json:"namespace,omitempty" yaml:"namespace,omitempty"`
	RequestKind metav1.GroupVersionKind `json:"requestKind" yaml:"requestKind"`
	RequestResource metav1.GroupVersionResource `json:"requestResource" yaml:"requestResource"`
	RequestSubResource string `json:"requestSubResource" yaml:"requestSubResource"`
	Operation string      `json:"operation" yaml:"operation"`
	UserInfo  authenticationv1.UserInfo `json:"userInfo" yaml:"userInfo"`
	Object    interface{} `json:"object,omitempty" yaml:"object,omitempty"`
	OldObject interface{} `json:"oldObject,omitempty" yaml:"oldObject,omitempty"`
}

type Status struct {
	Message string `json:"message,omitempty"`
	Code int32 `json:"code"`
}

func Mutate_admission(w http.ResponseWriter, r *http.Request) { 
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Printf("Error reading body: %v", err)
		http.Error(w, "can't read body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()
	log.Printf("Received admission request: %s", string(body))
	var review AdmissionReview
	err = json.Unmarshal(body, &review)
	if err != nil {
		log.Printf("Error unmarshalling request: %v", err)
		http.Error(w, "can't unmarshal request", http.StatusBadRequest)
		return
	}
	responseVersion:= "admission.k8s.io/v1"
	if review.APIVersion != "" {
		responseVersion = review.APIVersion
	}
	patch :=[]map[string]interface{}{
		{
			"op": "add",
			"path": "/metadata/annotations",
			"value": map[string]string{
				"kubectl.kubernetes.io/last-applied-configuration": "injected-test",
			},
		},
		{
			"op": "add",
			"path": "/spec/containers/-",
			"value": map[string]interface{}{
				"image": "nginx:1.19.9",
				"imagePullPolicy": "IfNotPresent",
				"name": "nginx-injected",
				"ports": []map[string]interface{}{
					{
						"containerPort": 80,
						"name": "http",
						"protocol": "TCP",
					},
				},
			},
		},
	}
	patchBytes, err := json.Marshal(patch)
	if err != nil {
		log.Printf("Error marshalling patch: %v", err)
		http.Error(w, "can't marshal patch", http.StatusInternalServerError)
		return
	}
	patchType := "JSONPatch"
	resp := &AdmissionReview{
		APIVersion: responseVersion,
		Kind: "AdmissionReview",
		Response: &AdmissionResponse{
			Allowed: true,
			Patch: patchBytes,
			PatchType: &patchType,
			UID: review.Request.UID,
		},
	}
	jsonBytes, err := json.Marshal(resp)
	if err != nil {
		log.Printf("Error marshalling response: %v", err)
		http.Error(w, "can't marshal response", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(jsonBytes)
	log.Printf("Sending response: %s", string(jsonBytes))
	

}



func main() {
  http.HandleFunc("/mutate", Mutate_admission)
  log.Println("Server started")
  log.Fatal(http.ListenAndServeTLS(":8443", "tls.crt", "tls.key", nil))
}

创建证书

shell 复制代码
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[ req_distinguished_name ]
CN = inject-webhook.default.svc

[ v3_req ]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = inject-webhook
DNS.2 = inject-webhook.default
DNS.3 = inject-webhook.default.svc
powershell 复制代码
openssl req -x509 -newkey rsa:2048 -keyout tls.key -out tls.crt -days 3650 -nodes -config tls.cnf -extensions v3_req

MutatingWebhookConfiguration

powershell 复制代码
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: sidecar-injector
webhooks:
- name: inject-webhook.default.svc
  clientConfig:
    service:
      name: inject-webhook
      namespace: default
      path: "/mutate"
      port: 8443
    #caBundle的值为cat tls.crt | base64 | tr -d '\n'
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURiRENDQWxTZ0F3SUJBZ0lVQisrSXRqa0gvbDhuQU1aRkxXeTZGdmJ3Sndnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0pURWpNQ0VHQTFVRUF3d2FhVzVxWldOMExYZGxZbWh2YjJzdVpHVm1ZWFZzZEM1emRtTXdIaGNOTWpZdwpNVEEzTURrMU9ERXlXaGNOTXpZd01UQTFNRGsxT0RFeVdqQWxNU013SVFZRFZRUUREQnBwYm1wbFkzUXRkMlZpCmFHOXZheTVrWldaaGRXeDBMbk4yWXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUIKQUswVEhpZlg3ZTRiUmN3ajZZT2h5ME10Y2R5Z2dNQlNJUHBUQ0dxSlVEemVENllvRElIM3FLcmw5TnozSXFlTwpMQ3JrRnkwVzIzbVFuMUs0Y0R1RVI5N1kxQUd1Wm5YMXQyclhmRjZhOC85d2xpa3pvejlVTEZ1V1RVTmJTWXMzCmJtclhYUnRKZUFDT2w5WHVqMFM4OHdkeStSdVhjdjFESHlSRjJjdGo3Y0VjaWI3OVlVd3hSN2pGeG94dE00T2MKVi83eWdHQmJTYnRMM2syMHV1Vm1TbS81SFU2dEJnN0xSSGpnejFPK0hIRnY1aVREb3VVT1VNNkRVYU02aUpaSgpGSFJuQlpvaXAzK2xZb29BTjZYL29rUHJRQTVsbDQ3Ui9jZnNhbmpTZ1JBVVA3N1RXWjcxazdHMUV6Z003TGZmCjVMWXJGYXRnTldpWnBUSUNiOWtGY3pzQ0F3RUFBYU9Ca3pDQmtEQUxCZ05WSFE4RUJBTUNCREF3RXdZRFZSMGwKQkF3d0NnWUlLd1lCQlFVSEF3RXdUUVlEVlIwUkJFWXdSSUlPYVc1cVpXTjBMWGRsWW1odmIydUNGbWx1YW1WagpkQzEzWldKb2IyOXJMbVJsWm1GMWJIU0NHbWx1YW1WamRDMTNaV0pvYjI5ckxtUmxabUYxYkhRdWMzWmpNQjBHCkExVWREZ1FXQkJSZnZBS0ZzbVZwU0c0Vk1oWEFxZ2NsN0ZQSFdEQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUEKa0JXRDRXanYwdk5uTUFQb3d6Umc1VnJzOWVHL2xMcUYwNE4zV3BVU3pwNjcvM2p1TlJWY3NGQ2xkODFmRTJPUwpveXFyMXJtWFJBeDVCUFpNaWxZMjhGVi84dXVIQnVQZi9kS3Voc1VaTm90K2d6Y3BpbE52K2RkOXQxSVBkVkRsCkFjRU9mNWgyYjZsSzlMbXFNTGNDakxnbmczdnZGaXdWcGM0NzA1MDIwWk4yZnRNa3hORmhHUUxvYWZnNmJENDYKekZLVzNCT3FucUt6b04rZXFvL3RydXZjUWNOVUpsSGlvQnJscDgyaU54UUxNb2g3ekVvMTN0Q3l5VFZwbFhtUgpncndXQlJZekh5QnN3ZnlydnhjdzJqLytVZVU5QjczaEk4NVNTNmxWWUdZdmEvdjFRN1dlQ0pYMmJjZG1WTzNWCk9vUWRRcDArMERDK3N3N1BDWXUwTWc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  rules:
  - operations: ["CREATE"]
    apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
  failurePolicy: Fail
  sideEffects: None
  admissionReviewVersions: ["v1"]

webhook运行在k8s集群外,在k8s集群创建svc,指向外部的webhook

bash 复制代码
apiVersion: v1
kind: Service
metadata:
  name: inject-webhook
spec:
  ports:
  - port: 8443
    targetPort: 8443
    protocol: TCP
  type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
  name: inject-webhook
  namespace: default
subsets:
- addresses:
  - ip: 172.16.0.27
  ports:
  - port: 8443
    protocol: TCP

创建pod 测试

bash 复制代码
kubectl run test-inject --image=redis

验证

检查containers与annotations里面的内容

2.资源默认值设置

修改逻辑,添加添加默认resource配置

go 复制代码
func Mutate_admission(w http.ResponseWriter, r *http.Request) { 
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Printf("Error reading body: %v", err)
		http.Error(w, "can't read body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()
	log.Printf("Received admission request: %s", string(body))
	var review AdmissionReview
	err = json.Unmarshal(body, &review)
	if err != nil {
		log.Printf("Error unmarshalling request: %v", err)
		http.Error(w, "can't unmarshal request", http.StatusBadRequest)
		return
	}
	responseVersion:= "admission.k8s.io/v1"
	if review.APIVersion != "" {
		responseVersion = review.APIVersion
	}
	
	// 添加注解
	patch :=[]map[string]interface{}{
		{
			"op": "add",
			"path": "/metadata/annotations",
			"value": map[string]string{
				"kubectl.kubernetes.io/last-applied-configuration": "injected-test",
			},
		},
	}
	
	// 检查并自动添加资源限制到现有容器
	if review.Request != nil && review.Request.Object != nil {
		if objMap, ok := review.Request.Object.(map[string]interface{}); ok {
			if spec, ok := objMap["spec"].(map[string]interface{}); ok {
				if containers, ok := spec["containers"].([]interface{}); ok {
					// 遍历所有现有容器,检查是否已配置resources
					for idx, container := range containers {
						if containerMap, ok := container.(map[string]interface{}); ok {
							// 检查当前容器是否已配置resources,包括空的resources
							resources, exists := containerMap["resources"]
							if !exists || resources == nil || isEmptyResources(resources) {
								// 为没有配置resources的现有容器添加默认配置
								patch = append(patch, map[string]interface{}{
									"op": "add",
									"path": "/spec/containers/" + strconv.Itoa(idx) + "/resources",
									"value": map[string]interface{}{
										"requests": map[string]interface{}{
											"cpu": "10m",
											"memory": "20Mi",
										},
										"limits": map[string]interface{}{
											"cpu": "50m",
											"memory": "50Mi",
										},
									},
								})
							}
						}
					}
				}
			}
		}
	}
	
	// 最后添加 注入 容器
	patch = append(patch, map[string]interface{}{
		"op": "add",
		"path": "/spec/containers/-",
		"value": map[string]interface{}{
			"image": "nginx:1.19.9",
			"imagePullPolicy": "IfNotPresent",
			"name": "nginx-injected",
			"ports": []map[string]interface{}{
				{
					"containerPort": 80,
					"name": "http",
					"protocol": "TCP",
				},
			},
			"resources": map[string]interface{}{
				"requests": map[string]interface{}{
					"cpu": "10m",
					"memory": "20Mi",
				},
				"limits": map[string]interface{}{
					"cpu": "50m",
					"memory": "50Mi",
				},
			},
		},
	})

	patchBytes, err := json.Marshal(patch)
	if err != nil {
		log.Printf("Error marshalling patch: %v", err)
		http.Error(w, "can't marshal patch", http.StatusInternalServerError)
		return
	}
	patchType := "JSONPatch"
	resp := &AdmissionReview{
		APIVersion: responseVersion,
		Kind: "AdmissionReview",
		Response: &AdmissionResponse{
			Allowed: true,
			Patch: patchBytes,
			PatchType: &patchType,
			UID: review.Request.UID,
		},
	}
	jsonBytes, err := json.Marshal(resp)
	if err != nil {
		log.Printf("Error marshalling response: %v", err)
		http.Error(w, "can't marshal response", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(jsonBytes)
	log.Printf("Sending response: %s", string(jsonBytes))
	

}

// 检查resources是否为空
func isEmptyResources(resources interface{}) bool {
	if resources == nil {
		return true
	}
	
	resMap, ok := resources.(map[string]interface{})
	if !ok {
		return true
	}
	
	// 检查map是否为空
	if len(resMap) == 0 {
		return true
	}
	
	// 检查map的值是否都是空的
	hasRequests := false
	hasLimits := false
	
	if requests, exists := resMap["requests"]; exists && requests != nil {
		if reqMap, ok := requests.(map[string]interface{}); ok && len(reqMap) > 0 {
			hasRequests = true
		}
	}
	
	if limits, exists := resMap["limits"]; exists && limits != nil {
		if limMap, ok := limits.(map[string]interface{}); ok && len(limMap) > 0 {
			hasLimits = true
		}
	}
	
	return !hasRequests && !hasLimits
}

3.Validating Webhook检查Pod是否禁止特权模式,违规则拒绝请求。

validate_privileged.go

go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

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

type AdmissionReview struct {
	APIVersion string `json:"apiVersion"`
	Kind       string `json:"kind"`
	Request    *AdmissionRequest  `json:"request,omitempty"`
	Response   *AdmissionResponse `json:"response,omitempty"`
}

type AdmissionResponse struct {
	UID     string  `json:"uid"`
	Allowed bool    `json:"allowed"`
	Result  *Status `json:"status,omitempty"`
}

type AdmissionRequest struct {
	UID       string                                    `json:"uid" yaml:"uid"`
	Kind      metav1.GroupVersionKind                   `json:"kind" yaml:"kind"`
	Resource  metav1.GroupVersionResource               `json:"resource" yaml:"resource"`
	Name      string                                    `json:"name,omitempty" yaml:"name,omitempty"`
	Namespace string                                    `json:"namespace,omitempty" yaml:"namespace,omitempty"`
	RequestKind metav1.GroupVersionKind                `json:"requestKind" yaml:"requestKind"`
	RequestResource metav1.GroupVersionResource        `json:"requestResource" yaml:"requestResource"`
	RequestSubResource string                           `json:"requestSubResource" yaml:"requestSubResource"`
	Operation string                                   `json:"operation" yaml:"operation"`
	UserInfo  authenticationv1.UserInfo                `json:"userInfo" yaml:"userInfo"`
	Object    interface{}                              `json:"object,omitempty" yaml:"object,omitempty"`
	OldObject interface{}                              `json:"oldObject,omitempty" yaml:"oldObject,omitempty"`
}

type Status struct {
	Message string `json:"message,omitempty"`
	Code    int32  `json:"code"`
}

// 安全检查函数
func ValidatePrivilegedPods(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Printf("Error reading body: %v", err)
		httpError(w, "can't read body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	log.Printf("Received validation request: %s", string(body))

	var review AdmissionReview
	err = json.Unmarshal(body, &review)
	if err != nil {
		log.Printf("Error unmarshalling request: %v", err)
		httpError(w, "can't unmarshal request", http.StatusBadRequest)
		return
	}

	responseVersion := getResponseVersion(review.APIVersion)

	// 检查请求对象是否存在
	if review.Request == nil || review.Request.Object == nil {
		sendAllowResponse(w, responseVersion, review.Request.UID, "No object to validate")
		return
	}

	// 验证 Pod 是否符合安全规范
	validationError := validatePodSecurity(review.Request.Object)
	if validationError != "" {
		sendDenyResponse(w, responseVersion, review.Request.UID, validationError)
		return
	}

	// 验证通过
	sendAllowResponse(w, responseVersion, review.Request.UID, "")
}

// 获取响应版本
func getResponseVersion(apiVersion string) string {
	if apiVersion != "" {
		return apiVersion
	}
	return "admission.k8s.io/v1"
}

// 验证 Pod 安全性
func validatePodSecurity(obj interface{}) string {
	objMap, ok := obj.(map[string]interface{})
	if !ok {
		return "Invalid object format"
	}

	spec, ok := objMap["spec"].(map[string]interface{})
	if !ok {
		return "Pod spec is not a valid map"
	}

	// 检查容器的安全上下文
	if err := checkContainersSecurity(spec, "containers"); err != nil {
		return err.Error()
	}

	// 检查初始化容器的安全上下文
	if err := checkContainersSecurity(spec, "initContainers"); err != nil {
		return err.Error()
	}

	// 检查 Pod 级别的安全上下文
	if err := checkPodSecurityContext(spec); err != nil {
		return err.Error()
	}

	return ""
}

// 检查容器安全上下文
func checkContainersSecurity(spec map[string]interface{}, containerType string) error {
	containersInterface, exists := spec[containerType]
	if !exists {
		return nil // 如果没有这种类型的容器,直接返回
	}

	containers, ok := containersInterface.([]interface{})
	if !ok {
		return nil // 如果不是数组,直接返回
	}

	for _, container := range containers {
		containerMap, ok := container.(map[string]interface{})
		if !ok {
			continue
		}

		securityContext, exists := containerMap["securityContext"]
		if !exists {
			continue
		}

		secCtx, ok := securityContext.(map[string]interface{})
		if !ok {
			continue
		}

		privileged, exists := secCtx["privileged"]
		if !exists {
			continue
		}

		if isTrue(privileged) {
			return fmt.Errorf("%s contains privileged: true in securityContext which is not allowed", containerType[:len(containerType)-1])
		}
	}

	return nil
}

// 检查 Pod 级别的安全上下文
func checkPodSecurityContext(spec map[string]interface{}) error {
	podSecurityContext, exists := spec["securityContext"]
	if !exists {
		return nil // 如果没有 Pod 级别的安全上下文,直接返回
	}

	podSecCtx, ok := podSecurityContext.(map[string]interface{})
	if !ok {
		return nil // 如果不是 map,直接返回
	}

	runAsNonRoot, exists := podSecCtx["runAsNonRoot"]
	if !exists {
		return nil // 如果没有设置 runAsNonRoot,直接返回
	}

	if !isTrue(runAsNonRoot) {
		return fmt.Errorf("Pod specifies runAsNonRoot: false which is not allowed")
	}

	return nil
}

// 检查接口值是否为真
func isTrue(value interface{}) bool {
	boolValue, ok := value.(bool)
	return ok && boolValue
}

// 发送允许响应
func sendAllowResponse(w http.ResponseWriter, responseVersion, uid, msg string) {
	resp := createAdmissionReview(responseVersion, uid, true, nil)
	sendResponse(w, resp, msg)
}

// 发送拒绝响应
func sendDenyResponse(w http.ResponseWriter, responseVersion, uid, reason string) {
	status := &Status{
		Message: reason,
		Code:    400,
	}
	resp := createAdmissionReview(responseVersion, uid, false, status)
	sendResponse(w, resp, reason)
}

// 创建 AdmissionReview
func createAdmissionReview(version, uid string, allowed bool, status *Status) *AdmissionReview {
	return &AdmissionReview{
		APIVersion: version,
		Kind:       "AdmissionReview",
		Response: &AdmissionResponse{
			Allowed: allowed,
			Result:  status,
			UID:     uid,
		},
	}
}

// 发送响应
func sendResponse(w http.ResponseWriter, resp *AdmissionReview, msg string) {
	jsonBytes, err := json.Marshal(resp)
	if err != nil {
		log.Printf("Error marshalling response: %v", err)
		httpError(w, "can't marshal response", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(jsonBytes)
	log.Printf("Sending validation response: %s", string(jsonBytes))
}

// HTTP 错误响应
func httpError(w http.ResponseWriter, msg string, code int) {
	http.Error(w, msg, code)
}

func main() {
	http.HandleFunc("/validate", ValidatePrivilegedPods)
	log.Println("Validation server started on port 9443")
	log.Fatal(http.ListenAndServeTLS(":9443", "vatls.crt", "vatls.key", nil))
}

创建证书

bash 复制代码
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[ req_distinguished_name ]
CN = validate-webhook.default.svc

[ v3_req ]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = validate-webhook
DNS.2 = validate-webhook.default
DNS.3 = validate-webhook.default.svc
bash 复制代码
openssl req -x509 -newkey rsa:2048 -keyout vatls.key -out vatls.crt -days 3650 -nodes -config vatls.cnf -extensions v3_req

配置外部validate webhook 服务

bash 复制代码
apiVersion: v1
kind: Service
metadata:
  name: validate-webhook
spec:
  ports:
  - port: 9443
    targetPort: 9443
    protocol: TCP
  type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
  name: validate-webhook
  namespace: default
subsets:
- addresses:
  - ip: 172.16.0.27
  ports:
  - port: 9443
    protocol: TCP

ValidatingWebhook.yaml

yaml 复制代码
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: privileged-pod-validator
webhooks:
- name: privileged-pods.webhook.default.svc.cluster.local
  rules:
  - apiGroups: [""]
    apiVersions: ["v1"]
    operations: ["CREATE", "UPDATE"]
    resources: ["pods"]
    scope: "Namespaced"
  clientConfig:
    service:
      name: validate-webhook  # 服务名称,需要与实际部署的服务名称匹配
      namespace: default
      path: "/validate"  # 与代码中定义的路径匹配
      port: 9443
    # 请使用实际的 CA 证书 base64 编码
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVSExkTk1iamZhVHBrencvQ2NrRjZKOHZFbitVd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0p6RWxNQ01HQTFVRUF3d2NkbUZzYVdSaGRHVXRkMlZpYUc5dmF5NWtaV1poZFd4MExuTjJZekFlRncweQpOakF4TURnd01qSXpOVGxhRncwek5qQXhNRFl3TWpJek5UbGFNQ2N4SlRBakJnTlZCQU1NSEhaaGJHbGtZWFJsCkxYZGxZbWh2YjJzdVpHVm1ZWFZzZEM1emRtTXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUsKQW9JQkFRREl0WW9BclhVdnNhZVgrVUE3UzlNNDFRaWM3UUZyL1dOeCtxeHVtODUwRDFMOVBEVUxMNXBJRVdMZwpGMW5iR2dzOHNEeVNoS1hQNWpPTUdxS1FVbzJidFMrM2Q0ZldiQ2d5ZnBmbjdUcnZNQmpmVXBZUWxpVTFxaHVYCmh4dmxVZ1ZQc3ZNUWhJVjBnTERmUXZ5WHM5V0IyUjBsZFA0YllFWUJVN25SMEZxQXlTMjNUYURNSUFDa09NUW8KaFJra1c3VENNZ2JiUHRiSWhpUC90MTVpV2hRckN2aTg5cE9xSDZSc09iMm5jYUNpdFZ3RjFPaG9GWHNUbVhVNgpBYkdIUndEbHBEb0JsY29DRjVURlZsTFpodmcvdzdlUm9odjRxMFBESld4VE1qbWdIZ2tmNW9xYm1xZzNLNEF5CkJlY2FCaEFPTnVEMEJmcXhoRmppcm1rS21xSlpBZ01CQUFHamdaa3dnWll3Q3dZRFZSMFBCQVFEQWdRd01CTUcKQTFVZEpRUU1NQW9HQ0NzR0FRVUZCd01CTUZNR0ExVWRFUVJNTUVxQ0VIWmhiR2xrWVhSbExYZGxZbWh2YjJ1QwpHSFpoYkdsa1lYUmxMWGRsWW1odmIyc3VaR1ZtWVhWc2RJSWNkbUZzYVdSaGRHVXRkMlZpYUc5dmF5NWtaV1poCmRXeDBMbk4yWXpBZEJnTlZIUTRFRmdRVUdyZlFJUUxISVFlOWtHYmp4dFRyTzZ4WkdEa3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQU1MYzZ5VnhNUXdnTEhLSjNyakgrNG1Yc1lzSEJETkdUbHJ5dnRJNUxEcEw1VkU1bE9JQwpYa29KWmQ3RlUxc3doODY3ZVhpb1FKQTd0d0xpYXA0Uk5NRmxNUEhzcVUzZEFkRXRlWW96RVZ2azFiaVRrenBaCnhiK25Wc0hxSm9MWk9yMTNmcDBmbVZHWUk2SmM4Zms3dXVRZnhIaVJhbnUwb2ZKQnI5Rm41bWtJTHI1eEVxeloKTnJvTGY1Y1UvVVBKdXh1ZU1xbnBCYTdSc0IyZklZejB4QzU4TXVKa2pVbTJ2RUJMa0drcGR6eXkrbEkyM0hqOApQNm1rWmhYWHVkTisrMlV4dm12eWZEKzdIQlIzekFQY2NMZGFKUGtOMm9uU3lubUxZeU5QZTJJQkZOQVh4L1lFCm45UU9VRVlCZGFLN0poYnhTT3FlcXRJOHQ1SkRZMjNvR3BjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  admissionReviewVersions: ["v1", "v1beta1"]
  sideEffects: None
  timeoutSeconds: 10
  failurePolicy: Fail  # 如果 webhook 服务不可用,请求将被拒绝
  matchPolicy: Equivalent
  namespaceSelector: {}  # 应用于所有命名空间,可以根据需要修改
  objectSelector: {}    # 应用于所有 Pod 对象,可以根据需要修改

测试创建特权pod

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: test
  name: valitest
spec:
  containers:
  - image: nginx
    name: valitest
    resources: {}
    securityContext:
      privileged: true
  dnsPolicy: ClusterFirst
  restartPolicy: Always

已被ValidatingWebhook拒绝


相关推荐
LucidX1 天前
Kubernetes集群架构与组件
容器·架构·kubernetes
Qianliwind1 天前
安卓手机作为服务器安装docker安装外网可访问网站
服务器·docker·容器
skywalk81631 天前
FreeBSD系统使用docker-compose使用docker容器(没搞定)
spring cloud·docker·容器
回忆是昨天里的海1 天前
docker file-制作镜像
运维·docker·容器
小张程序人生1 天前
一篇文章全面快速入门Docker
运维·docker·容器
2501_939909051 天前
Kubernetes 操作管理概述与项目生命周期管理
云原生·容器·kubernetes
Chris_12191 天前
Termux + 宝塔面板 + Docker 终极部署指南
运维·docker·容器
星环处相逢1 天前
K8s 实战笔记:3 种发布策略 + YAML 配置全攻略
java·docker·kubernetes
schinber1 天前
docker compose如何管理docker服务
运维·docker·容器