Kubernetes准入控制器:防患于未然的Pod部署安全卫士
背景:生产环境Pod配置错误的困扰
最近,我们团队的DevOps工程师们频繁遇到生产环境Pod因配置错误导致的问题,例如:
- 镜像拉取失败
- 特权模式运行导致的安全告警
这些问题往往在Pod已经部署后才被发现,修复过程既被动又耗时,严重影响了生产环境的稳定性和安全性。为了改变这种被动局面,我们开始探索如何在Pod创建前就拦截这些不合规的部署请求。
解决方案:Kubernetes Admission Controller
Kubernetes Admission Controller 正是解决这类问题的利器。它是一个准入控制策略,在Pod等资源对象被创建、更新或删除之前,对请求进行拦截和验证。如果请求不符合预定义的策略,Admission Controller可以拒绝该请求,从而防止不合规的资源部署到集群中。
Admission Controller 的类型:
Kubernetes 提供了两种类型的 Admission Controller:
- Mutating Admission Controller(变更准入控制器): 可以修改请求对象。例如,自动为Pod添加标签或注入sidecar容器。
- Validating Admission Controller(验证准入控制器): 仅验证请求对象,不进行修改。例如,验证Pod是否符合安全策略或资源限制。
工作流程:
- 用户提交Pod创建请求。
- API Server 接收到请求。
- API Server 按照配置顺序调用 Admission Controller。
- Admission Controller 对请求进行验证或修改。
- 如果所有 Admission Controller 都通过,则请求被允许,Pod 被创建。否则,请求被拒绝。
实践:使用Validating Admission Webhook拦截特权模式Pod
下面,我们以一个实际案例来演示如何使用 Validating Admission Webhook 拦截特权模式运行的Pod。
1. 创建一个Webhook服务:
首先,我们需要创建一个Webhook服务,该服务接收来自 Kubernetes API Server 的 Admission Review 请求,并根据策略进行验证。
apiVersion: apps/v1
kind: Deployment
metadata:
name: privileged-pod-webhook
spec:
selector:
matchLabels:
app: privileged-pod-webhook
template:
metadata:
labels:
app: privileged-pod-webhook
spec:
containers:
- name: privileged-pod-webhook
image: your-image:latest # 替换成你的webhook镜像
ports:
- containerPort: 443
Webhook服务需要实现一个 HTTPS 端点,用于接收和处理 Admission Review 请求。
2. 创建 Kubernetes Secret:
Webhook 服务需要使用 TLS 证书来保证通信安全。将证书和私钥存储在 Kubernetes Secret 中。
kubectl create secret tls webhook-tls --cert=tls.crt --key=tls.key -n your-namespace # 替换成你的namespace
3. 创建 ValidatingWebhookConfiguration:
接下来,我们需要创建一个 ValidatingWebhookConfiguration 对象,告诉 Kubernetes API Server 如何调用我们的 Webhook 服务。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: privileged-pod-webhook-configuration
webhooks:
- name: privileged-pod.example.com
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
clientConfig:
service:
name: privileged-pod-webhook
namespace: your-namespace # 替换成你的namespace
path: /validate
caBundle: $(base64 -w 0 tls.crt) # 替换成你的CA证书
admissionReviewVersions: ["v1", "v1beta1"]
sideEffects: None
timeoutSeconds: 5
关键配置说明:
rules: 定义了 Webhook 拦截的资源类型和操作。clientConfig: 定义了 Webhook 服务的地址和证书信息。sideEffects: None: 声明 Webhook 没有副作用,可以安全地缓存结果。
4. Webhook服务代码示例(Go):
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func validatePod(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
req := ar.Request
var pod corev1.Pod
if err := json.Unmarshal(req.Object.Raw, &pod); err != nil {
log.Printf("Could not unmarshal raw object: %v", err)
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
Code: http.StatusBadRequest,
},
}
}
allowed := true
message := ""
for _, container := range pod.Spec.Containers {
if container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged {
allowed = false
message = fmt.Sprintf("Container %s is running in privileged mode, which is not allowed.", container.Name)
break
}
}
return &admissionv1.AdmissionResponse{
Allowed: allowed,
Result: &metav1.Status{
Message: message,
},
}
}
func admissionHandler(w http.ResponseWriter, r *http.Request) {
var body []byte
if r.Body != nil {
if data, err := ioutil.ReadAll(r.Body); err == nil {
body = data
}
}
if len(body) == 0 {
log.Println("empty body")
http.Error(w, "empty body", http.StatusBadRequest)
return
}
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
log.Printf("Content-Type=%s, expect application/json", contentType)
http.Error(w, "invalid Content-Type, expect `application/json`", http.StatusBadRequest)
return
}
var admissionReview admissionv1.AdmissionReview
if err := json.Unmarshal(body, &admissionReview); err != nil {
log.Printf("Could not unmarshal admission review: %v", err)
http.Error(w, fmt.Sprintf("could not unmarshal admission review: %v", err), http.StatusBadRequest)
return
}
response := validatePod(&admissionReview)
resp, err := json.Marshal(admissionv1.AdmissionReview{
Response: response,
TypeMeta: admissionReview.TypeMeta,
})
if err != nil {
log.Printf("Could not marshal admission review response: %v", err)
http.Error(w, fmt.Sprintf("could not marshal admission review response: %v", err), http.StatusInternalServerError)
return
}
log.Printf("Sending response: %v", response)
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(resp); err != nil {
log.Printf("Could not write response: %v", err)
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
}
func main() {
http.HandleFunc("/validate", admissionHandler)
log.Println("Server listening on :443")
err := http.ListenAndServeTLS(":443", "/etc/tls/tls.crt", "/etc/tls/tls.key", nil) // 替换成你的证书路径
if err != nil {
log.Fatal("ListenAndServeTLS: ", err)
}
}
5. 测试:
尝试创建一个特权模式的Pod:
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
- name: privileged-container
image: busybox
securityContext:
privileged: true
command: ["sleep", "3600"]
你会发现该Pod创建请求被拒绝,并收到来自 Webhook 服务的错误信息。
总结
通过 Kubernetes Admission Controller,我们可以有效地预防不合规的Pod部署到生产环境,从而提高集群的安全性和稳定性。 除了拦截特权模式Pod,Admission Controller 还可以用于实现更复杂的策略,例如:
- 强制资源限制
- 验证镜像来源
- 自动注入Sidecar容器
希望本文能够帮助你理解和使用 Kubernetes Admission Controller,构建更加安全可靠的云原生应用。