...

Source file src/k8s.io/kubectl/pkg/cmd/proxy/proxy.go

Documentation: k8s.io/kubectl/pkg/cmd/proxy

     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  package proxy
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"net"
    23  	"net/url"
    24  	"os"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/spf13/cobra"
    29  
    30  	"k8s.io/cli-runtime/pkg/genericiooptions"
    31  	"k8s.io/client-go/rest"
    32  	"k8s.io/klog/v2"
    33  
    34  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    35  	"k8s.io/kubectl/pkg/proxy"
    36  	"k8s.io/kubectl/pkg/util/i18n"
    37  	"k8s.io/kubectl/pkg/util/templates"
    38  )
    39  
    40  // ProxyOptions have the data required to perform the proxy operation
    41  type ProxyOptions struct {
    42  	// Common user flags
    43  	staticDir     string
    44  	staticPrefix  string
    45  	apiPrefix     string
    46  	acceptPaths   string
    47  	rejectPaths   string
    48  	acceptHosts   string
    49  	rejectMethods string
    50  	port          int
    51  	address       string
    52  	disableFilter bool
    53  	unixSocket    string
    54  	keepalive     time.Duration
    55  
    56  	appendServerPath bool
    57  
    58  	clientConfig *rest.Config
    59  	filter       *proxy.FilterServer
    60  
    61  	genericiooptions.IOStreams
    62  }
    63  
    64  const (
    65  	defaultPort         = 8001
    66  	defaultStaticPrefix = "/static/"
    67  	defaultAPIPrefix    = "/"
    68  	defaultAddress      = "127.0.0.1"
    69  )
    70  
    71  var (
    72  	proxyLong = templates.LongDesc(i18n.T(`
    73  		Creates a proxy server or application-level gateway between localhost and
    74  		the Kubernetes API server. It also allows serving static content over specified
    75  		HTTP path. All incoming data enters through one port and gets forwarded to
    76  		the remote Kubernetes API server port, except for the path matching the static content path.`))
    77  
    78  	proxyExample = templates.Examples(i18n.T(`
    79  		# To proxy all of the Kubernetes API and nothing else
    80  		kubectl proxy --api-prefix=/
    81  
    82  		# To proxy only part of the Kubernetes API and also some static files
    83  		# You can get pods info with 'curl localhost:8001/api/v1/pods'
    84  		kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/
    85  
    86  		# To proxy the entire Kubernetes API at a different root
    87  		# You can get pods info with 'curl localhost:8001/custom/api/v1/pods'
    88  		kubectl proxy --api-prefix=/custom/
    89  
    90  		# Run a proxy to the Kubernetes API server on port 8011, serving static content from ./local/www/
    91  		kubectl proxy --port=8011 --www=./local/www/
    92  
    93  		# Run a proxy to the Kubernetes API server on an arbitrary local port
    94  		# The chosen port for the server will be output to stdout
    95  		kubectl proxy --port=0
    96  
    97  		# Run a proxy to the Kubernetes API server, changing the API prefix to k8s-api
    98  		# This makes e.g. the pods API available at localhost:8001/k8s-api/v1/pods/
    99  		kubectl proxy --api-prefix=/k8s-api`))
   100  )
   101  
   102  // NewProxyOptions creates the options for proxy
   103  func NewProxyOptions(ioStreams genericiooptions.IOStreams) *ProxyOptions {
   104  	return &ProxyOptions{
   105  		IOStreams:     ioStreams,
   106  		staticPrefix:  defaultStaticPrefix,
   107  		apiPrefix:     defaultAPIPrefix,
   108  		acceptPaths:   proxy.DefaultPathAcceptRE,
   109  		rejectPaths:   proxy.DefaultPathRejectRE,
   110  		acceptHosts:   proxy.DefaultHostAcceptRE,
   111  		rejectMethods: proxy.DefaultMethodRejectRE,
   112  		port:          defaultPort,
   113  		address:       defaultAddress,
   114  	}
   115  }
   116  
   117  // NewCmdProxy returns the proxy Cobra command
   118  func NewCmdProxy(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   119  	o := NewProxyOptions(ioStreams)
   120  
   121  	cmd := &cobra.Command{
   122  		Use:                   "proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix]",
   123  		DisableFlagsInUseLine: true,
   124  		Short:                 i18n.T("Run a proxy to the Kubernetes API server"),
   125  		Long:                  proxyLong,
   126  		Example:               proxyExample,
   127  		Run: func(cmd *cobra.Command, args []string) {
   128  			cmdutil.CheckErr(o.Complete(f))
   129  			cmdutil.CheckErr(o.Validate())
   130  			cmdutil.CheckErr(o.RunProxy())
   131  		},
   132  	}
   133  
   134  	cmd.Flags().StringVarP(&o.staticDir, "www", "w", o.staticDir, "Also serve static files from the given directory under the specified prefix.")
   135  	cmd.Flags().StringVarP(&o.staticPrefix, "www-prefix", "P", o.staticPrefix, "Prefix to serve static files under, if static file directory is specified.")
   136  	cmd.Flags().StringVar(&o.apiPrefix, "api-prefix", o.apiPrefix, "Prefix to serve the proxied API under.")
   137  	cmd.Flags().StringVar(&o.acceptPaths, "accept-paths", o.acceptPaths, "Regular expression for paths that the proxy should accept.")
   138  	cmd.Flags().StringVar(&o.rejectPaths, "reject-paths", o.rejectPaths, "Regular expression for paths that the proxy should reject. Paths specified here will be rejected even accepted by --accept-paths.")
   139  	cmd.Flags().StringVar(&o.acceptHosts, "accept-hosts", o.acceptHosts, "Regular expression for hosts that the proxy should accept.")
   140  	cmd.Flags().StringVar(&o.rejectMethods, "reject-methods", o.rejectMethods, "Regular expression for HTTP methods that the proxy should reject (example --reject-methods='POST,PUT,PATCH'). ")
   141  	cmd.Flags().IntVarP(&o.port, "port", "p", o.port, "The port on which to run the proxy. Set to 0 to pick a random port.")
   142  	cmd.Flags().StringVar(&o.address, "address", o.address, "The IP address on which to serve on.")
   143  	cmd.Flags().BoolVar(&o.disableFilter, "disable-filter", o.disableFilter, "If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks, when used with an accessible port.")
   144  	cmd.Flags().StringVarP(&o.unixSocket, "unix-socket", "u", o.unixSocket, "Unix socket on which to run the proxy.")
   145  	cmd.Flags().DurationVar(&o.keepalive, "keepalive", o.keepalive, "keepalive specifies the keep-alive period for an active network connection. Set to 0 to disable keepalive.")
   146  	cmd.Flags().BoolVar(&o.appendServerPath, "append-server-path", o.appendServerPath, "If true, enables automatic path appending of the kube context server path to each request.")
   147  	return cmd
   148  }
   149  
   150  // Complete adapts from the command line args and factory to the data required.
   151  func (o *ProxyOptions) Complete(f cmdutil.Factory) error {
   152  	clientConfig, err := f.ToRESTConfig()
   153  	if err != nil {
   154  		return err
   155  	}
   156  	o.clientConfig = clientConfig
   157  
   158  	if !strings.HasSuffix(o.staticPrefix, "/") {
   159  		o.staticPrefix += "/"
   160  	}
   161  
   162  	if !strings.HasSuffix(o.apiPrefix, "/") {
   163  		o.apiPrefix += "/"
   164  	}
   165  
   166  	if o.appendServerPath == false {
   167  		target, err := url.Parse(clientConfig.Host)
   168  		if err != nil {
   169  			return err
   170  		}
   171  		if target.Path != "" && target.Path != "/" {
   172  			klog.Warning("Your kube context contains a server path " + target.Path + ", use --append-server-path to automatically append the path to each request")
   173  		}
   174  	}
   175  	if o.disableFilter {
   176  		if o.unixSocket == "" {
   177  			klog.Warning("Request filter disabled, your proxy is vulnerable to XSRF attacks, please be cautious")
   178  		}
   179  		o.filter = nil
   180  	} else {
   181  		o.filter = &proxy.FilterServer{
   182  			AcceptPaths:   proxy.MakeRegexpArrayOrDie(o.acceptPaths),
   183  			RejectPaths:   proxy.MakeRegexpArrayOrDie(o.rejectPaths),
   184  			AcceptHosts:   proxy.MakeRegexpArrayOrDie(o.acceptHosts),
   185  			RejectMethods: proxy.MakeRegexpArrayOrDie(o.rejectMethods),
   186  		}
   187  	}
   188  	return nil
   189  }
   190  
   191  // Validate checks to the ProxyOptions to see if there is sufficient information to run the command.
   192  func (o ProxyOptions) Validate() error {
   193  	if o.port != defaultPort && o.unixSocket != "" {
   194  		return errors.New("cannot set --unix-socket and --port at the same time")
   195  	}
   196  
   197  	if o.staticDir != "" {
   198  		fileInfo, err := os.Stat(o.staticDir)
   199  		if err != nil {
   200  			klog.Warning("Failed to stat static file directory "+o.staticDir+": ", err)
   201  		} else if !fileInfo.IsDir() {
   202  			klog.Warning("Static file directory " + o.staticDir + " is not a directory")
   203  		}
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  // RunProxy checks given arguments and executes command
   210  func (o ProxyOptions) RunProxy() error {
   211  	server, err := proxy.NewServer(o.staticDir, o.apiPrefix, o.staticPrefix, o.filter, o.clientConfig, o.keepalive, o.appendServerPath)
   212  
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	// Separate listening from serving so we can report the bound port
   218  	// when it is chosen by os (eg: port == 0)
   219  	var l net.Listener
   220  	if o.unixSocket == "" {
   221  		l, err = server.Listen(o.address, o.port)
   222  	} else {
   223  		l, err = server.ListenUnix(o.unixSocket)
   224  	}
   225  	if err != nil {
   226  		return err
   227  	}
   228  	fmt.Fprintf(o.IOStreams.Out, "Starting to serve on %s\n", l.Addr().String())
   229  	return server.ServeOnListener(l)
   230  }
   231  

View as plain text