kubectl: Add proxy --unix-socket=/file/path option

Proxies on a TCP port are accessible outside the current security
context (eg: uid). Add support for having the proxy listen on a
unix socket, which has permissions applied to it.

We make sure the socket starts its life only accessible by the
current user using Umask.

This is useful for applications like Cockpit and other tools which
want the help of kubectl to handle authentication, configuration and
transport security, but also want to not make that accessible to
all users on a multi-user system.
This commit is contained in:
Stef Walter 2015-07-01 10:17:53 +02:00
parent cd30bd6167
commit f6da3fdbe1
6 changed files with 53 additions and 18 deletions

View File

@ -595,6 +595,8 @@ _kubectl_proxy()
two_word_flags+=("-p") two_word_flags+=("-p")
flags+=("--reject-methods=") flags+=("--reject-methods=")
flags+=("--reject-paths=") flags+=("--reject-paths=")
flags+=("--unix-socket=")
two_word_flags+=("-u")
flags+=("--www=") flags+=("--www=")
two_word_flags+=("-w") two_word_flags+=("-w")
flags+=("--www-prefix=") flags+=("--www-prefix=")

View File

@ -52,7 +52,7 @@ The above lets you 'curl localhost:8001/custom/api/v1/pods'
.PP .PP
\fB\-\-disable\-filter\fP=false \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 .PP
\fB\-h\fP, \fB\-\-help\fP=false \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" \fB\-\-reject\-paths\fP="^/api/.\fI/exec,^/api/.\fP/run"
Regular expression for paths that the proxy should reject. 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 .PP
\fB\-w\fP, \fB\-\-www\fP="" \fB\-w\fP, \fB\-\-www\fP=""
Also serve static files from the given directory under the specified prefix. 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-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. --accept-paths="^/.*": Regular expression for paths that the proxy should accept.
--api-prefix="/api/": Prefix to serve the proxied API under. --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 -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. -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-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. --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. -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. -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 * [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 --> <!-- BEGIN MUNGE: GENERATED_ANALYTICS -->

View File

@ -17,8 +17,10 @@ limitations under the License.
package cmd package cmd
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"net"
"strings" "strings"
"github.com/golang/glog" "github.com/golang/glog"
@ -28,6 +30,7 @@ import (
) )
const ( const (
default_port = 8001
proxy_example = `// Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/ proxy_example = `// Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/
$ kubectl proxy --port=8011 --www=./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("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("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().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().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. Use with caution.") 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 return cmd
} }
func RunProxy(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error { func RunProxy(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error {
path := cmdutil.GetFlagString(cmd, "unix-socket")
port := cmdutil.GetFlagInt(cmd, "port") 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() clientConfig, err := f.ClientConfig()
if err != nil { if err != nil {
return err 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")), AcceptHosts: kubectl.MakeRegexpArrayOrDie(cmdutil.GetFlagString(cmd, "accept-hosts")),
} }
if cmdutil.GetFlagBool(cmd, "disable-filter") { 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 filter = nil
} }
server, err := kubectl.NewProxyServer(port, cmdutil.GetFlagString(cmd, "www"), apiProxyPrefix, staticPrefix, filter, clientConfig) server, err := kubectl.NewProxyServer(cmdutil.GetFlagString(cmd, "www"), apiProxyPrefix, staticPrefix, filter, clientConfig)
if err != nil {
return err
}
// Separate listening from serving so we can report the bound port // Separate listening from serving so we can report the bound port
// when it is chosen by os (port == 0) // when it is chosen by os (eg: port == 0)
l, err := server.Listen() var l net.Listener
if path == "" {
l, err = server.Listen(port)
} else {
l, err = server.ListenUnix(path)
}
if err != nil { if err != nil {
glog.Fatal(err) glog.Fatal(err)
} }

View File

@ -22,12 +22,14 @@ import (
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"os"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/client" "k8s.io/kubernetes/pkg/client"
"k8s.io/kubernetes/pkg/util"
) )
const ( 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. // ProxyServer is a http.Handler which proxies Kubernetes APIs to remote API server.
type ProxyServer struct { type ProxyServer struct {
handler http.Handler handler http.Handler
port int
} }
// NewProxyServer creates and installs a new ProxyServer. // NewProxyServer creates and installs a new ProxyServer.
// It automatically registers the created ProxyServer to http.DefaultServeMux. // It automatically registers the created ProxyServer to http.DefaultServeMux.
// 'filter', if non-nil, protects requests to the api only. // '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 host := cfg.Host
if !strings.HasSuffix(host, "/") { if !strings.HasSuffix(host, "/") {
host = host + "/" host = host + "/"
@ -174,12 +175,26 @@ func NewProxyServer(port int, filebase string, apiProxyPrefix string, staticPref
// serving their working directory by default. // serving their working directory by default.
mux.Handle(staticPrefix, newFileHandler(staticPrefix, filebase)) 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. // Listen is a simple wrapper around net.Listen.
func (s *ProxyServer) Listen() (net.Listener, error) { func (s *ProxyServer) Listen(port int) (net.Listener, error) {
return net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", s.port)) 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. // Serve starts the server using given listener, loops forever.

View File

@ -287,7 +287,7 @@ func TestPathHandling(t *testing.T) {
for _, item := range table { for _, item := range table {
func() { 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 { if err != nil {
t.Fatalf("%#v: %v", item, err) t.Fatalf("%#v: %v", item, err)
} }