From 12019edde49bd27eda6557202752b3684cd7aa99 Mon Sep 17 00:00:00 2001 From: mritd Date: Tue, 25 Aug 2020 11:48:54 +0800 Subject: [PATCH] refactor(all): refactor code refactor code Signed-off-by: mritd --- main.go | 8 +- pkg/adfunc/adfuncs.go | 10 +- pkg/adfunc/func_check_deploy_time.go | 109 ++++++------- pkg/adfunc/func_disable_service_links.go | 146 +++++++++--------- pkg/adfunc/func_image_rename.go | 187 ++++++++++++----------- pkg/adfunc/func_print_request.go | 1 + pkg/route/router.go | 4 +- version | 2 +- 8 files changed, 244 insertions(+), 223 deletions(-) diff --git a/main.go b/main.go index 81d2eea..711c56d 100644 --- a/main.go +++ b/main.go @@ -70,14 +70,18 @@ var rootCmd = &cobra.Command{ logger.Infof("Listen TLS Server at %s", conf.Addr) err := srv.ListenAndServeTLS(conf.Cert, conf.Key) if err != nil { - logger.Fatal(err) + if err == http.ErrServerClosed { + logger.Info("server shutdown success.") + } else { + logger.Fatal(err) + } } } else { logger.Infof("Listen HTTP Server at %s", conf.Addr) err := srv.ListenAndServe() if err != nil { if err == http.ErrServerClosed { - logger.Info("server shutdown success") + logger.Info("server shutdown success.") } else { logger.Fatal(err) } diff --git a/pkg/adfunc/adfuncs.go b/pkg/adfunc/adfuncs.go index 42d511d..058fc7b 100644 --- a/pkg/adfunc/adfuncs.go +++ b/pkg/adfunc/adfuncs.go @@ -7,14 +7,15 @@ import ( "strings" "sync" + "github.com/mritd/goadmission/pkg/zaplogger" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/runtime/serializer" jsoniter "github.com/json-iterator/go" "github.com/mritd/goadmission/pkg/route" - "github.com/mritd/goadmission/pkg/zaplogger" admissionv1 "k8s.io/api/admission/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -26,12 +27,14 @@ const ( type AdmissionType string +// AdmissionFunc defines an admission control handler type AdmissionFunc struct { Type AdmissionType Path string Func func(request *admissionv1.AdmissionRequest) (*admissionv1.AdmissionResponse, error) } +// admissionFuncMap is a collection of global admission control handlers type admissionFuncMap map[string]AdmissionFunc var funcMap = make(admissionFuncMap, 10) @@ -40,6 +43,8 @@ var adfuncOnce sync.Once var deserializer runtime.Decoder var logger *zap.SugaredLogger +// Setup initialize deserializer and register admission control handlers +// to the global routing handlers collection. func Setup() { adfuncOnce.Do(func() { logger = zaplogger.NewSugar("adfunc") @@ -137,7 +142,8 @@ func register(af AdmissionFunc) { logger.Fatalf("unsupported admission func type") } - if _, exist := funcMap[handlePath]; exist { + registeredAf, exist := funcMap[handlePath] + if exist && registeredAf.Type == af.Type { logger.Fatalf("admission func [%s], type: %s already registered", af.Path, af.Type) } diff --git a/pkg/adfunc/func_check_deploy_time.go b/pkg/adfunc/func_check_deploy_time.go index 6ff87e9..940a4e9 100644 --- a/pkg/adfunc/func_check_deploy_time.go +++ b/pkg/adfunc/func_check_deploy_time.go @@ -20,65 +20,68 @@ func init() { register(AdmissionFunc{ Type: AdmissionTypeValidating, Path: "/check-deploy-time", - Func: func(request *admissionv1.AdmissionRequest) (*admissionv1.AdmissionResponse, error) { - switch request.Kind.Kind { - case "Deployment": - var deploy appsv1.Deployment - err := jsoniter.Unmarshal(request.Object.Raw, &deploy) - if err != nil { - errMsg := fmt.Sprintf("[route.Validating] /check-deploy-time: failed to unmarshal object: %v", err) - logger.Error(errMsg) - return &admissionv1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Code: http.StatusBadRequest, - Message: errMsg, - }, - }, nil - } - for label := range deploy.Labels { - if label == conf.ForceDeployLabel { - return &admissionv1.AdmissionResponse{ - Allowed: true, - Result: &metav1.Status{ - Code: http.StatusOK, - Message: "success", - }, - }, nil - } - } + Func: checkDeployTime, + }) +} - err = checkTime(conf.AllowDeployTime) - if err != nil { - return &admissionv1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Code: http.StatusForbidden, - Message: err.Error(), - }, - }, nil - } else { - return &admissionv1.AdmissionResponse{ - Allowed: true, - Result: &metav1.Status{ - Code: http.StatusOK, - Message: "success", - }, - }, nil - } - default: - errMsg := fmt.Sprintf("[route.Validating] /check-deploy-time: received wrong kind request: %s, Only support Kind: Deployment", request.Kind.Kind) - logger.Error(errMsg) +// checkDeployTime check the current time allow deployment +func checkDeployTime(request *admissionv1.AdmissionRequest) (*admissionv1.AdmissionResponse, error) { + switch request.Kind.Kind { + case "Deployment": + var deploy appsv1.Deployment + err := jsoniter.Unmarshal(request.Object.Raw, &deploy) + if err != nil { + errMsg := fmt.Sprintf("[route.Validating] /check-deploy-time: failed to unmarshal object: %v", err) + logger.Error(errMsg) + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: http.StatusBadRequest, + Message: errMsg, + }, + }, nil + } + for label := range deploy.Labels { + if label == conf.ForceDeployLabel { return &admissionv1.AdmissionResponse{ - Allowed: false, + Allowed: true, Result: &metav1.Status{ - Code: http.StatusForbidden, - Message: errMsg, + Code: http.StatusOK, + Message: "success", }, }, nil } - }, - }) + } + + err = checkTime(conf.AllowDeployTime) + if err != nil { + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: http.StatusForbidden, + Message: err.Error(), + }, + }, nil + } else { + return &admissionv1.AdmissionResponse{ + Allowed: true, + Result: &metav1.Status{ + Code: http.StatusOK, + Message: "success", + }, + }, nil + } + default: + errMsg := fmt.Sprintf("[route.Validating] /check-deploy-time: received wrong kind request: %s, Only support Kind: Deployment", request.Kind.Kind) + logger.Error(errMsg) + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: http.StatusForbidden, + Message: errMsg, + }, + }, nil + } } func checkTime(allowTime []string) error { diff --git a/pkg/adfunc/func_disable_service_links.go b/pkg/adfunc/func_disable_service_links.go index 318c7e8..cb00ef2 100644 --- a/pkg/adfunc/func_disable_service_links.go +++ b/pkg/adfunc/func_disable_service_links.go @@ -20,84 +20,88 @@ func init() { register(AdmissionFunc{ Type: AdmissionTypeMutating, Path: "/disable-service-links", - Func: func(request *admissionv1.AdmissionRequest) (*admissionv1.AdmissionResponse, error) { - switch request.Kind.Kind { - case "Deployment": - var deploy appsv1.Deployment - err := jsoniter.Unmarshal(request.Object.Raw, &deploy) - if err != nil { - errMsg := fmt.Sprintf("[route.Mutating] /disable-service-links: failed to unmarshal object: %v", err) - logger.Error(errMsg) - return &admissionv1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Code: http.StatusBadRequest, - Message: errMsg, - }, - }, nil - } - - for label := range deploy.Labels { - if label == conf.ForceEnableServiceLinksLabel { - return &admissionv1.AdmissionResponse{ - Allowed: true, - Result: &metav1.Status{ - Code: http.StatusOK, - Message: "success", - }, - }, nil - } - } - - patches := []Patch{ - { - Option: PatchOptionAdd, - Path: "/metadata/annotations", - Value: map[string]string{ - fmt.Sprintf("disable-service-links-mutatingwebhook-%d.mritd.me", time.Now().Unix()): "true", - }, - }, - { - Option: PatchOptionReplace, - Path: "/spec/template/spec/enableServiceLinks", - Value: false, - }, - } + Func: disableServiceLinks, + }) +} - patch, err := jsoniter.Marshal(patches) - if err != nil { - errMsg := fmt.Sprintf("[route.Mutating] /disable-service-links: failed to marshal patch: %v", err) - logger.Error(errMsg) - return &admissionv1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Code: http.StatusInternalServerError, - Message: errMsg, - }, - }, nil - } +// disableServiceLinks auto set enableServiceLinks of the target Deployment to false +// to prevent k8s environment variable injection +func disableServiceLinks(request *admissionv1.AdmissionRequest) (*admissionv1.AdmissionResponse, error) { + switch request.Kind.Kind { + case "Deployment": + var deploy appsv1.Deployment + err := jsoniter.Unmarshal(request.Object.Raw, &deploy) + if err != nil { + errMsg := fmt.Sprintf("[route.Mutating] /disable-service-links: failed to unmarshal object: %v", err) + logger.Error(errMsg) + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: http.StatusBadRequest, + Message: errMsg, + }, + }, nil + } - logger.Infof("[route.Mutating] /disable-service-links: patches: %s", string(patch)) + for label := range deploy.Labels { + if label == conf.ForceEnableServiceLinksLabel { return &admissionv1.AdmissionResponse{ - Allowed: true, - Patch: patch, - PatchType: JSONPatch(), + Allowed: true, Result: &metav1.Status{ Code: http.StatusOK, Message: "success", }, }, nil - default: - errMsg := fmt.Sprintf("[route.Mutating] /disable-service-links: received wrong kind request: %s, Only support Kind: Deployment", request.Kind.Kind) - logger.Error(errMsg) - return &admissionv1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Code: http.StatusForbidden, - Message: errMsg, - }, - }, nil } - }, - }) + } + + patches := []Patch{ + { + Option: PatchOptionAdd, + Path: "/metadata/annotations", + Value: map[string]string{ + fmt.Sprintf("disable-service-links-mutatingwebhook-%d.mritd.me", time.Now().Unix()): "true", + }, + }, + { + Option: PatchOptionReplace, + Path: "/spec/template/spec/enableServiceLinks", + Value: false, + }, + } + + patch, err := jsoniter.Marshal(patches) + if err != nil { + errMsg := fmt.Sprintf("[route.Mutating] /disable-service-links: failed to marshal patch: %v", err) + logger.Error(errMsg) + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: http.StatusInternalServerError, + Message: errMsg, + }, + }, nil + } + + logger.Infof("[route.Mutating] /disable-service-links: patches: %s", string(patch)) + return &admissionv1.AdmissionResponse{ + Allowed: true, + Patch: patch, + PatchType: JSONPatch(), + Result: &metav1.Status{ + Code: http.StatusOK, + Message: "success", + }, + }, nil + default: + errMsg := fmt.Sprintf("[route.Mutating] /disable-service-links: received wrong kind request: %s, Only support Kind: Deployment", request.Kind.Kind) + logger.Error(errMsg) + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: http.StatusForbidden, + Message: errMsg, + }, + }, nil + } } diff --git a/pkg/adfunc/func_image_rename.go b/pkg/adfunc/func_image_rename.go index eadaefd..16cdc70 100644 --- a/pkg/adfunc/func_image_rename.go +++ b/pkg/adfunc/func_image_rename.go @@ -24,106 +24,109 @@ func init() { register(AdmissionFunc{ Type: AdmissionTypeMutating, Path: "/rename", - Func: func(request *admissionv1.AdmissionRequest) (*admissionv1.AdmissionResponse, error) { - // init rename rules map - renameOnce.Do(func() { - renameMap = make(map[string]string, 10) - for _, s := range conf.ImageRename { - ss := strings.Split(s, "=") - if len(ss) != 2 { - logger.Fatalf("failed to parse image name rename rules: %s", s) - } - renameMap[ss[0]] = ss[1] - } - }) - - switch request.Kind.Kind { - case "Pod": - var pod corev1.Pod - err := jsoniter.Unmarshal(request.Object.Raw, &pod) - if err != nil { - errMsg := fmt.Sprintf("[route.Mutating] /rename: failed to unmarshal object: %v", err) - logger.Error(errMsg) - return &admissionv1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Code: http.StatusBadRequest, - Message: errMsg, - }, - }, nil - } - - // skip static pod - for k := range pod.Annotations { - if k == "kubernetes.io/config.mirror" { - errMsg := fmt.Sprintf("[route.Mutating] /rename: pod %s has kubernetes.io/config.mirror annotation, skip image rename", pod.Name) - logger.Warn(errMsg) - return &admissionv1.AdmissionResponse{ - Allowed: true, - Result: &metav1.Status{ - Code: http.StatusOK, - Message: errMsg, - }, - }, nil - } - } - - var patches []Patch - for i, c := range pod.Spec.Containers { - for s, t := range renameMap { - if strings.HasPrefix(c.Image, s) { - patches = append(patches, Patch{ - Option: PatchOptionReplace, - Path: fmt.Sprintf("/spec/containers/%d/image", i), - Value: strings.Replace(c.Image, s, t, 1), - }) + Func: rename, + }) +} - patches = append(patches, Patch{ - Option: PatchOptionAdd, - Path: "/metadata/annotations", - Value: map[string]string{ - fmt.Sprintf("rename-mutatingwebhook-%d.mritd.me", time.Now().Unix()): fmt.Sprintf("%d-%s-%s", i, strings.ReplaceAll(s, "/", "_"), strings.ReplaceAll(t, "/", "_")), - }, - }) - break - } - } - } +// rename auto modify the image name of the pod +func rename(request *admissionv1.AdmissionRequest) (*admissionv1.AdmissionResponse, error) { + // init rename rules map + renameOnce.Do(func() { + renameMap = make(map[string]string, 10) + for _, s := range conf.ImageRename { + ss := strings.Split(s, "=") + if len(ss) != 2 { + logger.Fatalf("failed to parse image name rename rules: %s", s) + } + renameMap[ss[0]] = ss[1] + } + }) - patch, err := jsoniter.Marshal(patches) - if err != nil { - errMsg := fmt.Sprintf("[route.Mutating] /rename: failed to marshal patch: %v", err) - logger.Error(errMsg) - return &admissionv1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Code: http.StatusInternalServerError, - Message: errMsg, - }, - }, nil - } + switch request.Kind.Kind { + case "Pod": + var pod corev1.Pod + err := jsoniter.Unmarshal(request.Object.Raw, &pod) + if err != nil { + errMsg := fmt.Sprintf("[route.Mutating] /rename: failed to unmarshal object: %v", err) + logger.Error(errMsg) + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: http.StatusBadRequest, + Message: errMsg, + }, + }, nil + } - logger.Infof("[route.Mutating] /rename: patches: %s", string(patch)) + // skip static pod + for k := range pod.Annotations { + if k == "kubernetes.io/config.mirror" { + errMsg := fmt.Sprintf("[route.Mutating] /rename: pod %s has kubernetes.io/config.mirror annotation, skip image rename", pod.Name) + logger.Warn(errMsg) return &admissionv1.AdmissionResponse{ - Allowed: true, - Patch: patch, - PatchType: JSONPatch(), + Allowed: true, Result: &metav1.Status{ Code: http.StatusOK, - Message: "success", - }, - }, nil - default: - errMsg := fmt.Sprintf("[route.Mutating] /rename: received wrong kind request: %s, Only support Kind: Pod", request.Kind.Kind) - logger.Error(errMsg) - return &admissionv1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Code: http.StatusForbidden, Message: errMsg, }, }, nil } - }, - }) + } + + var patches []Patch + for i, c := range pod.Spec.Containers { + for s, t := range renameMap { + if strings.HasPrefix(c.Image, s) { + patches = append(patches, Patch{ + Option: PatchOptionReplace, + Path: fmt.Sprintf("/spec/containers/%d/image", i), + Value: strings.Replace(c.Image, s, t, 1), + }) + + patches = append(patches, Patch{ + Option: PatchOptionAdd, + Path: "/metadata/annotations", + Value: map[string]string{ + fmt.Sprintf("rename-mutatingwebhook-%d.mritd.me", time.Now().Unix()): fmt.Sprintf("%d-%s-%s", i, strings.ReplaceAll(s, "/", "_"), strings.ReplaceAll(t, "/", "_")), + }, + }) + break + } + } + } + + patch, err := jsoniter.Marshal(patches) + if err != nil { + errMsg := fmt.Sprintf("[route.Mutating] /rename: failed to marshal patch: %v", err) + logger.Error(errMsg) + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: http.StatusInternalServerError, + Message: errMsg, + }, + }, nil + } + + logger.Infof("[route.Mutating] /rename: patches: %s", string(patch)) + return &admissionv1.AdmissionResponse{ + Allowed: true, + Patch: patch, + PatchType: JSONPatch(), + Result: &metav1.Status{ + Code: http.StatusOK, + Message: "success", + }, + }, nil + default: + errMsg := fmt.Sprintf("[route.Mutating] /rename: received wrong kind request: %s, Only support Kind: Pod", request.Kind.Kind) + logger.Error(errMsg) + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: http.StatusForbidden, + Message: errMsg, + }, + }, nil + } } diff --git a/pkg/adfunc/func_print_request.go b/pkg/adfunc/func_print_request.go index ff70dc0..8c5bfbd 100644 --- a/pkg/adfunc/func_print_request.go +++ b/pkg/adfunc/func_print_request.go @@ -22,6 +22,7 @@ func init() { }) } +// printRequest only print admission control request func printRequest(request *admissionv1.AdmissionRequest) (*admissionv1.AdmissionResponse, error) { bs, err := jsoniter.MarshalIndent(request, "", " ") if err != nil { diff --git a/pkg/route/router.go b/pkg/route/router.go index abe342f..df2bc7d 100644 --- a/pkg/route/router.go +++ b/pkg/route/router.go @@ -35,8 +35,8 @@ func RegisterHandler(hf HandleFunc) { if hf.Path == "" { logger.Fatalf("handle func path is empty") } - _, ok := funcMap[strings.ToLower(hf.Path)] - if ok { + registeredHf, ok := funcMap[strings.ToLower(hf.Path)] + if ok && registeredHf.Method == hf.Method { logger.Fatalf("handle func [%s] already registered", hf.Path) } funcMap[strings.ToLower(hf.Path)] = hf diff --git a/version b/version index b482243..13637f4 100644 --- a/version +++ b/version @@ -1 +1 @@ -v1.0.2 \ No newline at end of file +v1.0.3 \ No newline at end of file