github.com/lablabs/operator-sdk@v0.8.2/pkg/ansible/proxy/kubectl.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // This code was retrieved from
    18  // https://github.com/kubernetes/kubernetes/blob/204d994/pkg/kubectl/proxy/proxy_server.go
    19  // and modified for use in this project.
    20  
    21  package proxy
    22  
    23  import (
    24  	"fmt"
    25  	"net"
    26  	"net/http"
    27  	"net/url"
    28  	"os"
    29  	"regexp"
    30  	"strings"
    31  	"time"
    32  
    33  	utilnet "k8s.io/apimachinery/pkg/util/net"
    34  	k8sproxy "k8s.io/apimachinery/pkg/util/proxy"
    35  	"k8s.io/client-go/rest"
    36  	"k8s.io/client-go/transport"
    37  	"k8s.io/kubernetes/pkg/kubectl/util"
    38  	logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
    39  )
    40  
    41  var log = logf.Log.WithName("proxy")
    42  
    43  const (
    44  	// DefaultHostAcceptRE is the default value for which hosts to accept.
    45  	DefaultHostAcceptRE = "^localhost$,^127\\.0\\.0\\.1$,^\\[::1\\]$"
    46  	// DefaultPathAcceptRE is the default path to accept.
    47  	DefaultPathAcceptRE = "^.*"
    48  	// DefaultPathRejectRE is the default set of paths to reject.
    49  	DefaultPathRejectRE = "^/api/.*/pods/.*/exec,^/api/.*/pods/.*/attach"
    50  	// DefaultMethodRejectRE is the set of HTTP methods to reject by default.
    51  	DefaultMethodRejectRE = "^$"
    52  )
    53  
    54  var (
    55  	// ReverseProxyFlushInterval is the frequency to flush the reverse proxy.
    56  	// Only matters for long poll connections like the one used to watch. With an
    57  	// interval of 0 the reverse proxy will buffer content sent on any connection
    58  	// with transfer-encoding=chunked.
    59  	// TODO: Flush after each chunk so the client doesn't suffer a 100ms latency per
    60  	// watch event.
    61  	ReverseProxyFlushInterval = 100 * time.Millisecond
    62  )
    63  
    64  // FilterServer rejects requests which don't match one of the specified regular expressions
    65  type FilterServer struct {
    66  	// Only paths that match this regexp will be accepted
    67  	AcceptPaths []*regexp.Regexp
    68  	// Paths that match this regexp will be rejected, even if they match the above
    69  	RejectPaths []*regexp.Regexp
    70  	// Hosts are required to match this list of regexp
    71  	AcceptHosts []*regexp.Regexp
    72  	// Methods that match this regexp are rejected
    73  	RejectMethods []*regexp.Regexp
    74  	// The delegate to call to handle accepted requests.
    75  	delegate http.Handler
    76  }
    77  
    78  // MakeRegexpArray splits a comma separated list of regexps into an array of Regexp objects.
    79  func MakeRegexpArray(str string) ([]*regexp.Regexp, error) {
    80  	parts := strings.Split(str, ",")
    81  	result := make([]*regexp.Regexp, len(parts))
    82  	for ix := range parts {
    83  		re, err := regexp.Compile(parts[ix])
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  		result[ix] = re
    88  	}
    89  	return result, nil
    90  }
    91  
    92  // MakeRegexpArrayOrDie creates an array of regular expression objects from a string or exits.
    93  func MakeRegexpArrayOrDie(str string) []*regexp.Regexp {
    94  	result, err := MakeRegexpArray(str)
    95  	if err != nil {
    96  		log.Error(err, "Error compiling re")
    97  		os.Exit(1)
    98  	}
    99  	return result
   100  }
   101  
   102  func matchesRegexp(str string, regexps []*regexp.Regexp) bool {
   103  	for _, re := range regexps {
   104  		if re.MatchString(str) {
   105  			log.Info("Matched found", "MatchString", str, "Regexp", re)
   106  			return true
   107  		}
   108  	}
   109  	return false
   110  }
   111  
   112  func (f *FilterServer) accept(method, path, host string) bool {
   113  	if matchesRegexp(path, f.RejectPaths) {
   114  		return false
   115  	}
   116  	if matchesRegexp(method, f.RejectMethods) {
   117  		return false
   118  	}
   119  	if matchesRegexp(path, f.AcceptPaths) && matchesRegexp(host, f.AcceptHosts) {
   120  		return true
   121  	}
   122  	return false
   123  }
   124  
   125  // HandlerFor makes a shallow copy of f which passes its requests along to the
   126  // new delegate.
   127  func (f *FilterServer) HandlerFor(delegate http.Handler) *FilterServer {
   128  	f2 := *f
   129  	f2.delegate = delegate
   130  	return &f2
   131  }
   132  
   133  // Get host from a host header value like "localhost" or "localhost:8080"
   134  func extractHost(header string) (host string) {
   135  	host, _, err := net.SplitHostPort(header)
   136  	if err != nil {
   137  		host = header
   138  	}
   139  	return host
   140  }
   141  
   142  func (f *FilterServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
   143  	host := extractHost(req.Host)
   144  	if f.accept(req.Method, req.URL.Path, host) {
   145  		log.Info("Filter acception", "Request.Method", req.Method, "Request.URL", req.URL.Path, "Host", host)
   146  		f.delegate.ServeHTTP(rw, req)
   147  		return
   148  	}
   149  	log.Info("Filter rejection", "Request.Method", req.Method, "Request.URL", req.URL.Path, "Host", host)
   150  	rw.WriteHeader(http.StatusForbidden)
   151  	if _, err := rw.Write([]byte("<h3>Unauthorized</h3>")); err != nil {
   152  		log.Error(err, "Failed to write response body")
   153  	}
   154  }
   155  
   156  // Server is a http.Handler which proxies Kubernetes APIs to remote API server.
   157  type server struct {
   158  	Handler http.Handler
   159  }
   160  
   161  type responder struct{}
   162  
   163  func (r *responder) Error(w http.ResponseWriter, req *http.Request, err error) {
   164  	log.Error(err, "Error while proxying request")
   165  	http.Error(w, err.Error(), http.StatusInternalServerError)
   166  }
   167  
   168  // makeUpgradeTransport creates a transport that explicitly bypasses HTTP2 support
   169  // for proxy connections that must upgrade.
   170  func makeUpgradeTransport(config *rest.Config) (k8sproxy.UpgradeRequestRoundTripper, error) {
   171  	transportConfig, err := config.TransportConfig()
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	tlsConfig, err := transport.TLSConfigFor(transportConfig)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	rt := utilnet.SetOldTransportDefaults(&http.Transport{
   180  		TLSClientConfig: tlsConfig,
   181  		DialContext: (&net.Dialer{
   182  			Timeout: 30 * time.Second,
   183  		}).DialContext,
   184  	})
   185  
   186  	upgrader, err := transport.HTTPWrappersForConfig(transportConfig, k8sproxy.MirrorRequest)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	return k8sproxy.NewUpgradeRequestRoundTripper(rt, upgrader), nil
   191  }
   192  
   193  // NewServer creates and installs a new Server.
   194  func newServer(apiProxyPrefix string, cfg *rest.Config) (*server, error) {
   195  	host := cfg.Host
   196  	if !strings.HasSuffix(host, "/") {
   197  		host = host + "/"
   198  	}
   199  	target, err := url.Parse(host)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	responder := &responder{}
   205  	transport, err := rest.TransportFor(cfg)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  	upgradeTransport, err := makeUpgradeTransport(cfg)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	proxy := k8sproxy.NewUpgradeAwareHandler(target, transport, false, false, responder)
   214  	proxy.UpgradeTransport = upgradeTransport
   215  	proxy.UseRequestLocation = true
   216  
   217  	proxyServer := http.Handler(proxy)
   218  
   219  	if !strings.HasPrefix(apiProxyPrefix, "/api") {
   220  		proxyServer = stripLeaveSlash(apiProxyPrefix, proxyServer)
   221  	}
   222  
   223  	mux := http.NewServeMux()
   224  	mux.Handle(apiProxyPrefix, proxyServer)
   225  	return &server{Handler: mux}, nil
   226  }
   227  
   228  // Listen is a simple wrapper around net.Listen.
   229  func (s *server) Listen(address string, port int) (net.Listener, error) {
   230  	return net.Listen("tcp", fmt.Sprintf("%s:%d", address, port))
   231  }
   232  
   233  // ListenUnix does net.Listen for a unix socket
   234  func (s *server) ListenUnix(path string) (net.Listener, error) {
   235  	// Remove any socket, stale or not, but fall through for other files
   236  	fi, err := os.Stat(path)
   237  	if err == nil && (fi.Mode()&os.ModeSocket) != 0 {
   238  		if err := os.Remove(path); err != nil {
   239  			return nil, err
   240  		}
   241  	}
   242  	// Default to only user accessible socket, caller can open up later if desired
   243  	oldmask, _ := util.Umask(0077)
   244  	l, err := net.Listen("unix", path)
   245  	util.Umask(oldmask)
   246  	return l, err
   247  }
   248  
   249  // ServeOnListener starts the server using given listener, loops forever.
   250  func (s *server) ServeOnListener(l net.Listener) error {
   251  	server := http.Server{
   252  		Handler: s.Handler,
   253  	}
   254  	return server.Serve(l)
   255  }
   256  
   257  // like http.StripPrefix, but always leaves an initial slash. (so that our
   258  // regexps will work.)
   259  func stripLeaveSlash(prefix string, h http.Handler) http.Handler {
   260  	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   261  		p := strings.TrimPrefix(req.URL.Path, prefix)
   262  		if len(p) >= len(req.URL.Path) {
   263  			http.NotFound(w, req)
   264  			return
   265  		}
   266  		if len(p) > 0 && p[:1] != "/" {
   267  			p = "/" + p
   268  		}
   269  		req.URL.Path = p
   270  		h.ServeHTTP(w, req)
   271  	})
   272  }