1/* 2Copyright 2015 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package http 18 19import ( 20 "crypto/tls" 21 "errors" 22 "fmt" 23 "net/http" 24 "net/url" 25 "time" 26 27 utilnet "k8s.io/apimachinery/pkg/util/net" 28 "k8s.io/component-base/version" 29 "k8s.io/kubernetes/pkg/probe" 30 31 "k8s.io/klog/v2" 32 utilio "k8s.io/utils/io" 33) 34 35const ( 36 maxRespBodyLength = 10 * 1 << 10 // 10KB 37) 38 39// New creates Prober that will skip TLS verification while probing. 40// followNonLocalRedirects configures whether the prober should follow redirects to a different hostname. 41// If disabled, redirects to other hosts will trigger a warning result. 42func New(followNonLocalRedirects bool) Prober { 43 tlsConfig := &tls.Config{InsecureSkipVerify: true} 44 return NewWithTLSConfig(tlsConfig, followNonLocalRedirects) 45} 46 47// NewWithTLSConfig takes tls config as parameter. 48// followNonLocalRedirects configures whether the prober should follow redirects to a different hostname. 49// If disabled, redirects to other hosts will trigger a warning result. 50func NewWithTLSConfig(config *tls.Config, followNonLocalRedirects bool) Prober { 51 // We do not want the probe use node's local proxy set. 52 transport := utilnet.SetTransportDefaults( 53 &http.Transport{ 54 TLSClientConfig: config, 55 DisableKeepAlives: true, 56 Proxy: http.ProxyURL(nil), 57 }) 58 return httpProber{transport, followNonLocalRedirects} 59} 60 61// Prober is an interface that defines the Probe function for doing HTTP readiness/liveness checks. 62type Prober interface { 63 Probe(url *url.URL, headers http.Header, timeout time.Duration) (probe.Result, string, error) 64} 65 66type httpProber struct { 67 transport *http.Transport 68 followNonLocalRedirects bool 69} 70 71// Probe returns a ProbeRunner capable of running an HTTP check. 72func (pr httpProber) Probe(url *url.URL, headers http.Header, timeout time.Duration) (probe.Result, string, error) { 73 pr.transport.DisableCompression = true // removes Accept-Encoding header 74 client := &http.Client{ 75 Timeout: timeout, 76 Transport: pr.transport, 77 CheckRedirect: redirectChecker(pr.followNonLocalRedirects), 78 } 79 return DoHTTPProbe(url, headers, client) 80} 81 82// GetHTTPInterface is an interface for making HTTP requests, that returns a response and error. 83type GetHTTPInterface interface { 84 Do(req *http.Request) (*http.Response, error) 85} 86 87// DoHTTPProbe checks if a GET request to the url succeeds. 88// If the HTTP response code is successful (i.e. 400 > code >= 200), it returns Success. 89// If the HTTP response code is unsuccessful or HTTP communication fails, it returns Failure. 90// This is exported because some other packages may want to do direct HTTP probes. 91func DoHTTPProbe(url *url.URL, headers http.Header, client GetHTTPInterface) (probe.Result, string, error) { 92 req, err := http.NewRequest("GET", url.String(), nil) 93 if err != nil { 94 // Convert errors into failures to catch timeouts. 95 return probe.Failure, err.Error(), nil 96 } 97 if headers == nil { 98 headers = http.Header{} 99 } 100 if _, ok := headers["User-Agent"]; !ok { 101 // explicitly set User-Agent so it's not set to default Go value 102 v := version.Get() 103 headers.Set("User-Agent", fmt.Sprintf("kube-probe/%s.%s", v.Major, v.Minor)) 104 } 105 if _, ok := headers["Accept"]; !ok { 106 // Accept header was not defined. accept all 107 headers.Set("Accept", "*/*") 108 } else if headers.Get("Accept") == "" { 109 // Accept header was overridden but is empty. removing 110 headers.Del("Accept") 111 } 112 req.Header = headers 113 req.Host = headers.Get("Host") 114 res, err := client.Do(req) 115 if err != nil { 116 // Convert errors into failures to catch timeouts. 117 return probe.Failure, err.Error(), nil 118 } 119 defer res.Body.Close() 120 b, err := utilio.ReadAtMost(res.Body, maxRespBodyLength) 121 if err != nil { 122 if err == utilio.ErrLimitReached { 123 klog.V(4).Infof("Non fatal body truncation for %s, Response: %v", url.String(), *res) 124 } else { 125 return probe.Failure, "", err 126 } 127 } 128 body := string(b) 129 if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest { 130 if res.StatusCode >= http.StatusMultipleChoices { // Redirect 131 klog.V(4).Infof("Probe terminated redirects for %s, Response: %v", url.String(), *res) 132 return probe.Warning, body, nil 133 } 134 klog.V(4).Infof("Probe succeeded for %s, Response: %v", url.String(), *res) 135 return probe.Success, body, nil 136 } 137 klog.V(4).Infof("Probe failed for %s with request headers %v, response body: %v", url.String(), headers, body) 138 return probe.Failure, fmt.Sprintf("HTTP probe failed with statuscode: %d", res.StatusCode), nil 139} 140 141func redirectChecker(followNonLocalRedirects bool) func(*http.Request, []*http.Request) error { 142 if followNonLocalRedirects { 143 return nil // Use the default http client checker. 144 } 145 146 return func(req *http.Request, via []*http.Request) error { 147 if req.URL.Hostname() != via[0].URL.Hostname() { 148 return http.ErrUseLastResponse 149 } 150 // Default behavior: stop after 10 redirects. 151 if len(via) >= 10 { 152 return errors.New("stopped after 10 redirects") 153 } 154 return nil 155 } 156} 157