Merge pull request #10608 from stefwalter/proxy-unix-socket

kubectl: Add proxy --unix-socket=/file/path option
This commit is contained in:
Dawn Chen 2015-08-06 10:47:19 -07:00
commit 5175bb0b91
9 changed files with 154 additions and 21 deletions

View File

@ -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=")

View File

@ -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.

View File

@ -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
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->

View File

@ -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)
}

View File

@ -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.

View File

@ -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)
}

27
pkg/util/umask.go Normal file
View File

@ -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
}

27
pkg/util/umask_windows.go Normal file
View File

@ -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")
}

View File

@ -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) {