mirror of https://github.com/kubernetes/kops.git
192 lines
4.6 KiB
Go
192 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"
|
|
"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 := os.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)
|
|
}
|
|
}
|