Move volume name validation to the local driver.
Delegate validation tasks to the volume drivers. It's up to them to decide whether a name is valid or not. Restrict volume names for the local driver to prevent creating mount points outside docker's volumes directory. Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
parent
609961ddcc
commit
d6d60287ee
7 changed files with 81 additions and 20 deletions
|
@ -12,7 +12,6 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -59,8 +58,8 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
validContainerNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]`
|
||||
validContainerNamePattern = regexp.MustCompile(`^/?` + validContainerNameChars + `+$`)
|
||||
validContainerNameChars = utils.RestrictedNameChars
|
||||
validContainerNamePattern = utils.RestrictedNamePattern
|
||||
|
||||
errSystemNotSupported = errors.New("The Docker daemon is not supported on this platform.")
|
||||
)
|
||||
|
|
|
@ -24,10 +24,6 @@ func TestParseBindMount(t *testing.T) {
|
|||
{"name:/tmp:ro", "local", "/tmp", "", "name", "local", false, false},
|
||||
{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "local", true, false},
|
||||
{"/tmp:tmp", "", "", "", "", "", true, true},
|
||||
{"./name:/tmp", "", "", "", "", "", true, true},
|
||||
{"../name:/tmp", "", "", "", "", "", true, true},
|
||||
{"./:/tmp", "", "", "", "", "", true, true},
|
||||
{"../:/tmp", "", "", "", "", "", true, true},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
@ -103,12 +102,6 @@ func parseBindMount(spec, volumeDriver string) (*mountPoint, error) {
|
|||
}
|
||||
|
||||
if len(source) == 0 {
|
||||
//validate the name of named volume
|
||||
nameRegex := regexp.MustCompile(`(^.+[^0-9A-Za-z_]+$)|(/)`)
|
||||
if nameRegex.MatchString(name) {
|
||||
return nil, derr.ErrorCodeVolumeName.WithArgs(name)
|
||||
}
|
||||
|
||||
bind.Driver = volumeDriver
|
||||
if len(bind.Driver) == 0 {
|
||||
bind.Driver = volume.DefaultDriverName
|
||||
|
|
|
@ -387,10 +387,10 @@ var (
|
|||
|
||||
// ErrorCodeVolumeName is generated when the name of named volume isn't valid.
|
||||
ErrorCodeVolumeName = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||
Value: "VOLUMENAME",
|
||||
Message: "%s looks like a relative path, but it's taken as a name. And it is not a valid name.",
|
||||
Description: "The name of named volume is invalid",
|
||||
HTTPStatusCode: http.StatusInternalServerError,
|
||||
Value: "VOLUME_NAME_INVALID",
|
||||
Message: "%s includes invalid characters for a local volume name, only %s are allowed",
|
||||
Description: "The name of volume is invalid",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
})
|
||||
|
||||
// ErrorCodeVolumeFromBlank is generated when path to a volume is blank.
|
||||
|
|
9
utils/names.go
Normal file
9
utils/names.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package utils
|
||||
|
||||
import "regexp"
|
||||
|
||||
// RestrictedNameChars collects the characters allowed to represent a name, normally used to validate container and volume names.
|
||||
const RestrictedNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]`
|
||||
|
||||
// RestrictedNamePattern is a regular expression to validate names against the collection of restricted characters.
|
||||
var RestrictedNamePattern = regexp.MustCompile(`^/?` + RestrictedNameChars + `+$`)
|
|
@ -11,7 +11,9 @@ import (
|
|||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
derr "github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/docker/volume"
|
||||
)
|
||||
|
||||
|
@ -23,8 +25,14 @@ const (
|
|||
volumesPathName = "volumes"
|
||||
)
|
||||
|
||||
// ErrNotFound is the typed error returned when the requested volume name can't be found
|
||||
var ErrNotFound = errors.New("volume not found")
|
||||
var (
|
||||
// ErrNotFound is the typed error returned when the requested volume name can't be found
|
||||
ErrNotFound = errors.New("volume not found")
|
||||
// volumeNameRegex ensures the name asigned for the volume is valid.
|
||||
// This name is used to create the bind directory, so we need to avoid characters that
|
||||
// would make the path to escape the root directory.
|
||||
volumeNameRegex = utils.RestrictedNamePattern
|
||||
)
|
||||
|
||||
// New instantiates a new Root instance with the provided scope. Scope
|
||||
// is the base path that the Root instance uses to store its
|
||||
|
@ -96,6 +104,10 @@ func (r *Root) Name() string {
|
|||
// the underlying directory tree required for this volume in the
|
||||
// process.
|
||||
func (r *Root) Create(name string, _ map[string]string) (volume.Volume, error) {
|
||||
if err := r.validateName(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
|
@ -174,6 +186,13 @@ func (r *Root) Get(name string) (volume.Volume, error) {
|
|||
return v, nil
|
||||
}
|
||||
|
||||
func (r *Root) validateName(name string) error {
|
||||
if !volumeNameRegex.MatchString(name) {
|
||||
return derr.ErrorCodeVolumeName.WithArgs(name, utils.RestrictedNameChars)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// localVolume implements the Volume interface from the volume package and
|
||||
// represents the volumes created by Root.
|
||||
type localVolume struct {
|
||||
|
|
|
@ -79,3 +79,48 @@ func TestInitializeWithVolumes(t *testing.T) {
|
|||
t.Fatal("expected to re-initialize root with existing volumes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
rootDir, err := ioutil.TempDir("", "local-volume-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(rootDir)
|
||||
|
||||
r, err := New(rootDir, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cases := map[string]bool{
|
||||
"name": true,
|
||||
"name-with-dash": true,
|
||||
"name_with_underscore": true,
|
||||
"name/with/slash": false,
|
||||
"name/with/../../slash": false,
|
||||
"./name": false,
|
||||
"../name": false,
|
||||
"./": false,
|
||||
"../": false,
|
||||
"~": false,
|
||||
".": false,
|
||||
"..": false,
|
||||
"...": false,
|
||||
}
|
||||
|
||||
for name, success := range cases {
|
||||
v, err := r.Create(name, nil)
|
||||
if success {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.Name() != name {
|
||||
t.Fatalf("Expected volume with name %s, got %s", name, v.Name())
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error creating volume with name %s, got nil", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue