diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 9bb84b5533e..bfb00cfa5c8 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -595,6 +595,8 @@ _kubectl_proxy() two_word_flags+=("-p") flags+=("--reject-methods=") flags+=("--reject-paths=") + flags+=("--unix-socket=") + two_word_flags+=("-u") flags+=("--www=") two_word_flags+=("-w") flags+=("--www-prefix=") diff --git a/docs/man/man1/kubectl-proxy.1 b/docs/man/man1/kubectl-proxy.1 index 3200c91c131..d07777c7bed 100644 --- a/docs/man/man1/kubectl-proxy.1 +++ b/docs/man/man1/kubectl-proxy.1 @@ -52,7 +52,7 @@ The above lets you 'curl localhost:8001/custom/api/v1/pods' .PP \fB\-\-disable\-filter\fP=false - If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks. Use with caution. + 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. .PP \fB\-h\fP, \fB\-\-help\fP=false @@ -70,6 +70,10 @@ The above lets you 'curl localhost:8001/custom/api/v1/pods' \fB\-\-reject\-paths\fP="^/api/.\fI/exec,^/api/.\fP/run" Regular expression for paths that the proxy should reject. +.PP +\fB\-u\fP, \fB\-\-unix\-socket\fP="" + Unix socket on which to run the proxy. + .PP \fB\-w\fP, \fB\-\-www\fP="" Also serve static files from the given directory under the specified prefix. diff --git a/docs/user-guide/kubectl/kubectl_proxy.md b/docs/user-guide/kubectl/kubectl_proxy.md index 60fcb0274c1..82e79aaf4fa 100644 --- a/docs/user-guide/kubectl/kubectl_proxy.md +++ b/docs/user-guide/kubectl/kubectl_proxy.md @@ -80,11 +80,12 @@ $ kubectl proxy --api-prefix=/k8s-api --accept-hosts="^localhost$,^127\\.0\\.0\\.1$,^\\[::1\\]$": Regular expression for hosts that the proxy should accept. --accept-paths="^/.*": Regular expression for paths that the proxy should accept. --api-prefix="/api/": Prefix to serve the proxied API under. - --disable-filter=false: If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks. Use with caution. + --disable-filter=false: 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. -h, --help=false: help for proxy -p, --port=8001: The port on which to run the proxy. Set to 0 to pick a random port. --reject-methods="POST,PUT,PATCH": Regular expression for HTTP methods that the proxy should reject. --reject-paths="^/api/.*/exec,^/api/.*/run": Regular expression for paths that the proxy should reject. + -u, --unix-socket="": Unix socket on which to run the proxy. -w, --www="": Also serve static files from the given directory under the specified prefix. -P, --www-prefix="/static/": Prefix to serve static files under, if static file directory is specified. ``` @@ -122,7 +123,7 @@ $ kubectl proxy --api-prefix=/k8s-api * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-07-14 00:11:42.957150329 +0000 UTC +###### Auto generated by spf13/cobra at 2015-08-04 15:27:44.354669483 +0000 UTC diff --git a/pkg/kubectl/cmd/proxy.go b/pkg/kubectl/cmd/proxy.go index 21c473c3d8d..55716f32afb 100644 --- a/pkg/kubectl/cmd/proxy.go +++ b/pkg/kubectl/cmd/proxy.go @@ -17,8 +17,10 @@ limitations under the License. package cmd import ( + "errors" "fmt" "io" + "net" "strings" "github.com/golang/glog" @@ -28,6 +30,7 @@ import ( ) const ( + default_port = 8001 proxy_example = `// Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/ $ kubectl proxy --port=8011 --www=./local/www/ @@ -73,14 +76,20 @@ The above lets you 'curl localhost:8001/custom/api/v1/pods' cmd.Flags().String("reject-paths", kubectl.DefaultPathRejectRE, "Regular expression for paths that the proxy should reject.") cmd.Flags().String("accept-hosts", kubectl.DefaultHostAcceptRE, "Regular expression for hosts that the proxy should accept.") cmd.Flags().String("reject-methods", kubectl.DefaultMethodRejectRE, "Regular expression for HTTP methods that the proxy should reject.") - cmd.Flags().IntP("port", "p", 8001, "The port on which to run the proxy. Set to 0 to pick a random port.") - cmd.Flags().Bool("disable-filter", false, "If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks. Use with caution.") + cmd.Flags().IntP("port", "p", default_port, "The port on which to run the proxy. Set to 0 to pick a random port.") + cmd.Flags().Bool("disable-filter", false, "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.") + cmd.Flags().StringP("unix-socket", "u", "", "Unix socket on which to run the proxy.") return cmd } func RunProxy(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error { + path := cmdutil.GetFlagString(cmd, "unix-socket") port := cmdutil.GetFlagInt(cmd, "port") + if port != default_port && path != "" { + return errors.New("Don't specify both --unix-socket and --port") + } + clientConfig, err := f.ClientConfig() if err != nil { return err @@ -101,18 +110,22 @@ func RunProxy(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error { AcceptHosts: kubectl.MakeRegexpArrayOrDie(cmdutil.GetFlagString(cmd, "accept-hosts")), } if cmdutil.GetFlagBool(cmd, "disable-filter") { - glog.Warning("Request filter disabled, your proxy is vulnerable to XSRF attacks, please be cautious") + if path == "" { + glog.Warning("Request filter disabled, your proxy is vulnerable to XSRF attacks, please be cautious") + } filter = nil } - server, err := kubectl.NewProxyServer(port, cmdutil.GetFlagString(cmd, "www"), apiProxyPrefix, staticPrefix, filter, clientConfig) - if err != nil { - return err - } + server, err := kubectl.NewProxyServer(cmdutil.GetFlagString(cmd, "www"), apiProxyPrefix, staticPrefix, filter, clientConfig) // Separate listening from serving so we can report the bound port - // when it is chosen by os (port == 0) - l, err := server.Listen() + // when it is chosen by os (eg: port == 0) + var l net.Listener + if path == "" { + l, err = server.Listen(port) + } else { + l, err = server.ListenUnix(path) + } if err != nil { glog.Fatal(err) } diff --git a/pkg/kubectl/proxy_server.go b/pkg/kubectl/proxy_server.go index d1e0e9bfdee..4935a24dc37 100644 --- a/pkg/kubectl/proxy_server.go +++ b/pkg/kubectl/proxy_server.go @@ -22,12 +22,14 @@ import ( "net/http" "net/http/httputil" "net/url" + "os" "regexp" "strings" "time" "github.com/golang/glog" "k8s.io/kubernetes/pkg/client" + "k8s.io/kubernetes/pkg/util" ) const ( @@ -139,13 +141,12 @@ func (f *FilterServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // ProxyServer is a http.Handler which proxies Kubernetes APIs to remote API server. type ProxyServer struct { handler http.Handler - port int } // NewProxyServer creates and installs a new ProxyServer. // It automatically registers the created ProxyServer to http.DefaultServeMux. // 'filter', if non-nil, protects requests to the api only. -func NewProxyServer(port int, filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *client.Config) (*ProxyServer, error) { +func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *client.Config) (*ProxyServer, error) { host := cfg.Host if !strings.HasSuffix(host, "/") { host = host + "/" @@ -174,12 +175,26 @@ func NewProxyServer(port int, filebase string, apiProxyPrefix string, staticPref // serving their working directory by default. mux.Handle(staticPrefix, newFileHandler(staticPrefix, filebase)) } - return &ProxyServer{handler: mux, port: port}, nil + return &ProxyServer{handler: mux}, nil } // Listen is a simple wrapper around net.Listen. -func (s *ProxyServer) Listen() (net.Listener, error) { - return net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", s.port)) +func (s *ProxyServer) Listen(port int) (net.Listener, error) { + return net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) +} + +// ListenUnix does net.Listen for a unix socket +func (s *ProxyServer) ListenUnix(path string) (net.Listener, error) { + // Remove any socket, stale or not, but fall through for other files + fi, err := os.Stat(path) + if err == nil && (fi.Mode()&os.ModeSocket) != 0 { + os.Remove(path) + } + // Default to only user accessible socket, caller can open up later if desired + oldmask, _ := util.Umask(0077) + l, err := net.Listen("unix", path) + util.Umask(oldmask) + return l, err } // Serve starts the server using given listener, loops forever. diff --git a/pkg/kubectl/proxy_server_test.go b/pkg/kubectl/proxy_server_test.go index a8d44e14cb0..43b29f25165 100644 --- a/pkg/kubectl/proxy_server_test.go +++ b/pkg/kubectl/proxy_server_test.go @@ -287,7 +287,7 @@ func TestPathHandling(t *testing.T) { for _, item := range table { func() { - p, err := NewProxyServer(0, "", item.prefix, "/not/used/for/this/test", nil, cc) + p, err := NewProxyServer("", item.prefix, "/not/used/for/this/test", nil, cc) if err != nil { t.Fatalf("%#v: %v", item, err) } diff --git a/pkg/util/umask.go b/pkg/util/umask.go new file mode 100644 index 00000000000..48311f4e337 --- /dev/null +++ b/pkg/util/umask.go @@ -0,0 +1,27 @@ +// +build !windows + +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 util + +import ( + "syscall" +) + +func Umask(mask int) (old int, err error) { + return syscall.Umask(mask), nil +} diff --git a/pkg/util/umask_windows.go b/pkg/util/umask_windows.go new file mode 100644 index 00000000000..0f97c26edb3 --- /dev/null +++ b/pkg/util/umask_windows.go @@ -0,0 +1,27 @@ +// +build windows + +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 util + +import ( + "errors" +) + +func Umask(mask int) (old int, err error) { + return 0, errors.New("platform and architecture is not supported") +} diff --git a/test/e2e/kubectl.go b/test/e2e/kubectl.go index ac6c949f4ec..6a21e668b34 100644 --- a/test/e2e/kubectl.go +++ b/test/e2e/kubectl.go @@ -21,7 +21,9 @@ import ( "errors" "fmt" "io/ioutil" + "net" "net/http" + "os" "os/exec" "path/filepath" "regexp" @@ -544,8 +546,35 @@ var _ = Describe("Kubectl client", func() { Failf("Expected at least one supported apiversion, got %v", apiVersions) } }) - }) + It("should support --unix-socket=/path", func() { + By("Starting the proxy") + tmpdir, err := ioutil.TempDir("", "kubectl-proxy-unix") + if err != nil { + Failf("Failed to create temporary directory: %v", err) + } + path := filepath.Join(tmpdir, "test") + defer os.Remove(path) + defer os.Remove(tmpdir) + cmd := kubectlCmd("proxy", fmt.Sprintf("--unix-socket=%s", path)) + stdout, stderr, err := startCmdAndStreamOutput(cmd) + if err != nil { + Failf("Failed to start kubectl command: %v", err) + } + defer stdout.Close() + defer stderr.Close() + defer tryKill(cmd) + buf := make([]byte, 128) + if _, err = stdout.Read(buf); err != nil { + Failf("Expected output from kubectl proxy: %v", err) + } + By("retrieving proxy /api/ output") + _, err = curlUnix("http://unused/api", path) + if err != nil { + Failf("Failed get of /api at %s: %v", path, err) + } + }) + }) }) // Checks whether the output split by line contains the required elements. @@ -603,8 +632,19 @@ func startProxyServer() (int, *exec.Cmd, error) { return -1, cmd, fmt.Errorf("Failed to parse port from proxy stdout: %s", output) } -func curl(addr string) (string, error) { - resp, err := http.Get(addr) +func curlUnix(url string, path string) (string, error) { + dial := func(proto, addr string) (net.Conn, error) { + return net.Dial("unix", path) + } + transport := &http.Transport{ + Dial: dial, + } + return curlTransport(url, transport) +} + +func curlTransport(url string, transport *http.Transport) (string, error) { + client := &http.Client{Transport: transport} + resp, err := client.Get(url) if err != nil { return "", err } @@ -616,6 +656,10 @@ func curl(addr string) (string, error) { return string(body[:]), nil } +func curl(url string) (string, error) { + return curlTransport(url, &http.Transport{}) +} + func validateGuestbookApp(c *client.Client, ns string) { Logf("Waiting for frontend to serve content.") if !waitForGuestbookResponse(c, "get", "", `{"data": ""}`, guestbookStartupTimeout, ns) {