From e7e16c9655bda79139e0ccf24ed26b93dcd3f4b1 Mon Sep 17 00:00:00 2001 From: Jun Mukai Date: Tue, 26 Sep 2017 13:45:13 -0700 Subject: [PATCH] allow both http for normal access and https for external admission webhook --- cmd/server/cmd/root.go | 2 +- cmd/server/cmd/validator.go | 23 +++++++++++----- pkg/config/crd/init.go | 52 ++++++++++++++++++++++++++++++------- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/cmd/server/cmd/root.go b/cmd/server/cmd/root.go index 1fbb6930f..7165a2259 100644 --- a/cmd/server/cmd/root.go +++ b/cmd/server/cmd/root.go @@ -54,7 +54,7 @@ func GetRootCmd(args []string, info map[string]template.Info, adapters []adapter rootCmd.AddCommand(adapterCmd(printf)) rootCmd.AddCommand(serverCmd(info, adapters, printf, fatalf)) rootCmd.AddCommand(crdCmd(info, adapters, printf, fatalf)) - rootCmd.AddCommand(validatorCmd(info, adapters, fatalf)) + rootCmd.AddCommand(validatorCmd(info, adapters, printf, fatalf)) rootCmd.AddCommand(shared.VersionCmd(printf)) return rootCmd diff --git a/cmd/server/cmd/validator.go b/cmd/server/cmd/validator.go index c973a9d52..8d642976c 100644 --- a/cmd/server/cmd/validator.go +++ b/cmd/server/cmd/validator.go @@ -35,11 +35,12 @@ type validatorConfig struct { targetNamespaces []string resources map[string]proto.Message port uint16 + httpPort uint16 secretName string certsDir string } -func validatorCmd(info map[string]template.Info, adapters []adapter.InfoFn, fatalf shared.FormatFn) *cobra.Command { +func validatorCmd(info map[string]template.Info, adapters []adapter.InfoFn, printf, fatalf shared.FormatFn) *cobra.Command { vc := &validatorConfig{ resources: runtime.KindMap(config.InventoryMap(adapters), info), } @@ -47,7 +48,7 @@ func validatorCmd(info map[string]template.Info, adapters []adapter.InfoFn, fata Use: "validator", Short: "Runs an https server for validations. Works as an external admission webhook for k8s", Run: func(cmd *cobra.Command, args []string) { - runValidator(vc, fatalf) + runValidator(vc, printf, fatalf) }, } validatorCmd.PersistentFlags().StringVar(&vc.namespace, "namespace", "default", "the namespace where this webhook is deployed") @@ -55,6 +56,7 @@ func validatorCmd(info map[string]template.Info, adapters []adapter.InfoFn, fata validatorCmd.PersistentFlags().StringArrayVar(&vc.targetNamespaces, "target-namespaces", []string{}, "the list of namespaces where changes should be validated. Empty means to validate everything.") validatorCmd.PersistentFlags().Uint16VarP(&vc.port, "port", "p", 9099, "the port number of the server") + validatorCmd.PersistentFlags().Uint16Var(&vc.httpPort, "http-port", 9199, "the port number to access to the server through plain http") validatorCmd.PersistentFlags().StringVar(&vc.certsDir, "certs", "/etc/certs", "the directory name where cert files are stored") validatorCmd.PersistentFlags().StringVar(&vc.secretName, "secret-name", "", "The name of k8s secret where the certificates are stored") return validatorCmd @@ -90,16 +92,25 @@ func createCertProvider(vc *validatorConfig, client *kubernetes.Clientset) crd.C return crd.NewFileCertProvider(vc.certsDir) } -func runValidator(vc *validatorConfig, fatalf shared.FormatFn) { +func runValidator(vc *validatorConfig, printf, fatalf shared.FormatFn) { client, err := createK8sClient() if err != nil { - fatalf("Failed to create kubernetes client: %v", err) + printf("Failed to create kubernetes client: %v", err) + printf("Starting plain http server, but external admission hook is not enabled") + client = nil } vs, err := createValidatorServer(vc, client) if err != nil { fatalf("Failed to create validator server: %v", err) } - if err = vs.Start(vc.port, createCertProvider(vc, client)); err != nil { - fatalf("Failed to start validator server: %v", err) + if client != nil { + go func() { + if err = vs.StartWebhook(vc.port, createCertProvider(vc, client)); err != nil { + fatalf("Failed to start validator server: %v", err) + } + }() + } + if err = vs.StartHTTP(vc.httpPort); err != nil { + fatalf("Failed to start plain http server: %v", err) } } diff --git a/pkg/config/crd/init.go b/pkg/config/crd/init.go index a7bb4ffc1..4e10c9c64 100644 --- a/pkg/config/crd/init.go +++ b/pkg/config/crd/init.go @@ -16,6 +16,7 @@ package crd import ( "crypto/tls" + "crypto/x509" "fmt" "net/http" "net/url" @@ -105,29 +106,60 @@ func Register(builders map[string]store.Store2Builder) { builders["kubernetes"] = NewStore } -// Start starts the validation server. -func (v *ValidatorServer) Start(port uint16, certProvider CertProvider) error { +// StartHTTP starts the validation server as a normal http server. +func (v *ValidatorServer) StartHTTP(port uint16) error { + server := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: v, + } + glog.Infof("HTTP server starting with port %d", port) + return server.ListenAndServe() +} + +// retrieve the CA cert that will signed the cert used by the +// "GenericAdmissionWebhook" plugin admission controller. +func (v *ValidatorServer) getAPIServerCert() ([]byte, error) { + c, err := v.client.CoreV1().ConfigMaps("kube-system").Get("extension-apiserver-authentication", metav1.GetOptions{}) + if err != nil { + return nil, err + } + + pem, ok := c.Data["requestheader-client-ca-file"] + if !ok { + return nil, fmt.Errorf("cannot find the ca.crt in the configmap, configMap.Data is %#v", c.Data) + } + return []byte(pem), nil +} + +// StartWebhook starts the validation server as an external admission hook. +func (v *ValidatorServer) StartWebhook(port uint16, certProvider CertProvider) error { key, cert, caCert, err := certProvider.Get() if err != nil { return err } + apiServerCert, err := v.getAPIServerCert() + if err != nil { + return err + } + apiserverCA := x509.NewCertPool() + apiserverCA.AppendCertsFromPEM(apiServerCert) sCert, err := tls.X509KeyPair(cert, key) if err != nil { return err } - // Unlike the webhook example code, this validation server does not take the kube-system's certificate and does not - // require client cert, since istioctl (which is not a kube-system client) will take the role of the validation - // if the external admission webhook isn't ready. - cfg := &tls.Config{Certificates: []tls.Certificate{sCert}} server := &http.Server{ - Addr: fmt.Sprintf(":%d", port), - Handler: v, - TLSConfig: cfg, + Addr: fmt.Sprintf(":%d", port), + Handler: v, + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{sCert}, + ClientCAs: apiserverCA, + ClientAuth: tls.RequireAndVerifyClientCert, + }, } // ensureRegistration can fail if external admission webhook is not yet ready. if err = v.ensureRegistration(caCert); err != nil { glog.V(3).Infof("Failed to register the validation server as the webhook: %v", err) } - glog.Infof("server starting with port %d", port) + glog.Infof("External admission webhook starting with port %d", port) return server.ListenAndServeTLS("", "") }