Merge pull request #13740 from Microsoft/10662-securitywarning

Windows: Security warning based on server OS
This commit is contained in:
David Calavera 2015-06-26 13:09:03 -07:00
commit 389b806945
11 changed files with 92 additions and 37 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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