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>
This commit is contained in:
Brian Goff 2017-12-14 10:27:10 -05:00
parent a53930a04f
commit 0e5eaf8ee3
11 changed files with 60 additions and 68 deletions

View file

@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"io"
"path/filepath"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/containerfs"
@ -143,7 +142,7 @@ func (d *graphDriverProxy) Get(id, mountLabel string) (containerfs.ContainerFS,
if ret.Err != "" {
err = errors.New(ret.Err)
}
return containerfs.NewLocalContainerFS(filepath.Join(d.p.BasePath(), ret.Dir)), err
return containerfs.NewLocalContainerFS(d.p.ScopedPath(ret.Dir)), err
}
func (d *graphDriverProxy) Put(id string) error {

View file

@ -3,7 +3,7 @@ package logger
import (
"io"
"os"
"strings"
"path/filepath"
"sync"
"time"
@ -19,7 +19,6 @@ type pluginAdapter struct {
driverName string
id string
plugin logPlugin
basePath string
fifoPath string
capabilities Capability
logInfo Info
@ -58,7 +57,7 @@ func (a *pluginAdapter) Close() error {
a.mu.Lock()
defer a.mu.Unlock()
if err := a.plugin.StopLogging(strings.TrimPrefix(a.fifoPath, a.basePath)); err != nil {
if err := a.plugin.StopLogging(filepath.Join("/", "run", "docker", "logging", a.id)); err != nil {
return err
}

View file

@ -5,7 +5,6 @@ import (
"io"
"os"
"path/filepath"
"strings"
"github.com/docker/docker/api/types/plugins/logdriver"
getter "github.com/docker/docker/pkg/plugingetter"
@ -39,18 +38,20 @@ func getPlugin(name string, mode int) (Creator, error) {
}
d := &logPluginProxy{p.Client()}
return makePluginCreator(name, d, p.BasePath()), nil
return makePluginCreator(name, d, p.ScopedPath), nil
}
func makePluginCreator(name string, l *logPluginProxy, basePath string) Creator {
func makePluginCreator(name string, l *logPluginProxy, scopePath func(s string) string) Creator {
return func(logCtx Info) (logger Logger, err error) {
defer func() {
if err != nil {
pluginGetter.Get(name, extName, getter.Release)
}
}()
root := filepath.Join(basePath, "run", "docker", "logging")
if err := os.MkdirAll(root, 0700); err != nil {
unscopedPath := filepath.Join("/", "run", "docker", "logging")
logRoot := scopePath(unscopedPath)
if err := os.MkdirAll(logRoot, 0700); err != nil {
return nil, err
}
@ -59,8 +60,7 @@ func makePluginCreator(name string, l *logPluginProxy, basePath string) Creator
driverName: name,
id: id,
plugin: l,
basePath: basePath,
fifoPath: filepath.Join(root, id),
fifoPath: filepath.Join(logRoot, id),
logInfo: logCtx,
}
@ -77,7 +77,7 @@ func makePluginCreator(name string, l *logPluginProxy, basePath string) Creator
a.stream = stream
a.enc = logdriver.NewLogEntryEncoder(a.stream)
if err := l.StartLogging(strings.TrimPrefix(a.fifoPath, basePath), logCtx); err != nil {
if err := l.StartLogging(filepath.Join(unscopedPath, id), logCtx); err != nil {
return nil, errors.Wrapf(err, "error creating logger")
}

View file

@ -17,7 +17,7 @@ const (
type CompatPlugin interface {
Client() *plugins.Client
Name() string
BasePath() string
ScopedPath(string) string
IsV1() bool
}

View file

@ -2,8 +2,8 @@
package plugins
// BasePath returns the path to which all paths returned by the plugin are relative to.
// For v1 plugins, this always returns the host's root directory.
func (p *Plugin) BasePath() string {
return "/"
// ScopedPath returns the path scoped to the plugin's rootfs.
// For v1 plugins, this always returns the path unchanged as v1 plugins run directly on the host.
func (p *Plugin) ScopedPath(s string) string {
return s
}

View file

@ -1,8 +1,7 @@
package plugins
// BasePath returns the path to which all paths returned by the plugin are relative to.
// For Windows v1 plugins, this returns an empty string, since the plugin is already aware
// of the absolute path of the mount.
func (p *Plugin) BasePath() string {
return ""
// ScopedPath returns the path scoped to the plugin's rootfs.
// For v1 plugins, this always returns the path unchanged as v1 plugins run directly on the host.
func (p *Plugin) ScopedPath(s string) string {
return s
}

View file

@ -65,7 +65,6 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
}
}
}
return pm.pluginPostStart(p, c)
}

View file

@ -2,6 +2,7 @@ package v2
import (
"fmt"
"path/filepath"
"strings"
"sync"
@ -39,10 +40,13 @@ func (e ErrInadequateCapability) Error() string {
return fmt.Sprintf("plugin does not provide %q capability", e.cap)
}
// BasePath returns the path to which all paths returned by the plugin are relative to.
// For Plugin objects this returns the host path of the plugin container's rootfs.
func (p *Plugin) BasePath() string {
return p.Rootfs
// ScopedPath returns the path scoped to the plugin rootfs
func (p *Plugin) ScopedPath(s string) string {
if p.PluginObj.Config.PropagatedMount != "" && strings.HasPrefix(s, p.PluginObj.Config.PropagatedMount) {
// re-scope to the propagated mount path on the host
return filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount", strings.TrimPrefix(s, p.PluginObj.Config.PropagatedMount))
}
return filepath.Join(p.Rootfs, s)
}
// Client returns the plugin client.

View file

@ -2,7 +2,6 @@ package volumedrivers
import (
"errors"
"path/filepath"
"strings"
"time"
@ -16,7 +15,7 @@ var (
type volumeDriverAdapter struct {
name string
baseHostPath string
scopePath func(s string) string
capabilities *volume.Capability
proxy *volumeDriverProxy
}
@ -33,7 +32,7 @@ func (a *volumeDriverAdapter) Create(name string, opts map[string]string) (volum
proxy: a.proxy,
name: name,
driverName: a.name,
baseHostPath: a.baseHostPath,
scopePath: a.scopePath,
}, nil
}
@ -41,13 +40,6 @@ func (a *volumeDriverAdapter) Remove(v volume.Volume) error {
return a.proxy.Remove(v.Name())
}
func hostPath(baseHostPath, path string) string {
if baseHostPath != "" {
path = filepath.Join(baseHostPath, path)
}
return path
}
func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
ls, err := a.proxy.List()
if err != nil {
@ -59,9 +51,9 @@ func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
out = append(out, &volumeAdapter{
proxy: a.proxy,
name: vp.Name,
baseHostPath: a.baseHostPath,
scopePath: a.scopePath,
driverName: a.name,
eMount: hostPath(a.baseHostPath, vp.Mountpoint),
eMount: a.scopePath(vp.Mountpoint),
})
}
return out, nil
@ -85,7 +77,7 @@ func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
eMount: v.Mountpoint,
createdAt: v.CreatedAt,
status: v.Status,
baseHostPath: a.baseHostPath,
scopePath: a.scopePath,
}, nil
}
@ -124,7 +116,7 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
type volumeAdapter struct {
proxy *volumeDriverProxy
name string
baseHostPath string
scopePath func(string) string
driverName string
eMount string // ephemeral host volume path
createdAt time.Time // time the directory was created
@ -149,7 +141,7 @@ func (a *volumeAdapter) DriverName() string {
func (a *volumeAdapter) Path() string {
if len(a.eMount) == 0 {
mountpoint, _ := a.proxy.Path(a.name)
a.eMount = hostPath(a.baseHostPath, mountpoint)
a.eMount = a.scopePath(mountpoint)
}
return a.eMount
}
@ -160,7 +152,7 @@ func (a *volumeAdapter) CachedPath() string {
func (a *volumeAdapter) Mount(id string) (string, error) {
mountpoint, err := a.proxy.Mount(a.name, id)
a.eMount = hostPath(a.baseHostPath, mountpoint)
a.eMount = a.scopePath(mountpoint)
return a.eMount, err
}

View file

@ -25,9 +25,9 @@ var drivers = &driverExtpoint{
const extName = "VolumeDriver"
// NewVolumeDriver returns a driver has the given name mapped on the given client.
func NewVolumeDriver(name string, baseHostPath string, c client) volume.Driver {
func NewVolumeDriver(name string, scopePath func(string) string, c client) volume.Driver {
proxy := &volumeDriverProxy{c}
return &volumeDriverAdapter{name: name, baseHostPath: baseHostPath, proxy: proxy}
return &volumeDriverAdapter{name: name, scopePath: scopePath, proxy: proxy}
}
// volumeDriver defines the available functions that volume plugins must implement.
@ -129,7 +129,7 @@ func lookup(name string, mode int) (volume.Driver, error) {
return nil, errors.Wrap(err, "error looking up volume plugin "+name)
}
d := NewVolumeDriver(p.Name(), p.BasePath(), p.Client())
d := NewVolumeDriver(p.Name(), p.ScopedPath, p.Client())
if err := validateDriver(d); err != nil {
if mode > 0 {
// Undo any reference count changes from the initial `Get`
@ -224,7 +224,7 @@ func GetAllDrivers() ([]volume.Driver, error) {
continue
}
ext := NewVolumeDriver(name, p.BasePath(), p.Client())
ext := NewVolumeDriver(name, p.ScopedPath, p.Client())
if p.IsV1() {
drivers.extensions[name] = ext
}

View file

@ -178,8 +178,8 @@ func (p *fakePlugin) IsV1() bool {
return false
}
func (p *fakePlugin) BasePath() string {
return ""
func (p *fakePlugin) ScopedPath(s string) string {
return s
}
type fakePluginGetter struct {