From 126529c6d0a20675de8f50939c57f23259d3e763 Mon Sep 17 00:00:00 2001 From: John Howard Date: Thu, 4 Jun 2015 06:30:14 -0700 Subject: [PATCH] Windows: Security warning based on server OS Signed-off-by: John Howard --- api/client/build.go | 21 +++++++++----- api/client/create.go | 2 +- api/client/events.go | 2 +- api/client/export.go | 2 +- api/client/import.go | 3 +- api/client/load.go | 2 +- api/client/logs.go | 3 +- api/client/save.go | 4 +-- api/client/utils.go | 57 +++++++++++++++++++++++--------------- api/server/server.go | 2 ++ pkg/httputils/httputils.go | 31 +++++++++++++++++++++ 11 files changed, 92 insertions(+), 37 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index ebe48b097f..05d4eaacf2 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -21,6 +21,7 @@ import ( "github.com/docker/docker/graph/tags" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" + "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/jsonmessage" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" @@ -188,12 +189,6 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } } - // windows: show error message about modified file permissions - // FIXME: this is not a valid warning when the daemon is running windows. should be removed once docker engine for windows can build. - if runtime.GOOS == "windows" { - fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a Linux Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`) - } - var body io.Reader // Setup an upload progress bar // FIXME: ProgressReader shouldn't be this annoying to use @@ -298,7 +293,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error { out: cli.out, headers: headers, } - err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts) + + serverResp, err := cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts) + + // Windows: show error message about modified file permissions. + if runtime.GOOS == "windows" { + h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")) + if err == nil { + if h.OS != "windows" { + fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`) + } + } + } + if jerr, ok := err.(*jsonmessage.JSONError); ok { // If no error code is set, default to 1 if jerr.Code == 0 { diff --git a/api/client/create.go b/api/client/create.go index 979c700e32..b97d2c4d57 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -52,7 +52,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { out: out, headers: map[string][]string{"X-Registry-Auth": registryAuthHeader}, } - if err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil { + if _, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil { return err } return nil diff --git a/api/client/events.go b/api/client/events.go index ee2932a433..d4829edfd4 100644 --- a/api/client/events.go +++ b/api/client/events.go @@ -55,7 +55,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error { rawTerminal: true, out: cli.out, } - if err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil { + if _, err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil { return err } return nil diff --git a/api/client/export.go b/api/client/export.go index bda7991fdd..87a8546d9a 100644 --- a/api/client/export.go +++ b/api/client/export.go @@ -38,7 +38,7 @@ func (cli *DockerCli) CmdExport(args ...string) error { rawTerminal: true, out: output, } - if err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil { + if _, err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil { return err } diff --git a/api/client/import.go b/api/client/import.go index d1f4ea0ccc..43edce3def 100644 --- a/api/client/import.go +++ b/api/client/import.go @@ -71,5 +71,6 @@ func (cli *DockerCli) CmdImport(args ...string) error { out: cli.out, } - return cli.stream("POST", "/images/create?"+v.Encode(), sopts) + _, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts) + return err } diff --git a/api/client/load.go b/api/client/load.go index 47350545fe..fb011a3111 100644 --- a/api/client/load.go +++ b/api/client/load.go @@ -34,7 +34,7 @@ func (cli *DockerCli) CmdLoad(args ...string) error { in: input, out: cli.out, } - if err := cli.stream("POST", "/images/load", sopts); err != nil { + if _, err := cli.stream("POST", "/images/load", sopts); err != nil { return err } return nil diff --git a/api/client/logs.go b/api/client/logs.go index fcfb09d25d..62924dac47 100644 --- a/api/client/logs.go +++ b/api/client/logs.go @@ -65,5 +65,6 @@ func (cli *DockerCli) CmdLogs(args ...string) error { err: cli.err, } - return cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts) + _, err = cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts) + return err } diff --git a/api/client/save.go b/api/client/save.go index 2d3d296540..8101837cbf 100644 --- a/api/client/save.go +++ b/api/client/save.go @@ -41,7 +41,7 @@ func (cli *DockerCli) CmdSave(args ...string) error { if len(cmd.Args()) == 1 { image := cmd.Arg(0) - if err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil { + if _, err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil { return err } } else { @@ -49,7 +49,7 @@ func (cli *DockerCli) CmdSave(args ...string) error { for _, arg := range cmd.Args() { v.Add("names", arg) } - if err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil { + if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil { return err } } diff --git a/api/client/utils.go b/api/client/utils.go index ec92a8d520..8f1188d8ed 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -33,6 +33,12 @@ var ( errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") ) +type serverResponse struct { + body io.ReadCloser + header http.Header + statusCode int +} + // HTTPClient creates a new HTTP client with the cli's client transport instance. func (cli *DockerCli) HTTPClient() *http.Client { return &http.Client{Transport: cli.transport} @@ -48,14 +54,20 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) { return params, nil } -func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, http.Header, int, error) { +func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) { + + serverResp := &serverResponse{ + body: nil, + statusCode: -1, + } + expectedPayload := (method == "POST" || method == "PUT") if expectedPayload && in == nil { in = bytes.NewReader([]byte{}) } req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.Version, path), in) if err != nil { - return nil, nil, -1, err + return serverResp, err } // Add CLI Config's HTTP Headers BEFORE we set the Docker headers @@ -79,33 +91,34 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m } resp, err := cli.HTTPClient().Do(req) - statusCode := -1 if resp != nil { - statusCode = resp.StatusCode + serverResp.statusCode = resp.StatusCode } if err != nil { if strings.Contains(err.Error(), "connection refused") { - return nil, nil, statusCode, errConnectionRefused + return serverResp, errConnectionRefused } if cli.tlsConfig == nil { - return nil, nil, statusCode, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?\n* Is your docker daemon up and running?", err) + return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?\n* Is your docker daemon up and running?", err) } - return nil, nil, statusCode, fmt.Errorf("An error occurred trying to connect: %v", err) + return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err) } - if statusCode < 200 || statusCode >= 400 { + if serverResp.statusCode < 200 || serverResp.statusCode >= 400 { body, err := ioutil.ReadAll(resp.Body) if err != nil { - return nil, nil, statusCode, err + return serverResp, err } if len(body) == 0 { - return nil, nil, statusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(statusCode), req.URL) + return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL) } - return nil, nil, statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body)) + return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body)) } - return resp.Body, resp.Header, statusCode, nil + serverResp.body = resp.Body + serverResp.header = resp.Header + return serverResp, nil } func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) { @@ -119,13 +132,13 @@ func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reade } // begin the request - body, hdr, statusCode, err := cli.clientRequest(method, path, in, map[string][]string{ + serverResp, err := cli.clientRequest(method, path, in, map[string][]string{ "X-Registry-Auth": registryAuthHeader, }) if err == nil && out != nil { // If we are streaming output, complete the stream since // errors may not appear until later. - err = cli.streamBody(body, hdr.Get("Content-Type"), true, out, nil) + err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil) } if err != nil { // Since errors in a stream appear after status 200 has been written, @@ -133,10 +146,10 @@ func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reade if strings.Contains(err.Error(), "Authentication is required") || strings.Contains(err.Error(), "Status 401") || strings.Contains(err.Error(), "status code 401") { - statusCode = http.StatusUnauthorized + serverResp.statusCode = http.StatusUnauthorized } } - return body, statusCode, err + return serverResp.body, serverResp.statusCode, err } // Resolve the Auth config relevant for this server @@ -166,8 +179,8 @@ func (cli *DockerCli) call(method, path string, data interface{}, headers map[st headers["Content-Type"] = []string{"application/json"} } - body, hdr, statusCode, err := cli.clientRequest(method, path, params, headers) - return body, hdr, statusCode, err + serverResp, err := cli.clientRequest(method, path, params, headers) + return serverResp.body, serverResp.header, serverResp.statusCode, err } type streamOpts struct { @@ -178,12 +191,12 @@ type streamOpts struct { headers map[string][]string } -func (cli *DockerCli) stream(method, path string, opts *streamOpts) error { - body, hdr, _, err := cli.clientRequest(method, path, opts.in, opts.headers) +func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) { + serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers) if err != nil { - return err + return serverResp, err } - return cli.streamBody(body, hdr.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err) + return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err) } func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error { diff --git a/api/server/server.go b/api/server/server.go index ea93916c83..fdb25f39ac 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1502,6 +1502,8 @@ func makeHttpHandler(logging bool, localMethod string, localRoute string, handle return } + w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")") + if err := handlerFunc(version, w, r, mux.Vars(r)); err != nil { logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err) httpError(w, err) diff --git a/pkg/httputils/httputils.go b/pkg/httputils/httputils.go index 02d22ee6f6..f1e5dcd1af 100644 --- a/pkg/httputils/httputils.go +++ b/pkg/httputils/httputils.go @@ -1,8 +1,11 @@ package httputils import ( + "errors" "fmt" "net/http" + "regexp" + "strings" "github.com/docker/docker/pkg/jsonmessage" ) @@ -25,3 +28,31 @@ func NewHTTPRequestError(msg string, res *http.Response) error { Code: res.StatusCode, } } + +type ServerHeader struct { + App string // docker + Ver string // 1.8.0-dev + OS string // windows or linux +} + +// parseServerHeader extracts pieces from am HTTP server header +// which is in the format "docker/version (os)" eg docker/1.8.0-dev (windows) +func ParseServerHeader(hdr string) (*ServerHeader, error) { + re := regexp.MustCompile(`.*\((.+)\).*$`) + r := &ServerHeader{} + if matches := re.FindStringSubmatch(hdr); matches != nil { + r.OS = matches[1] + parts := strings.Split(hdr, "/") + if len(parts) != 2 { + return nil, errors.New("Bad header: '/' missing") + } + r.App = parts[0] + v := strings.Split(parts[1], " ") + if len(v) != 2 { + return nil, errors.New("Bad header: Expected single space") + } + r.Ver = v[0] + return r, nil + } + return nil, errors.New("Bad header: Failed regex match") +}