mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Windows: Security warning based on server OS
Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
parent
31ae9d876a
commit
126529c6d0
11 changed files with 92 additions and 37 deletions
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/docker/docker/graph/tags"
|
"github.com/docker/docker/graph/tags"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/fileutils"
|
"github.com/docker/docker/pkg/fileutils"
|
||||||
|
"github.com/docker/docker/pkg/httputils"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"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
|
var body io.Reader
|
||||||
// Setup an upload progress bar
|
// Setup an upload progress bar
|
||||||
// FIXME: ProgressReader shouldn't be this annoying to use
|
// FIXME: ProgressReader shouldn't be this annoying to use
|
||||||
|
@ -298,7 +293,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
out: cli.out,
|
out: cli.out,
|
||||||
headers: headers,
|
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 jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||||
// If no error code is set, default to 1
|
// If no error code is set, default to 1
|
||||||
if jerr.Code == 0 {
|
if jerr.Code == 0 {
|
||||||
|
|
|
@ -52,7 +52,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
||||||
out: out,
|
out: out,
|
||||||
headers: map[string][]string{"X-Registry-Auth": registryAuthHeader},
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
rawTerminal: true,
|
rawTerminal: true,
|
||||||
out: cli.out,
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (cli *DockerCli) CmdExport(args ...string) error {
|
||||||
rawTerminal: true,
|
rawTerminal: true,
|
||||||
out: output,
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,5 +71,6 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
||||||
out: cli.out,
|
out: cli.out,
|
||||||
}
|
}
|
||||||
|
|
||||||
return cli.stream("POST", "/images/create?"+v.Encode(), sopts)
|
_, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
|
||||||
in: input,
|
in: input,
|
||||||
out: cli.out,
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -65,5 +65,6 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||||
err: cli.err,
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
||||||
|
|
||||||
if len(cmd.Args()) == 1 {
|
if len(cmd.Args()) == 1 {
|
||||||
image := cmd.Arg(0)
|
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
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -49,7 +49,7 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
||||||
for _, arg := range cmd.Args() {
|
for _, arg := range cmd.Args() {
|
||||||
v.Add("names", arg)
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,12 @@ var (
|
||||||
errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
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.
|
// HTTPClient creates a new HTTP client with the cli's client transport instance.
|
||||||
func (cli *DockerCli) HTTPClient() *http.Client {
|
func (cli *DockerCli) HTTPClient() *http.Client {
|
||||||
return &http.Client{Transport: cli.transport}
|
return &http.Client{Transport: cli.transport}
|
||||||
|
@ -48,14 +54,20 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||||
return params, nil
|
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")
|
expectedPayload := (method == "POST" || method == "PUT")
|
||||||
if expectedPayload && in == nil {
|
if expectedPayload && in == nil {
|
||||||
in = bytes.NewReader([]byte{})
|
in = bytes.NewReader([]byte{})
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.Version, path), in)
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.Version, path), in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, -1, err
|
return serverResp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
// 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)
|
resp, err := cli.HTTPClient().Do(req)
|
||||||
statusCode := -1
|
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
statusCode = resp.StatusCode
|
serverResp.statusCode = resp.StatusCode
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
return nil, nil, statusCode, errConnectionRefused
|
return serverResp, errConnectionRefused
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli.tlsConfig == nil {
|
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)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, statusCode, err
|
return serverResp, err
|
||||||
}
|
}
|
||||||
if len(body) == 0 {
|
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) {
|
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
|
// 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,
|
"X-Registry-Auth": registryAuthHeader,
|
||||||
})
|
})
|
||||||
if err == nil && out != nil {
|
if err == nil && out != nil {
|
||||||
// If we are streaming output, complete the stream since
|
// If we are streaming output, complete the stream since
|
||||||
// errors may not appear until later.
|
// 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 {
|
if err != nil {
|
||||||
// Since errors in a stream appear after status 200 has been written,
|
// 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") ||
|
if strings.Contains(err.Error(), "Authentication is required") ||
|
||||||
strings.Contains(err.Error(), "Status 401") ||
|
strings.Contains(err.Error(), "Status 401") ||
|
||||||
strings.Contains(err.Error(), "status code 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
|
// 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"}
|
headers["Content-Type"] = []string{"application/json"}
|
||||||
}
|
}
|
||||||
|
|
||||||
body, hdr, statusCode, err := cli.clientRequest(method, path, params, headers)
|
serverResp, err := cli.clientRequest(method, path, params, headers)
|
||||||
return body, hdr, statusCode, err
|
return serverResp.body, serverResp.header, serverResp.statusCode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamOpts struct {
|
type streamOpts struct {
|
||||||
|
@ -178,12 +191,12 @@ type streamOpts struct {
|
||||||
headers map[string][]string
|
headers map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) stream(method, path string, opts *streamOpts) error {
|
func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) {
|
||||||
body, hdr, _, err := cli.clientRequest(method, path, opts.in, opts.headers)
|
serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers)
|
||||||
if err != nil {
|
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 {
|
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
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
||||||
|
|
||||||
if err := handlerFunc(version, w, r, mux.Vars(r)); err != nil {
|
if err := handlerFunc(version, w, r, mux.Vars(r)); err != nil {
|
||||||
logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err)
|
logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err)
|
||||||
httpError(w, err)
|
httpError(w, err)
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package httputils
|
package httputils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
)
|
)
|
||||||
|
@ -25,3 +28,31 @@ func NewHTTPRequestError(msg string, res *http.Response) error {
|
||||||
Code: res.StatusCode,
|
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")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue