通过创建与公共顶级域名同名的名字空间, 这些名字空间中的服务可以拥有与公共 DNS 记录重叠的、较短的 DNS 名称。 所有名字空间中的负载在执行 DNS 查找时, 如果查找的名称没有尾部句点, 就会被重定向到这些服务上,因此呈现出比公共 DNS 更高的优先序。
为了缓解这类问题,需要将创建名字空间的权限授予可信的用户。
或者额外部署第三方的安全控制机制, 例如以准入 Webhook 的形式,阻止用户创建与公共 TLD 同名的名字空间,也就是本文的内容。
环境
ubuntu 22.04
kubernetes 1.28.2
步骤一:准备证书
说明,apiserver访问webhook服务必须走https,需要准备证书(域名是webhook的service)。ca我们复用k8s的自签ca。
1.生成私钥和证书签名请求 (CSR)
首先,生成一个私钥和证书签名请求 (CSR)。
1.生成私钥:
openssl genrsa -out /etc/kubernetes/pki/webhook-service.key 2048
2.创建一个 CSR 配置文件 csr.conf
,内容如下:
req_extensions = v3_req
distinguished_name = req_distinguished_name
prompt = no[req_distinguished_name]
CN = webhook-service.default.svc[v3_req]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names[alt_names]
DNS.1 = webhook-service.default.svc
DNS.2 = webhook-service.default.svc.cluster.local
3.使用私钥和 CSR 配置文件生成证书签名请求:
openssl req -new -key /etc/kubernetes/pki/webhook-service.key -out /etc/kubernetes/pki/webhook-service.csr -config csr.conf
2.使用 Kubernetes CA 签发证书
现在我们使用 Kubernetes 的 CA 证书 /etc/kubernetes/pki/ca.crt
和 CA 密钥 /etc/kubernetes/pki/ca.key
来签发证书。
使用 openssl
签发证书:
openssl x509 -req -in /etc/kubernetes/pki/webhook-service.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out /etc/kubernetes/pki/webhook-service.crt -days 365 -extensions v3_req -extfile csr.conf
这会生成一个自签名的证书 /etc/kubernetes/pki/webhook-service.crt
,有效期为 365 天。
验证证书
openssl x509 -in /etc/kubernetes/pki/webhook-service.crt -text -noout
步骤二:写webhook的程序(golang)
package mainimport ("encoding/json""net/http"admv1 "k8s.io/api/admission/v1"metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)// 可根据需要扩展,TLD清单参考:https://data.iana.org/TLD/tlds-alpha-by-domain.txt
var publicTLDs = []string{"com", "org", "net", "gov", "edu", "mil", "cn", "jp"} func admitNamespace(ar *admv1.AdmissionReview) *admv1.AdmissionResponse {nsName := ar.Request.Namefor _, tld := range publicTLDs {fmt.Println(nsName,1,tld)if nsName==tld {return &admv1.AdmissionResponse{Allowed: false,Result: &metav1.Status{Message: "Namespace name matches a public TLD.",},}}}return &admv1.AdmissionResponse{Allowed: true}
}func serve(w http.ResponseWriter, r *http.Request) {var admissionReview admv1.AdmissionReviewif err := json.NewDecoder(r.Body).Decode(&admissionReview); err != nil {http.Error(w, err.Error(), http.StatusBadRequest)return}admissionResponse := admitNamespace(&admissionReview)admissionReview.Response = admissionResponseadmissionReview.Response.UID = admissionReview.Request.UIDjson.NewEncoder(w).Encode(admissionReview)
}func main() {http.HandleFunc("/validate", serve)http.ListenAndServeTLS(":8443", "/etc/kubernetes/pki/webhook-service.crt", "/etc/kubernetes/pki/webhook-service.key", nil)
打包出的执行程序为:main
步骤三:部署服务
1. 部署webhook的dp和service
说明:本程序仅为测试,所以直接用nodeSelector直接把pod锁定到指定node上了。该node是golang程序所在的node,为了测试方便。
apiVersion: apps/v1
kind: Deployment
metadata:name: webhook-servernamespace: default labels:app: webhook-server
spec:replicas: 1selector:matchLabels:app: webhook-servertemplate:metadata:labels:app: webhook-serverspec:containers:- name: webhook-serverimage: busybox:latestcommand: ["/opt/main"]imagePullPolicy: IfNotPresentports:- containerPort: 8443volumeMounts:- name: tls-certsmountPath: /etc/kubernetes/pkireadOnly: true- name: programmountPath: /optreadOnly: truevolumes:- name: tls-certshostPath:path: /etc/kubernetes/pki- name: programhostPath:path: /root/yang/gonodeSelector:kubernetes.io/hostname: vm38tolerations:- key: "node-role.kubernetes.io/control-plane"effect: "NoSchedule"
---
apiVersion: v1
kind: Service
metadata:name: webhook-servicenamespace: defaultlabels:app: webhook-server
spec:ports:- port: 443targetPort: 8443protocol: TCPselector:app: webhook-server
kubectl apply -f 上面的yaml文件
2.部署ValidatingWebhookConfiguration
官方文档:ValidatingWebhookConfiguration | Kubernetes
官方文档:https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/
说明:
1.请参阅官方文档
2.下面yaml中clientConfig部分就是webhook服务的配置
3.caBundle是步骤一中ca证书的Base64编码格式,生成的命令为:cat /etc/kubernetes/pki/ca.crt | base64 | tr -d '\n'
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:name: prevent-public-tld-ns
webhooks:- name: prevent-public-tld-ns.k8s.ioclientConfig:service:name: webhook-servicenamespace: defaultpath: "/validate"caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJR1k2NDdMdnZjTGt3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzV***********************************ZjhhTk0yNmZQTzdSK2gxTDBjCmRXbk1QdzR3cXU5emFYSEJyK2pneUsyRHVIOER6QnFYWjZXSVp4ZXRmRm1KMjRRV3dITCs0OEdtUGpWbGFRMUYKbkhYdkV5clhZclVJCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0Krules:- operations: ["CREATE"]apiGroups: [""]apiVersions: ["v1"]resources: ["namespaces"]admissionReviewVersions: ["v1"]sideEffects: None
kubectl apply -f 上面的yaml文件
步骤四:验证
# 执行下面命令尝试创建com命名空间,失败
kubectl create namespace com
# 输出报错提示如下:
Error from server: admission webhook "prevent-public-tld-ns.k8s.io" denied the request: Namespace name matches a public TLD.# 执行下面命令尝试创建普通命名空间,成功
kubectl create namespace yeluomen
# 正常
namespace/yeluomen created
kubectl get namespaces
验证com这个命名空间确实没有创建出来,yeluomen命名空间创建出来了,完美!