Browse code

Ensure plugin returns correctly scoped paths

Before this change, volume management was relying on the fact that
everything the plugin mounts is visible on the host within the plugin's
rootfs. In practice this caused some issues with mount leaks, so we
changed the behavior such that mounts are not visible on the plugin's
rootfs, but available outside of it, which breaks volume management.

To fix the issue, allow the plugin to scope the path correctly rather
than assuming that everything is visible in `p.Rootfs`.
In practice this is just scoping the `PropagatedMount` paths to the
correct host path.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2017/12/15 00:27:10
Showing 11 changed files
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"errors"
5 5
 	"fmt"
6 6
 	"io"
7
-	"path/filepath"
8 7
 
9 8
 	"github.com/docker/docker/pkg/archive"
10 9
 	"github.com/docker/docker/pkg/containerfs"
... ...
@@ -143,7 +142,7 @@ func (d *graphDriverProxy) Get(id, mountLabel string) (containerfs.ContainerFS,
143 143
 	if ret.Err != "" {
144 144
 		err = errors.New(ret.Err)
145 145
 	}
146
-	return containerfs.NewLocalContainerFS(filepath.Join(d.p.BasePath(), ret.Dir)), err
146
+	return containerfs.NewLocalContainerFS(d.p.ScopedPath(ret.Dir)), err
147 147
 }
148 148
 
149 149
 func (d *graphDriverProxy) Put(id string) error {
... ...
@@ -3,7 +3,7 @@ package logger
3 3
 import (
4 4
 	"io"
5 5
 	"os"
6
-	"strings"
6
+	"path/filepath"
7 7
 	"sync"
8 8
 	"time"
9 9
 
... ...
@@ -19,7 +19,6 @@ type pluginAdapter struct {
19 19
 	driverName   string
20 20
 	id           string
21 21
 	plugin       logPlugin
22
-	basePath     string
23 22
 	fifoPath     string
24 23
 	capabilities Capability
25 24
 	logInfo      Info
... ...
@@ -58,7 +57,7 @@ func (a *pluginAdapter) Close() error {
58 58
 	a.mu.Lock()
59 59
 	defer a.mu.Unlock()
60 60
 
61
-	if err := a.plugin.StopLogging(strings.TrimPrefix(a.fifoPath, a.basePath)); err != nil {
61
+	if err := a.plugin.StopLogging(filepath.Join("/", "run", "docker", "logging", a.id)); err != nil {
62 62
 		return err
63 63
 	}
64 64
 
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"io"
6 6
 	"os"
7 7
 	"path/filepath"
8
-	"strings"
9 8
 
10 9
 	"github.com/docker/docker/api/types/plugins/logdriver"
11 10
 	getter "github.com/docker/docker/pkg/plugingetter"
... ...
@@ -39,18 +38,20 @@ func getPlugin(name string, mode int) (Creator, error) {
39 39
 	}
40 40
 
41 41
 	d := &logPluginProxy{p.Client()}
42
-	return makePluginCreator(name, d, p.BasePath()), nil
42
+	return makePluginCreator(name, d, p.ScopedPath), nil
43 43
 }
44 44
 
45
-func makePluginCreator(name string, l *logPluginProxy, basePath string) Creator {
45
+func makePluginCreator(name string, l *logPluginProxy, scopePath func(s string) string) Creator {
46 46
 	return func(logCtx Info) (logger Logger, err error) {
47 47
 		defer func() {
48 48
 			if err != nil {
49 49
 				pluginGetter.Get(name, extName, getter.Release)
50 50
 			}
51 51
 		}()
52
-		root := filepath.Join(basePath, "run", "docker", "logging")
53
-		if err := os.MkdirAll(root, 0700); err != nil {
52
+
53
+		unscopedPath := filepath.Join("/", "run", "docker", "logging")
54
+		logRoot := scopePath(unscopedPath)
55
+		if err := os.MkdirAll(logRoot, 0700); err != nil {
54 56
 			return nil, err
55 57
 		}
56 58
 
... ...
@@ -59,8 +60,7 @@ func makePluginCreator(name string, l *logPluginProxy, basePath string) Creator
59 59
 			driverName: name,
60 60
 			id:         id,
61 61
 			plugin:     l,
62
-			basePath:   basePath,
63
-			fifoPath:   filepath.Join(root, id),
62
+			fifoPath:   filepath.Join(logRoot, id),
64 63
 			logInfo:    logCtx,
65 64
 		}
66 65
 
... ...
@@ -77,7 +77,7 @@ func makePluginCreator(name string, l *logPluginProxy, basePath string) Creator
77 77
 		a.stream = stream
78 78
 		a.enc = logdriver.NewLogEntryEncoder(a.stream)
79 79
 
80
-		if err := l.StartLogging(strings.TrimPrefix(a.fifoPath, basePath), logCtx); err != nil {
80
+		if err := l.StartLogging(filepath.Join(unscopedPath, id), logCtx); err != nil {
81 81
 			return nil, errors.Wrapf(err, "error creating logger")
82 82
 		}
83 83
 
... ...
@@ -17,7 +17,7 @@ const (
17 17
 type CompatPlugin interface {
18 18
 	Client() *plugins.Client
19 19
 	Name() string
20
-	BasePath() string
20
+	ScopedPath(string) string
21 21
 	IsV1() bool
22 22
 }
23 23
 
... ...
@@ -2,8 +2,8 @@
2 2
 
3 3
 package plugins
4 4
 
5
-// BasePath returns the path to which all paths returned by the plugin are relative to.
6
-// For v1 plugins, this always returns the host's root directory.
7
-func (p *Plugin) BasePath() string {
8
-	return "/"
5
+// ScopedPath returns the path scoped to the plugin's rootfs.
6
+// For v1 plugins, this always returns the path unchanged as v1 plugins run directly on the host.
7
+func (p *Plugin) ScopedPath(s string) string {
8
+	return s
9 9
 }
... ...
@@ -1,8 +1,7 @@
1 1
 package plugins
2 2
 
3
-// BasePath returns the path to which all paths returned by the plugin are relative to.
4
-// For Windows v1 plugins, this returns an empty string, since the plugin is already aware
5
-// of the absolute path of the mount.
6
-func (p *Plugin) BasePath() string {
7
-	return ""
3
+// ScopedPath returns the path scoped to the plugin's rootfs.
4
+// For v1 plugins, this always returns the path unchanged as v1 plugins run directly on the host.
5
+func (p *Plugin) ScopedPath(s string) string {
6
+	return s
8 7
 }
... ...
@@ -65,7 +65,6 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
65 65
 			}
66 66
 		}
67 67
 	}
68
-
69 68
 	return pm.pluginPostStart(p, c)
70 69
 }
71 70
 
... ...
@@ -2,6 +2,7 @@ package v2
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"path/filepath"
5 6
 	"strings"
6 7
 	"sync"
7 8
 
... ...
@@ -39,10 +40,13 @@ func (e ErrInadequateCapability) Error() string {
39 39
 	return fmt.Sprintf("plugin does not provide %q capability", e.cap)
40 40
 }
41 41
 
42
-// BasePath returns the path to which all paths returned by the plugin are relative to.
43
-// For Plugin objects this returns the host path of the plugin container's rootfs.
44
-func (p *Plugin) BasePath() string {
45
-	return p.Rootfs
42
+// ScopedPath returns the path scoped to the plugin rootfs
43
+func (p *Plugin) ScopedPath(s string) string {
44
+	if p.PluginObj.Config.PropagatedMount != "" && strings.HasPrefix(s, p.PluginObj.Config.PropagatedMount) {
45
+		// re-scope to the propagated mount path on the host
46
+		return filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount", strings.TrimPrefix(s, p.PluginObj.Config.PropagatedMount))
47
+	}
48
+	return filepath.Join(p.Rootfs, s)
46 49
 }
47 50
 
48 51
 // Client returns the plugin client.
... ...
@@ -2,7 +2,6 @@ package volumedrivers
2 2
 
3 3
 import (
4 4
 	"errors"
5
-	"path/filepath"
6 5
 	"strings"
7 6
 	"time"
8 7
 
... ...
@@ -16,7 +15,7 @@ var (
16 16
 
17 17
 type volumeDriverAdapter struct {
18 18
 	name         string
19
-	baseHostPath string
19
+	scopePath    func(s string) string
20 20
 	capabilities *volume.Capability
21 21
 	proxy        *volumeDriverProxy
22 22
 }
... ...
@@ -30,10 +29,10 @@ func (a *volumeDriverAdapter) Create(name string, opts map[string]string) (volum
30 30
 		return nil, err
31 31
 	}
32 32
 	return &volumeAdapter{
33
-		proxy:        a.proxy,
34
-		name:         name,
35
-		driverName:   a.name,
36
-		baseHostPath: a.baseHostPath,
33
+		proxy:      a.proxy,
34
+		name:       name,
35
+		driverName: a.name,
36
+		scopePath:  a.scopePath,
37 37
 	}, nil
38 38
 }
39 39
 
... ...
@@ -41,13 +40,6 @@ func (a *volumeDriverAdapter) Remove(v volume.Volume) error {
41 41
 	return a.proxy.Remove(v.Name())
42 42
 }
43 43
 
44
-func hostPath(baseHostPath, path string) string {
45
-	if baseHostPath != "" {
46
-		path = filepath.Join(baseHostPath, path)
47
-	}
48
-	return path
49
-}
50
-
51 44
 func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
52 45
 	ls, err := a.proxy.List()
53 46
 	if err != nil {
... ...
@@ -57,11 +49,11 @@ func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
57 57
 	var out []volume.Volume
58 58
 	for _, vp := range ls {
59 59
 		out = append(out, &volumeAdapter{
60
-			proxy:        a.proxy,
61
-			name:         vp.Name,
62
-			baseHostPath: a.baseHostPath,
63
-			driverName:   a.name,
64
-			eMount:       hostPath(a.baseHostPath, vp.Mountpoint),
60
+			proxy:      a.proxy,
61
+			name:       vp.Name,
62
+			scopePath:  a.scopePath,
63
+			driverName: a.name,
64
+			eMount:     a.scopePath(vp.Mountpoint),
65 65
 		})
66 66
 	}
67 67
 	return out, nil
... ...
@@ -79,13 +71,13 @@ func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
79 79
 	}
80 80
 
81 81
 	return &volumeAdapter{
82
-		proxy:        a.proxy,
83
-		name:         v.Name,
84
-		driverName:   a.Name(),
85
-		eMount:       v.Mountpoint,
86
-		createdAt:    v.CreatedAt,
87
-		status:       v.Status,
88
-		baseHostPath: a.baseHostPath,
82
+		proxy:      a.proxy,
83
+		name:       v.Name,
84
+		driverName: a.Name(),
85
+		eMount:     v.Mountpoint,
86
+		createdAt:  v.CreatedAt,
87
+		status:     v.Status,
88
+		scopePath:  a.scopePath,
89 89
 	}, nil
90 90
 }
91 91
 
... ...
@@ -122,13 +114,13 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
122 122
 }
123 123
 
124 124
 type volumeAdapter struct {
125
-	proxy        *volumeDriverProxy
126
-	name         string
127
-	baseHostPath string
128
-	driverName   string
129
-	eMount       string    // ephemeral host volume path
130
-	createdAt    time.Time // time the directory was created
131
-	status       map[string]interface{}
125
+	proxy      *volumeDriverProxy
126
+	name       string
127
+	scopePath  func(string) string
128
+	driverName string
129
+	eMount     string    // ephemeral host volume path
130
+	createdAt  time.Time // time the directory was created
131
+	status     map[string]interface{}
132 132
 }
133 133
 
134 134
 type proxyVolume struct {
... ...
@@ -149,7 +141,7 @@ func (a *volumeAdapter) DriverName() string {
149 149
 func (a *volumeAdapter) Path() string {
150 150
 	if len(a.eMount) == 0 {
151 151
 		mountpoint, _ := a.proxy.Path(a.name)
152
-		a.eMount = hostPath(a.baseHostPath, mountpoint)
152
+		a.eMount = a.scopePath(mountpoint)
153 153
 	}
154 154
 	return a.eMount
155 155
 }
... ...
@@ -160,7 +152,7 @@ func (a *volumeAdapter) CachedPath() string {
160 160
 
161 161
 func (a *volumeAdapter) Mount(id string) (string, error) {
162 162
 	mountpoint, err := a.proxy.Mount(a.name, id)
163
-	a.eMount = hostPath(a.baseHostPath, mountpoint)
163
+	a.eMount = a.scopePath(mountpoint)
164 164
 	return a.eMount, err
165 165
 }
166 166
 
... ...
@@ -25,9 +25,9 @@ var drivers = &driverExtpoint{
25 25
 const extName = "VolumeDriver"
26 26
 
27 27
 // NewVolumeDriver returns a driver has the given name mapped on the given client.
28
-func NewVolumeDriver(name string, baseHostPath string, c client) volume.Driver {
28
+func NewVolumeDriver(name string, scopePath func(string) string, c client) volume.Driver {
29 29
 	proxy := &volumeDriverProxy{c}
30
-	return &volumeDriverAdapter{name: name, baseHostPath: baseHostPath, proxy: proxy}
30
+	return &volumeDriverAdapter{name: name, scopePath: scopePath, proxy: proxy}
31 31
 }
32 32
 
33 33
 // volumeDriver defines the available functions that volume plugins must implement.
... ...
@@ -129,7 +129,7 @@ func lookup(name string, mode int) (volume.Driver, error) {
129 129
 			return nil, errors.Wrap(err, "error looking up volume plugin "+name)
130 130
 		}
131 131
 
132
-		d := NewVolumeDriver(p.Name(), p.BasePath(), p.Client())
132
+		d := NewVolumeDriver(p.Name(), p.ScopedPath, p.Client())
133 133
 		if err := validateDriver(d); err != nil {
134 134
 			if mode > 0 {
135 135
 				// Undo any reference count changes from the initial `Get`
... ...
@@ -224,7 +224,7 @@ func GetAllDrivers() ([]volume.Driver, error) {
224 224
 			continue
225 225
 		}
226 226
 
227
-		ext := NewVolumeDriver(name, p.BasePath(), p.Client())
227
+		ext := NewVolumeDriver(name, p.ScopedPath, p.Client())
228 228
 		if p.IsV1() {
229 229
 			drivers.extensions[name] = ext
230 230
 		}
... ...
@@ -178,8 +178,8 @@ func (p *fakePlugin) IsV1() bool {
178 178
 	return false
179 179
 }
180 180
 
181
-func (p *fakePlugin) BasePath() string {
182
-	return ""
181
+func (p *fakePlugin) ScopedPath(s string) string {
182
+	return s
183 183
 }
184 184
 
185 185
 type fakePluginGetter struct {