Browse code

Windows: Security warning based on server OS

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2015/06/04 22:30:14
Showing 11 changed files
... ...
@@ -21,6 +21,7 @@ import (
21 21
 	"github.com/docker/docker/graph/tags"
22 22
 	"github.com/docker/docker/pkg/archive"
23 23
 	"github.com/docker/docker/pkg/fileutils"
24
+	"github.com/docker/docker/pkg/httputils"
24 25
 	"github.com/docker/docker/pkg/jsonmessage"
25 26
 	flag "github.com/docker/docker/pkg/mflag"
26 27
 	"github.com/docker/docker/pkg/parsers"
... ...
@@ -188,12 +189,6 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
188 188
 		}
189 189
 	}
190 190
 
191
-	// windows: show error message about modified file permissions
192
-	// FIXME: this is not a valid warning when the daemon is running windows. should be removed once docker engine for windows can build.
193
-	if runtime.GOOS == "windows" {
194
-		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.`)
195
-	}
196
-
197 191
 	var body io.Reader
198 192
 	// Setup an upload progress bar
199 193
 	// FIXME: ProgressReader shouldn't be this annoying to use
... ...
@@ -298,7 +293,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
298 298
 		out:         cli.out,
299 299
 		headers:     headers,
300 300
 	}
301
-	err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts)
301
+
302
+	serverResp, err := cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts)
303
+
304
+	// Windows: show error message about modified file permissions.
305
+	if runtime.GOOS == "windows" {
306
+		h, err := httputils.ParseServerHeader(serverResp.header.Get("Server"))
307
+		if err == nil {
308
+			if h.OS != "windows" {
309
+				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.`)
310
+			}
311
+		}
312
+	}
313
+
302 314
 	if jerr, ok := err.(*jsonmessage.JSONError); ok {
303 315
 		// If no error code is set, default to 1
304 316
 		if jerr.Code == 0 {
... ...
@@ -52,7 +52,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
52 52
 		out:         out,
53 53
 		headers:     map[string][]string{"X-Registry-Auth": registryAuthHeader},
54 54
 	}
55
-	if err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
55
+	if _, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
56 56
 		return err
57 57
 	}
58 58
 	return nil
... ...
@@ -55,7 +55,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
55 55
 		rawTerminal: true,
56 56
 		out:         cli.out,
57 57
 	}
58
-	if err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
58
+	if _, err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
59 59
 		return err
60 60
 	}
61 61
 	return nil
... ...
@@ -38,7 +38,7 @@ func (cli *DockerCli) CmdExport(args ...string) error {
38 38
 		rawTerminal: true,
39 39
 		out:         output,
40 40
 	}
41
-	if err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
41
+	if _, err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
42 42
 		return err
43 43
 	}
44 44
 
... ...
@@ -71,5 +71,6 @@ func (cli *DockerCli) CmdImport(args ...string) error {
71 71
 		out:         cli.out,
72 72
 	}
73 73
 
74
-	return cli.stream("POST", "/images/create?"+v.Encode(), sopts)
74
+	_, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts)
75
+	return err
75 76
 }
... ...
@@ -34,7 +34,7 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
34 34
 		in:          input,
35 35
 		out:         cli.out,
36 36
 	}
37
-	if err := cli.stream("POST", "/images/load", sopts); err != nil {
37
+	if _, err := cli.stream("POST", "/images/load", sopts); err != nil {
38 38
 		return err
39 39
 	}
40 40
 	return nil
... ...
@@ -65,5 +65,6 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
65 65
 		err:         cli.err,
66 66
 	}
67 67
 
68
-	return cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts)
68
+	_, err = cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts)
69
+	return err
69 70
 }
... ...
@@ -41,7 +41,7 @@ func (cli *DockerCli) CmdSave(args ...string) error {
41 41
 
42 42
 	if len(cmd.Args()) == 1 {
43 43
 		image := cmd.Arg(0)
44
-		if err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
44
+		if _, err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
45 45
 			return err
46 46
 		}
47 47
 	} else {
... ...
@@ -49,7 +49,7 @@ func (cli *DockerCli) CmdSave(args ...string) error {
49 49
 		for _, arg := range cmd.Args() {
50 50
 			v.Add("names", arg)
51 51
 		}
52
-		if err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
52
+		if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
53 53
 			return err
54 54
 		}
55 55
 	}
... ...
@@ -33,6 +33,12 @@ var (
33 33
 	errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
34 34
 )
35 35
 
36
+type serverResponse struct {
37
+	body       io.ReadCloser
38
+	header     http.Header
39
+	statusCode int
40
+}
41
+
36 42
 // HTTPClient creates a new HTTP client with the cli's client transport instance.
37 43
 func (cli *DockerCli) HTTPClient() *http.Client {
38 44
 	return &http.Client{Transport: cli.transport}
... ...
@@ -48,14 +54,20 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
48 48
 	return params, nil
49 49
 }
50 50
 
51
-func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
51
+func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) {
52
+
53
+	serverResp := &serverResponse{
54
+		body:       nil,
55
+		statusCode: -1,
56
+	}
57
+
52 58
 	expectedPayload := (method == "POST" || method == "PUT")
53 59
 	if expectedPayload && in == nil {
54 60
 		in = bytes.NewReader([]byte{})
55 61
 	}
56 62
 	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.Version, path), in)
57 63
 	if err != nil {
58
-		return nil, nil, -1, err
64
+		return serverResp, err
59 65
 	}
60 66
 
61 67
 	// 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
79 79
 	}
80 80
 
81 81
 	resp, err := cli.HTTPClient().Do(req)
82
-	statusCode := -1
83 82
 	if resp != nil {
84
-		statusCode = resp.StatusCode
83
+		serverResp.statusCode = resp.StatusCode
85 84
 	}
86 85
 	if err != nil {
87 86
 		if strings.Contains(err.Error(), "connection refused") {
88
-			return nil, nil, statusCode, errConnectionRefused
87
+			return serverResp, errConnectionRefused
89 88
 		}
90 89
 
91 90
 		if cli.tlsConfig == nil {
92
-			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)
91
+			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)
93 92
 		}
94
-		return nil, nil, statusCode, fmt.Errorf("An error occurred trying to connect: %v", err)
93
+		return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
95 94
 	}
96 95
 
97
-	if statusCode < 200 || statusCode >= 400 {
96
+	if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
98 97
 		body, err := ioutil.ReadAll(resp.Body)
99 98
 		if err != nil {
100
-			return nil, nil, statusCode, err
99
+			return serverResp, err
101 100
 		}
102 101
 		if len(body) == 0 {
103
-			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)
102
+			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)
104 103
 		}
105
-		return nil, nil, statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
104
+		return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
106 105
 	}
107 106
 
108
-	return resp.Body, resp.Header, statusCode, nil
107
+	serverResp.body = resp.Body
108
+	serverResp.header = resp.Header
109
+	return serverResp, nil
109 110
 }
110 111
 
111 112
 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
119 119
 		}
120 120
 
121 121
 		// begin the request
122
-		body, hdr, statusCode, err := cli.clientRequest(method, path, in, map[string][]string{
122
+		serverResp, err := cli.clientRequest(method, path, in, map[string][]string{
123 123
 			"X-Registry-Auth": registryAuthHeader,
124 124
 		})
125 125
 		if err == nil && out != nil {
126 126
 			// If we are streaming output, complete the stream since
127 127
 			// errors may not appear until later.
128
-			err = cli.streamBody(body, hdr.Get("Content-Type"), true, out, nil)
128
+			err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil)
129 129
 		}
130 130
 		if err != nil {
131 131
 			// 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
133 133
 			if strings.Contains(err.Error(), "Authentication is required") ||
134 134
 				strings.Contains(err.Error(), "Status 401") ||
135 135
 				strings.Contains(err.Error(), "status code 401") {
136
-				statusCode = http.StatusUnauthorized
136
+				serverResp.statusCode = http.StatusUnauthorized
137 137
 			}
138 138
 		}
139
-		return body, statusCode, err
139
+		return serverResp.body, serverResp.statusCode, err
140 140
 	}
141 141
 
142 142
 	// Resolve the Auth config relevant for this server
... ...
@@ -166,8 +179,8 @@ func (cli *DockerCli) call(method, path string, data interface{}, headers map[st
166 166
 		headers["Content-Type"] = []string{"application/json"}
167 167
 	}
168 168
 
169
-	body, hdr, statusCode, err := cli.clientRequest(method, path, params, headers)
170
-	return body, hdr, statusCode, err
169
+	serverResp, err := cli.clientRequest(method, path, params, headers)
170
+	return serverResp.body, serverResp.header, serverResp.statusCode, err
171 171
 }
172 172
 
173 173
 type streamOpts struct {
... ...
@@ -178,12 +191,12 @@ type streamOpts struct {
178 178
 	headers     map[string][]string
179 179
 }
180 180
 
181
-func (cli *DockerCli) stream(method, path string, opts *streamOpts) error {
182
-	body, hdr, _, err := cli.clientRequest(method, path, opts.in, opts.headers)
181
+func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) {
182
+	serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers)
183 183
 	if err != nil {
184
-		return err
184
+		return serverResp, err
185 185
 	}
186
-	return cli.streamBody(body, hdr.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err)
186
+	return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err)
187 187
 }
188 188
 
189 189
 func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error {
... ...
@@ -1502,6 +1502,8 @@ func makeHttpHandler(logging bool, localMethod string, localRoute string, handle
1502 1502
 			return
1503 1503
 		}
1504 1504
 
1505
+		w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
1506
+
1505 1507
 		if err := handlerFunc(version, w, r, mux.Vars(r)); err != nil {
1506 1508
 			logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err)
1507 1509
 			httpError(w, err)
... ...
@@ -1,8 +1,11 @@
1 1
 package httputils
2 2
 
3 3
 import (
4
+	"errors"
4 5
 	"fmt"
5 6
 	"net/http"
7
+	"regexp"
8
+	"strings"
6 9
 
7 10
 	"github.com/docker/docker/pkg/jsonmessage"
8 11
 )
... ...
@@ -25,3 +28,31 @@ func NewHTTPRequestError(msg string, res *http.Response) error {
25 25
 		Code:    res.StatusCode,
26 26
 	}
27 27
 }
28
+
29
+type ServerHeader struct {
30
+	App string // docker
31
+	Ver string // 1.8.0-dev
32
+	OS  string // windows or linux
33
+}
34
+
35
+// parseServerHeader extracts pieces from am HTTP server header
36
+// which is in the format "docker/version (os)" eg docker/1.8.0-dev (windows)
37
+func ParseServerHeader(hdr string) (*ServerHeader, error) {
38
+	re := regexp.MustCompile(`.*\((.+)\).*$`)
39
+	r := &ServerHeader{}
40
+	if matches := re.FindStringSubmatch(hdr); matches != nil {
41
+		r.OS = matches[1]
42
+		parts := strings.Split(hdr, "/")
43
+		if len(parts) != 2 {
44
+			return nil, errors.New("Bad header: '/' missing")
45
+		}
46
+		r.App = parts[0]
47
+		v := strings.Split(parts[1], " ")
48
+		if len(v) != 2 {
49
+			return nil, errors.New("Bad header: Expected single space")
50
+		}
51
+		r.Ver = v[0]
52
+		return r, nil
53
+	}
54
+	return nil, errors.New("Bad header: Failed regex match")
55
+}