mirror of https://github.com/kubernetes/kops.git
				
				
				
			
		
			
				
	
	
		
			193 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
/*
 | 
						|
Copyright 2020 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/tls"
 | 
						|
	"crypto/x509"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"os"
 | 
						|
 | 
						|
	"k8s.io/klog/v2"
 | 
						|
	"k8s.io/kops/pkg/wellknownports"
 | 
						|
)
 | 
						|
 | 
						|
// healthCheckServer is the http server
 | 
						|
type healthCheckServer struct {
 | 
						|
	transport *http.Transport
 | 
						|
}
 | 
						|
 | 
						|
// handler processes a single http request
 | 
						|
func (s *healthCheckServer) handler(w http.ResponseWriter, r *http.Request) {
 | 
						|
	if r.Method == "GET" && r.URL.Path == "/.kube-apiserver-healthcheck/healthz" {
 | 
						|
		// This is a check for our own health
 | 
						|
		w.WriteHeader(http.StatusOK)
 | 
						|
		w.Write([]byte("ok"))
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if proxyRequest := mapToProxyRequest(r); proxyRequest != nil {
 | 
						|
		s.proxyRequest(w, proxyRequest)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	klog.Infof("unknown request: %s %s", r.Method, r.URL.Path)
 | 
						|
	http.Error(w, "not found", http.StatusNotFound)
 | 
						|
}
 | 
						|
 | 
						|
// httpClient builds an isolated http.Client
 | 
						|
func (s *healthCheckServer) httpClient() *http.Client {
 | 
						|
	return &http.Client{Transport: s.transport}
 | 
						|
}
 | 
						|
 | 
						|
// mapToProxyRequest returns the request we should make to the apiserver,
 | 
						|
// or nil if the query is not on the safelist
 | 
						|
func mapToProxyRequest(r *http.Request) *http.Request {
 | 
						|
	if r.Method == "GET" {
 | 
						|
		switch r.URL.Path {
 | 
						|
		case "/livez", "/healthz", "/readyz":
 | 
						|
			// This is a health-check we will proxy
 | 
						|
			return sanitizeRequest(r, []string{"exclude"})
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// sanitizeRequest builds the request we should pass to the target apiserver,
 | 
						|
// passing through only allowedQueryParameters
 | 
						|
func sanitizeRequest(r *http.Request, allowedQueryParameters []string) *http.Request {
 | 
						|
	u := &url.URL{
 | 
						|
		Scheme: "https",
 | 
						|
		Host:   "127.0.0.1",
 | 
						|
		Path:   r.URL.Path,
 | 
						|
	}
 | 
						|
 | 
						|
	// Pass-through (only) the parameters in allowedQueryParameters
 | 
						|
	{
 | 
						|
		in := r.URL.Query()
 | 
						|
		out := make(url.Values)
 | 
						|
 | 
						|
		for _, k := range allowedQueryParameters {
 | 
						|
			for _, v := range in[k] {
 | 
						|
				out.Add(k, v)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		u.RawQuery = out.Encode()
 | 
						|
	}
 | 
						|
 | 
						|
	req := &http.Request{
 | 
						|
		Method: r.Method,
 | 
						|
		URL:    u,
 | 
						|
	}
 | 
						|
 | 
						|
	return req
 | 
						|
}
 | 
						|
 | 
						|
// proxyRequest forwards a request, that has been sanitized by mapToProxyRequest/buildProxyRequest
 | 
						|
func (s *healthCheckServer) proxyRequest(w http.ResponseWriter, forwardRequest *http.Request) {
 | 
						|
	httpClient := s.httpClient()
 | 
						|
 | 
						|
	resp, err := httpClient.Do(forwardRequest)
 | 
						|
	if err != nil {
 | 
						|
		klog.Infof("error from %s: %v", forwardRequest.URL, err)
 | 
						|
		http.Error(w, "internal error", http.StatusBadGateway)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	w.WriteHeader(resp.StatusCode)
 | 
						|
	if _, err := io.Copy(w, resp.Body); err != nil {
 | 
						|
		klog.Warningf("error writing response body: %v", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	switch resp.StatusCode {
 | 
						|
	case 200:
 | 
						|
		klog.V(2).Infof("proxied to %s %s: %s", forwardRequest.Method, forwardRequest.URL, resp.Status)
 | 
						|
	default:
 | 
						|
		klog.Infof("proxied to %s %s: %s", forwardRequest.Method, forwardRequest.URL, resp.Status)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func run() error {
 | 
						|
	listen := fmt.Sprintf(":%d", wellknownports.KubeAPIServerHealthCheck)
 | 
						|
 | 
						|
	clientCert := ""
 | 
						|
	clientKey := ""
 | 
						|
	caCert := ""
 | 
						|
 | 
						|
	flag.StringVar(&clientCert, "client-cert", clientCert, "path to client certificate")
 | 
						|
	flag.StringVar(&clientKey, "client-key", clientKey, "path to client key")
 | 
						|
	flag.StringVar(&caCert, "ca-cert", caCert, "path to ca certificate")
 | 
						|
 | 
						|
	klog.InitFlags(nil)
 | 
						|
 | 
						|
	flag.Parse()
 | 
						|
 | 
						|
	tlsConfig := &tls.Config{}
 | 
						|
 | 
						|
	if caCert != "" {
 | 
						|
		b, err := ioutil.ReadFile(caCert)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("error reading certificate %q: %v", caCert, err)
 | 
						|
		}
 | 
						|
		rootCAs := x509.NewCertPool()
 | 
						|
		rootCAs.AppendCertsFromPEM(b)
 | 
						|
		tlsConfig.RootCAs = rootCAs
 | 
						|
	}
 | 
						|
 | 
						|
	if clientKey != "" {
 | 
						|
		keypair, err := tls.LoadX509KeyPair(clientCert, clientKey)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("error reading client keypair: %v", err)
 | 
						|
		}
 | 
						|
 | 
						|
		tlsConfig.Certificates = []tls.Certificate{keypair}
 | 
						|
	}
 | 
						|
 | 
						|
	transport := &http.Transport{
 | 
						|
		TLSClientConfig: tlsConfig,
 | 
						|
	}
 | 
						|
 | 
						|
	s := &healthCheckServer{
 | 
						|
		transport: transport,
 | 
						|
	}
 | 
						|
 | 
						|
	http.HandleFunc("/", s.handler)
 | 
						|
 | 
						|
	klog.Infof("listening on %s", listen)
 | 
						|
 | 
						|
	if err := http.ListenAndServe(listen, nil); err != nil {
 | 
						|
		return fmt.Errorf("error listening on %q: %v", listen, err)
 | 
						|
	}
 | 
						|
 | 
						|
	return fmt.Errorf("unexpected return from ListenAndServe")
 | 
						|
}
 | 
						|
 | 
						|
func main() {
 | 
						|
	if err := run(); err != nil {
 | 
						|
		fmt.Fprintf(os.Stderr, "%v\n", err)
 | 
						|
		os.Exit(1)
 | 
						|
	}
 | 
						|
}
 |