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 发送给 Webhook 。Webhook 处理完成后,也必须返回一个 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拒绝

