1
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
41 type ProxyOptions struct {
42
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
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
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
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
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
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
218
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