summaryrefslogtreecommitdiff
path: root/vendor/github.com/opencontainers/runtime-tools/validate/validate.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/opencontainers/runtime-tools/validate/validate.go')
-rw-r--r--vendor/github.com/opencontainers/runtime-tools/validate/validate.go1050
1 files changed, 1050 insertions, 0 deletions
diff --git a/vendor/github.com/opencontainers/runtime-tools/validate/validate.go b/vendor/github.com/opencontainers/runtime-tools/validate/validate.go
new file mode 100644
index 000000000..bbdb29c60
--- /dev/null
+++ b/vendor/github.com/opencontainers/runtime-tools/validate/validate.go
@@ -0,0 +1,1050 @@
+package validate
+
+import (
+ "bufio"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "runtime"
+ "strings"
+ "syscall"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/blang/semver"
+ "github.com/hashicorp/go-multierror"
+ rspec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/sirupsen/logrus"
+ "github.com/syndtr/gocapability/capability"
+
+ "github.com/opencontainers/runtime-tools/specerror"
+)
+
+const specConfig = "config.json"
+
+var (
+ defaultRlimits = []string{
+ "RLIMIT_AS",
+ "RLIMIT_CORE",
+ "RLIMIT_CPU",
+ "RLIMIT_DATA",
+ "RLIMIT_FSIZE",
+ "RLIMIT_LOCKS",
+ "RLIMIT_MEMLOCK",
+ "RLIMIT_MSGQUEUE",
+ "RLIMIT_NICE",
+ "RLIMIT_NOFILE",
+ "RLIMIT_NPROC",
+ "RLIMIT_RSS",
+ "RLIMIT_RTPRIO",
+ "RLIMIT_RTTIME",
+ "RLIMIT_SIGPENDING",
+ "RLIMIT_STACK",
+ }
+)
+
+// Validator represents a validator for runtime bundle
+type Validator struct {
+ spec *rspec.Spec
+ bundlePath string
+ HostSpecific bool
+ platform string
+}
+
+// NewValidator creates a Validator
+func NewValidator(spec *rspec.Spec, bundlePath string, hostSpecific bool, platform string) Validator {
+ if hostSpecific && platform != runtime.GOOS {
+ platform = runtime.GOOS
+ }
+ return Validator{
+ spec: spec,
+ bundlePath: bundlePath,
+ HostSpecific: hostSpecific,
+ platform: platform,
+ }
+}
+
+// NewValidatorFromPath creates a Validator with specified bundle path
+func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string) (Validator, error) {
+ if hostSpecific && platform != runtime.GOOS {
+ platform = runtime.GOOS
+ }
+ if bundlePath == "" {
+ return Validator{}, fmt.Errorf("bundle path shouldn't be empty")
+ }
+
+ if _, err := os.Stat(bundlePath); err != nil {
+ return Validator{}, err
+ }
+
+ configPath := filepath.Join(bundlePath, specConfig)
+ content, err := ioutil.ReadFile(configPath)
+ if err != nil {
+ return Validator{}, specerror.NewError(specerror.ConfigFileExistence, err, rspec.Version)
+ }
+ if !utf8.Valid(content) {
+ return Validator{}, fmt.Errorf("%q is not encoded in UTF-8", configPath)
+ }
+ var spec rspec.Spec
+ if err = json.Unmarshal(content, &spec); err != nil {
+ return Validator{}, err
+ }
+
+ return NewValidator(&spec, bundlePath, hostSpecific, platform), nil
+}
+
+// CheckAll checks all parts of runtime bundle
+func (v *Validator) CheckAll() (errs error) {
+ errs = multierror.Append(errs, v.CheckPlatform())
+ errs = multierror.Append(errs, v.CheckRoot())
+ errs = multierror.Append(errs, v.CheckMandatoryFields())
+ errs = multierror.Append(errs, v.CheckSemVer())
+ errs = multierror.Append(errs, v.CheckMounts())
+ errs = multierror.Append(errs, v.CheckProcess())
+ errs = multierror.Append(errs, v.CheckHooks())
+ errs = multierror.Append(errs, v.CheckLinux())
+
+ return
+}
+
+// CheckRoot checks status of v.spec.Root
+func (v *Validator) CheckRoot() (errs error) {
+ logrus.Debugf("check root")
+
+ if v.platform == "windows" && v.spec.Windows != nil && v.spec.Windows.HyperV != nil {
+ if v.spec.Root != nil {
+ errs = multierror.Append(errs,
+ specerror.NewError(specerror.RootOnHyperV, fmt.Errorf("for Hyper-V containers, Root must not be set"), rspec.Version))
+ return
+ }
+ return
+ } else if v.spec.Root == nil {
+ errs = multierror.Append(errs,
+ specerror.NewError(specerror.RootOnNonHyperV, fmt.Errorf("for non-Hyper-V containers, Root must be set"), rspec.Version))
+ return
+ }
+
+ absBundlePath, err := filepath.Abs(v.bundlePath)
+ if err != nil {
+ errs = multierror.Append(errs, fmt.Errorf("unable to convert %q to an absolute path", v.bundlePath))
+ return
+ }
+
+ if filepath.Base(v.spec.Root.Path) != "rootfs" {
+ errs = multierror.Append(errs,
+ specerror.NewError(specerror.PathName, fmt.Errorf("path name should be the conventional 'rootfs'"), rspec.Version))
+ }
+
+ var rootfsPath string
+ var absRootPath string
+ if filepath.IsAbs(v.spec.Root.Path) {
+ rootfsPath = v.spec.Root.Path
+ absRootPath = filepath.Clean(rootfsPath)
+ } else {
+ var err error
+ rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
+ absRootPath, err = filepath.Abs(rootfsPath)
+ if err != nil {
+ errs = multierror.Append(errs, fmt.Errorf("unable to convert %q to an absolute path", rootfsPath))
+ return
+ }
+ }
+
+ if fi, err := os.Stat(rootfsPath); err != nil {
+ errs = multierror.Append(errs,
+ specerror.NewError(specerror.PathExistence, fmt.Errorf("cannot find the root path %q", rootfsPath), rspec.Version))
+ } else if !fi.IsDir() {
+ errs = multierror.Append(errs,
+ specerror.NewError(specerror.PathExistence, fmt.Errorf("root.path %q is not a directory", rootfsPath), rspec.Version))
+ }
+
+ rootParent := filepath.Dir(absRootPath)
+ if absRootPath == string(filepath.Separator) || rootParent != absBundlePath {
+ errs = multierror.Append(errs,
+ specerror.NewError(specerror.ArtifactsInSingleDir, fmt.Errorf("root.path is %q, but it MUST be a child of %q", v.spec.Root.Path, absBundlePath), rspec.Version))
+ }
+
+ if v.platform == "windows" {
+ if v.spec.Root.Readonly {
+ errs = multierror.Append(errs,
+ specerror.NewError(specerror.ReadonlyOnWindows, fmt.Errorf("root.readonly field MUST be omitted or false when target platform is windows"), rspec.Version))
+ }
+ }
+
+ return
+}
+
+// CheckSemVer checks v.spec.Version
+func (v *Validator) CheckSemVer() (errs error) {
+ logrus.Debugf("check semver")
+
+ version := v.spec.Version
+ _, err := semver.Parse(version)
+ if err != nil {
+ errs = multierror.Append(errs,
+ specerror.NewError(specerror.SpecVersion, fmt.Errorf("%q is not valid SemVer: %s", version, err.Error()), rspec.Version))
+ }
+ if version != rspec.Version {
+ errs = multierror.Append(errs, fmt.Errorf("validate currently only handles version %s, but the supplied configuration targets %s", rspec.Version, version))
+ }
+
+ return
+}
+
+// CheckHooks check v.spec.Hooks
+func (v *Validator) CheckHooks() (errs error) {
+ logrus.Debugf("check hooks")
+
+ if v.spec.Hooks != nil {
+ errs = multierror.Append(errs, checkEventHooks("pre-start", v.spec.Hooks.Prestart, v.HostSpecific))
+ errs = multierror.Append(errs, checkEventHooks("post-start", v.spec.Hooks.Poststart, v.HostSpecific))
+ errs = multierror.Append(errs, checkEventHooks("post-stop", v.spec.Hooks.Poststop, v.HostSpecific))
+ }
+
+ return
+}
+
+func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (errs error) {
+ for _, hook := range hooks {
+ if !filepath.IsAbs(hook.Path) {
+ errs = multierror.Append(errs, fmt.Errorf("the %s hook %v: is not absolute path", hookType, hook.Path))
+ }
+
+ if hostSpecific {
+ fi, err := os.Stat(hook.Path)
+ if err != nil {
+ errs = multierror.Append(errs, fmt.Errorf("cannot find %s hook: %v", hookType, hook.Path))
+ }
+ if fi.Mode()&0111 == 0 {
+ errs = multierror.Append(errs, fmt.Errorf("the %s hook %v: is not executable", hookType, hook.Path))
+ }
+ }
+
+ for _, env := range hook.Env {
+ if !envValid(env) {
+ errs = multierror.Append(errs, fmt.Errorf("env %q for hook %v is in the invalid form", env, hook.Path))
+ }
+ }
+ }
+
+ return
+}
+
+// CheckProcess checks v.spec.Process
+func (v *Validator) CheckProcess() (errs error) {
+ logrus.Debugf("check process")
+
+ if v.spec.Process == nil {
+ return
+ }
+
+ process := v.spec.Process
+ if !filepath.IsAbs(process.Cwd) {
+ errs = multierror.Append(errs, fmt.Errorf("cwd %q is not an absolute path", process.Cwd))
+ }
+
+ for _, env := range process.Env {
+ if !envValid(env) {
+ errs = multierror.Append(errs, fmt.Errorf("env %q should be in the form of 'key=value'. The left hand side must consist solely of letters, digits, and underscores '_'", env))
+ }
+ }
+
+ if len(process.Args) == 0 {
+ errs = multierror.Append(errs, fmt.Errorf("args must not be empty"))
+ } else {
+ if filepath.IsAbs(process.Args[0]) {
+ var rootfsPath string
+ if filepath.IsAbs(v.spec.Root.Path) {
+ rootfsPath = v.spec.Root.Path
+ } else {
+ rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
+ }
+ absPath := filepath.Join(rootfsPath, process.Args[0])
+ fileinfo, err := os.Stat(absPath)
+ if os.IsNotExist(err) {
+ logrus.Warnf("executable %q is not available in rootfs currently", process.Args[0])
+ } else if err != nil {
+ errs = multierror.Append(errs, err)
+ } else {
+ m := fileinfo.Mode()
+ if m.IsDir() || m&0111 == 0 {
+ errs = multierror.Append(errs, fmt.Errorf("arg %q is not executable", process.Args[0]))
+ }
+ }
+ }
+ }
+
+ if v.spec.Process.Capabilities != nil {
+ errs = multierror.Append(errs, v.CheckCapabilities())
+ }
+ errs = multierror.Append(errs, v.CheckRlimits())
+
+ if v.platform == "linux" {
+ if len(process.ApparmorProfile) > 0 {
+ profilePath := filepath.Join(v.bundlePath, v.spec.Root.Path, "/etc/apparmor.d", process.ApparmorProfile)
+ _, err := os.Stat(profilePath)
+ if err != nil {
+ errs = multierror.Append(errs, err)
+ }
+ }
+ }
+
+ return
+}
+
+// CheckCapabilities checks v.spec.Process.Capabilities
+func (v *Validator) CheckCapabilities() (errs error) {
+ process := v.spec.Process
+ if v.platform == "linux" {
+ var effective, permitted, inheritable, ambient bool
+ caps := make(map[string][]string)
+
+ for _, cap := range process.Capabilities.Bounding {
+ caps[cap] = append(caps[cap], "bounding")
+ }
+ for _, cap := range process.Capabilities.Effective {
+ caps[cap] = append(caps[cap], "effective")
+ }
+ for _, cap := range process.Capabilities.Inheritable {
+ caps[cap] = append(caps[cap], "inheritable")
+ }
+ for _, cap := range process.Capabilities.Permitted {
+ caps[cap] = append(caps[cap], "permitted")
+ }
+ for _, cap := range process.Capabilities.Ambient {
+ caps[cap] = append(caps[cap], "ambient")
+ }
+
+ for capability, owns := range caps {
+ if err := CapValid(capability, v.HostSpecific); err != nil {
+ errs = multierror.Append(errs, fmt.Errorf("capability %q is not valid, man capabilities(7)", capability))
+ }
+
+ effective, permitted, ambient, inheritable = false, false, false, false
+ for _, set := range owns {
+ if set == "effective" {
+ effective = true
+ continue
+ }
+ if set == "inheritable" {
+ inheritable = true
+ continue
+ }
+ if set == "permitted" {
+ permitted = true
+ continue
+ }
+ if set == "ambient" {
+ ambient = true
+ continue
+ }
+ }
+ if effective && !permitted {
+ errs = multierror.Append(errs, fmt.Errorf("effective capability %q is not allowed, as it's not permitted", capability))
+ }
+ if ambient && !(effective && inheritable) {
+ errs = multierror.Append(errs, fmt.Errorf("ambient capability %q is not allowed, as it's not permitted and inheribate", capability))
+ }
+ }
+ } else {
+ logrus.Warnf("process.capabilities validation not yet implemented for OS %q", v.platform)
+ }
+
+ return
+}
+
+// CheckRlimits checks v.spec.Process.Rlimits
+func (v *Validator) CheckRlimits() (errs error) {
+ process := v.spec.Process
+ for index, rlimit := range process.Rlimits {
+ for i := index + 1; i < len(process.Rlimits); i++ {
+ if process.Rlimits[index].Type == process.Rlimits[i].Type {
+ errs = multierror.Append(errs, fmt.Errorf("rlimit can not contain the same type %q", process.Rlimits[index].Type))
+ }
+ }
+ errs = multierror.Append(errs, v.rlimitValid(rlimit))
+ }
+
+ return
+}
+
+func supportedMountTypes(OS string, hostSpecific bool) (map[string]bool, error) {
+ supportedTypes := make(map[string]bool)
+
+ if OS != "linux" && OS != "windows" {
+ logrus.Warnf("%v is not supported to check mount type", OS)
+ return nil, nil
+ } else if OS == "windows" {
+ supportedTypes["ntfs"] = true
+ return supportedTypes, nil
+ }
+
+ if hostSpecific {
+ f, err := os.Open("/proc/filesystems")
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ if err := s.Err(); err != nil {
+ return supportedTypes, err
+ }
+
+ text := s.Text()
+ parts := strings.Split(text, "\t")
+ if len(parts) > 1 {
+ supportedTypes[parts[1]] = true
+ } else {
+ supportedTypes[parts[0]] = true
+ }
+ }
+
+ supportedTypes["bind"] = true
+
+ return supportedTypes, nil
+ }
+ logrus.Warn("Checking linux mount types without --host-specific is not supported yet")
+ return nil, nil
+}
+
+// CheckMounts checks v.spec.Mounts
+func (v *Validator) CheckMounts() (errs error) {
+ logrus.Debugf("check mounts")
+
+ supportedTypes, err := supportedMountTypes(v.platform, v.HostSpecific)
+ if err != nil {
+ errs = multierror.Append(errs, err)
+ return
+ }
+
+ for i, mountA := range v.spec.Mounts {
+ if supportedTypes != nil && !supportedTypes[mountA.Type] {
+ errs = multierror.Append(errs, fmt.Errorf("unsupported mount type %q", mountA.Type))
+ }
+ if v.platform == "windows" {
+ if err := pathValid(v.platform, mountA.Destination); err != nil {
+ errs = multierror.Append(errs, err)
+ }
+ if err := pathValid(v.platform, mountA.Source); err != nil {
+ errs = multierror.Append(errs, err)
+ }
+ } else {
+ if err := pathValid(v.platform, mountA.Destination); err != nil {
+ errs = multierror.Append(errs, err)
+ }
+ }
+ for j, mountB := range v.spec.Mounts {
+ if i == j {
+ continue
+ }
+ // whether B.Desination is nested within A.Destination
+ nested, err := nestedValid(v.platform, mountA.Destination, mountB.Destination)
+ if err != nil {
+ errs = multierror.Append(errs, err)
+ continue
+ }
+ if nested {
+ if v.platform == "windows" && i < j {
+ errs = multierror.Append(errs, fmt.Errorf("on Windows, %v nested within %v is forbidden", mountB.Destination, mountA.Destination))
+ }
+ if i > j {
+ logrus.Warnf("%v will be covered by %v", mountB.Destination, mountA.Destination)
+ }
+ }
+ }
+ }
+
+ return
+}
+
+// CheckPlatform checks v.platform
+func (v *Validator) CheckPlatform() (errs error) {
+ logrus.Debugf("check platform")
+
+ if v.platform != "linux" && v.platform != "solaris" && v.platform != "windows" {
+ errs = multierror.Append(errs, fmt.Errorf("platform %q is not supported", v.platform))
+ return
+ }
+
+ if v.platform == "windows" {
+ if v.spec.Windows == nil {
+ errs = multierror.Append(errs, errors.New("'windows' MUST be set when platform is `windows`"))
+ }
+ }
+
+ return
+}
+
+// CheckLinux checks v.spec.Linux
+func (v *Validator) CheckLinux() (errs error) {
+ logrus.Debugf("check linux")
+
+ if v.spec.Linux == nil {
+ return
+ }
+
+ var nsTypeList = map[rspec.LinuxNamespaceType]struct {
+ num int
+ newExist bool
+ }{
+ rspec.PIDNamespace: {0, false},
+ rspec.NetworkNamespace: {0, false},
+ rspec.MountNamespace: {0, false},
+ rspec.IPCNamespace: {0, false},
+ rspec.UTSNamespace: {0, false},
+ rspec.UserNamespace: {0, false},
+ rspec.CgroupNamespace: {0, false},
+ }
+
+ for index := 0; index < len(v.spec.Linux.Namespaces); index++ {
+ ns := v.spec.Linux.Namespaces[index]
+ if !namespaceValid(ns) {
+ errs = multierror.Append(errs, fmt.Errorf("namespace %v is invalid", ns))
+ }
+
+ tmpItem := nsTypeList[ns.Type]
+ tmpItem.num = tmpItem.num + 1
+ if tmpItem.num > 1 {
+ errs = multierror.Append(errs, fmt.Errorf("duplicated namespace %q", ns.Type))
+ }
+
+ if len(ns.Path) == 0 {
+ tmpItem.newExist = true
+ }
+ nsTypeList[ns.Type] = tmpItem
+ }
+
+ if (len(v.spec.Linux.UIDMappings) > 0 || len(v.spec.Linux.GIDMappings) > 0) && !nsTypeList[rspec.UserNamespace].newExist {
+ errs = multierror.Append(errs, errors.New("the UID/GID mappings requires a new User namespace to be specified as well"))
+ } else if len(v.spec.Linux.UIDMappings) > 5 {
+ errs = multierror.Append(errs, errors.New("only 5 UID mappings are allowed (linux kernel restriction)"))
+ } else if len(v.spec.Linux.GIDMappings) > 5 {
+ errs = multierror.Append(errs, errors.New("only 5 GID mappings are allowed (linux kernel restriction)"))
+ }
+
+ for k := range v.spec.Linux.Sysctl {
+ if strings.HasPrefix(k, "net.") && !nsTypeList[rspec.NetworkNamespace].newExist {
+ errs = multierror.Append(errs, fmt.Errorf("sysctl %v requires a new Network namespace to be specified as well", k))
+ }
+ if strings.HasPrefix(k, "fs.mqueue.") {
+ if !nsTypeList[rspec.MountNamespace].newExist || !nsTypeList[rspec.IPCNamespace].newExist {
+ errs = multierror.Append(errs, fmt.Errorf("sysctl %v requires a new IPC namespace and Mount namespace to be specified as well", k))
+ }
+ }
+ }
+
+ if v.platform == "linux" && !nsTypeList[rspec.UTSNamespace].newExist && v.spec.Hostname != "" {
+ errs = multierror.Append(errs, fmt.Errorf("on Linux, hostname requires a new UTS namespace to be specified as well"))
+ }
+
+ // Linux devices validation
+ devList := make(map[string]bool)
+ devTypeList := make(map[string]bool)
+ for index := 0; index < len(v.spec.Linux.Devices); index++ {
+ device := v.spec.Linux.Devices[index]
+ if !deviceValid(device) {
+ errs = multierror.Append(errs, fmt.Errorf("device %v is invalid", device))
+ }
+
+ if _, exists := devList[device.Path]; exists {
+ errs = multierror.Append(errs, fmt.Errorf("device %s is duplicated", device.Path))
+ } else {
+ var rootfsPath string
+ if filepath.IsAbs(v.spec.Root.Path) {
+ rootfsPath = v.spec.Root.Path
+ } else {
+ rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
+ }
+ absPath := filepath.Join(rootfsPath, device.Path)
+ fi, err := os.Stat(absPath)
+ if os.IsNotExist(err) {
+ devList[device.Path] = true
+ } else if err != nil {
+ errs = multierror.Append(errs, err)
+ } else {
+ fStat, ok := fi.Sys().(*syscall.Stat_t)
+ if !ok {
+ errs = multierror.Append(errs, fmt.Errorf("cannot determine state for device %s", device.Path))
+ continue
+ }
+ var devType string
+ switch fStat.Mode & syscall.S_IFMT {
+ case syscall.S_IFCHR:
+ devType = "c"
+ case syscall.S_IFBLK:
+ devType = "b"
+ case syscall.S_IFIFO:
+ devType = "p"
+ default:
+ devType = "unmatched"
+ }
+ if devType != device.Type || (devType == "c" && device.Type == "u") {
+ errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
+ continue
+ }
+ if devType != "p" {
+ dev := fStat.Rdev
+ major := (dev >> 8) & 0xfff
+ minor := (dev & 0xff) | ((dev >> 12) & 0xfff00)
+ if int64(major) != device.Major || int64(minor) != device.Minor {
+ errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
+ continue
+ }
+ }
+ if device.FileMode != nil {
+ expectedPerm := *device.FileMode & os.ModePerm
+ actualPerm := fi.Mode() & os.ModePerm
+ if expectedPerm != actualPerm {
+ errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
+ continue
+ }
+ }
+ if device.UID != nil {
+ if *device.UID != fStat.Uid {
+ errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
+ continue
+ }
+ }
+ if device.GID != nil {
+ if *device.GID != fStat.Gid {
+ errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
+ continue
+ }
+ }
+ }
+ }
+
+ // unify u->c when comparing, they are synonyms
+ var devID string
+ if device.Type == "u" {
+ devID = fmt.Sprintf("%s:%d:%d", "c", device.Major, device.Minor)
+ } else {
+ devID = fmt.Sprintf("%s:%d:%d", device.Type, device.Major, device.Minor)
+ }
+
+ if _, exists := devTypeList[devID]; exists {
+ logrus.Warnf("type:%s, major:%d and minor:%d for linux devices is duplicated", device.Type, device.Major, device.Minor)
+ } else {
+ devTypeList[devID] = true
+ }
+ }
+
+ if v.spec.Linux.Resources != nil {
+ errs = multierror.Append(errs, v.CheckLinuxResources())
+ }
+
+ if v.spec.Linux.Seccomp != nil {
+ errs = multierror.Append(errs, v.CheckSeccomp())
+ }
+
+ switch v.spec.Linux.RootfsPropagation {
+ case "":
+ case "private":
+ case "rprivate":
+ case "slave":
+ case "rslave":
+ case "shared":
+ case "rshared":
+ case "unbindable":
+ case "runbindable":
+ default:
+ errs = multierror.Append(errs, errors.New("rootfsPropagation must be empty or one of \"private|rprivate|slave|rslave|shared|rshared|unbindable|runbindable\""))
+ }
+
+ for _, maskedPath := range v.spec.Linux.MaskedPaths {
+ if !strings.HasPrefix(maskedPath, "/") {
+ errs = multierror.Append(errs, fmt.Errorf("maskedPath %v is not an absolute path", maskedPath))
+ }
+ }
+
+ for _, readonlyPath := range v.spec.Linux.ReadonlyPaths {
+ if !strings.HasPrefix(readonlyPath, "/") {
+ errs = multierror.Append(errs, fmt.Errorf("readonlyPath %v is not an absolute path", readonlyPath))
+ }
+ }
+
+ return
+}
+
+// CheckLinuxResources checks v.spec.Linux.Resources
+func (v *Validator) CheckLinuxResources() (errs error) {
+ logrus.Debugf("check linux resources")
+
+ r := v.spec.Linux.Resources
+ if r.Memory != nil {
+ if r.Memory.Limit != nil && r.Memory.Swap != nil && uint64(*r.Memory.Limit) > uint64(*r.Memory.Swap) {
+ errs = multierror.Append(errs, fmt.Errorf("minimum memoryswap should be larger than memory limit"))
+ }
+ if r.Memory.Limit != nil && r.Memory.Reservation != nil && uint64(*r.Memory.Reservation) > uint64(*r.Memory.Limit) {
+ errs = multierror.Append(errs, fmt.Errorf("minimum memory limit should be larger than memory reservation"))
+ }
+ }
+ if r.Network != nil && v.HostSpecific {
+ var exist bool
+ interfaces, err := net.Interfaces()
+ if err != nil {
+ errs = multierror.Append(errs, err)
+ return
+ }
+ for _, prio := range r.Network.Priorities {
+ exist = false
+ for _, ni := range interfaces {
+ if prio.Name == ni.Name {
+ exist = true
+ break
+ }
+ }
+ if !exist {
+ errs = multierror.Append(errs, fmt.Errorf("interface %s does not exist currently", prio.Name))
+ }
+ }
+ }
+ for index := 0; index < len(r.Devices); index++ {
+ switch r.Devices[index].Type {
+ case "a", "b", "c":
+ default:
+ errs = multierror.Append(errs, fmt.Errorf("type of devices %s is invalid", r.Devices[index].Type))
+ }
+
+ access := []byte(r.Devices[index].Access)
+ for i := 0; i < len(access); i++ {
+ switch access[i] {
+ case 'r', 'w', 'm':
+ default:
+ errs = multierror.Append(errs, fmt.Errorf("access %s is invalid", r.Devices[index].Access))
+ return
+ }
+ }
+ }
+
+ return
+}
+
+// CheckSeccomp checkc v.spec.Linux.Seccomp
+func (v *Validator) CheckSeccomp() (errs error) {
+ logrus.Debugf("check linux seccomp")
+
+ s := v.spec.Linux.Seccomp
+ if !seccompActionValid(s.DefaultAction) {
+ errs = multierror.Append(errs, fmt.Errorf("seccomp defaultAction %q is invalid", s.DefaultAction))
+ }
+ for index := 0; index < len(s.Syscalls); index++ {
+ if !syscallValid(s.Syscalls[index]) {
+ errs = multierror.Append(errs, fmt.Errorf("syscall %v is invalid", s.Syscalls[index]))
+ }
+ }
+ for index := 0; index < len(s.Architectures); index++ {
+ switch s.Architectures[index] {
+ case rspec.ArchX86:
+ case rspec.ArchX86_64:
+ case rspec.ArchX32:
+ case rspec.ArchARM:
+ case rspec.ArchAARCH64:
+ case rspec.ArchMIPS:
+ case rspec.ArchMIPS64:
+ case rspec.ArchMIPS64N32:
+ case rspec.ArchMIPSEL:
+ case rspec.ArchMIPSEL64:
+ case rspec.ArchMIPSEL64N32:
+ case rspec.ArchPPC:
+ case rspec.ArchPPC64:
+ case rspec.ArchPPC64LE:
+ case rspec.ArchS390:
+ case rspec.ArchS390X:
+ case rspec.ArchPARISC:
+ case rspec.ArchPARISC64:
+ default:
+ errs = multierror.Append(errs, fmt.Errorf("seccomp architecture %q is invalid", s.Architectures[index]))
+ }
+ }
+
+ return
+}
+
+// CapValid checks whether a capability is valid
+func CapValid(c string, hostSpecific bool) error {
+ isValid := false
+
+ if !strings.HasPrefix(c, "CAP_") {
+ return fmt.Errorf("capability %s must start with CAP_", c)
+ }
+ for _, cap := range capability.List() {
+ if c == fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String())) {
+ if hostSpecific && cap > LastCap() {
+ return fmt.Errorf("%s is not supported on the current host", c)
+ }
+ isValid = true
+ break
+ }
+ }
+
+ if !isValid {
+ return fmt.Errorf("invalid capability: %s", c)
+ }
+ return nil
+}
+
+// LastCap return last cap of system
+func LastCap() capability.Cap {
+ last := capability.CAP_LAST_CAP
+ // hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
+ if last == capability.Cap(63) {
+ last = capability.CAP_BLOCK_SUSPEND
+ }
+
+ return last
+}
+
+func envValid(env string) bool {
+ items := strings.Split(env, "=")
+ if len(items) < 2 {
+ return false
+ }
+ for i, ch := range strings.TrimSpace(items[0]) {
+ if !unicode.IsDigit(ch) && !unicode.IsLetter(ch) && ch != '_' {
+ return false
+ }
+ if i == 0 && unicode.IsDigit(ch) {
+ logrus.Warnf("Env %v: variable name beginning with digit is not recommended.", env)
+ }
+ }
+ return true
+}
+
+func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (errs error) {
+ if rlimit.Hard < rlimit.Soft {
+ errs = multierror.Append(errs, fmt.Errorf("hard limit of rlimit %s should not be less than soft limit", rlimit.Type))
+ }
+
+ if v.platform == "linux" {
+ for _, val := range defaultRlimits {
+ if val == rlimit.Type {
+ return
+ }
+ }
+ errs = multierror.Append(errs, fmt.Errorf("rlimit type %q is invalid", rlimit.Type))
+ } else {
+ logrus.Warnf("process.rlimits validation not yet implemented for platform %q", v.platform)
+ }
+
+ return
+}
+
+func namespaceValid(ns rspec.LinuxNamespace) bool {
+ switch ns.Type {
+ case rspec.PIDNamespace:
+ case rspec.NetworkNamespace:
+ case rspec.MountNamespace:
+ case rspec.IPCNamespace:
+ case rspec.UTSNamespace:
+ case rspec.UserNamespace:
+ case rspec.CgroupNamespace:
+ default:
+ return false
+ }
+
+ if ns.Path != "" && !filepath.IsAbs(ns.Path) {
+ return false
+ }
+
+ return true
+}
+
+func pathValid(os, path string) error {
+ if os == "windows" {
+ matched, err := regexp.MatchString("^[a-zA-Z]:(\\\\[^\\\\/<>|:*?\"]+)+$", path)
+ if err != nil {
+ return err
+ }
+ if !matched {
+ return fmt.Errorf("invalid windows path %v", path)
+ }
+ return nil
+ }
+ if !filepath.IsAbs(path) {
+ return fmt.Errorf("%v is not an absolute path", path)
+ }
+ return nil
+}
+
+// Check whether pathB is nested whithin pathA
+func nestedValid(os, pathA, pathB string) (bool, error) {
+ if pathA == pathB {
+ return false, nil
+ }
+ if pathA == "/" && pathB != "" {
+ return true, nil
+ }
+
+ var sep string
+ if os == "windows" {
+ sep = "\\"
+ } else {
+ sep = "/"
+ }
+
+ splitedPathA := strings.Split(filepath.Clean(pathA), sep)
+ splitedPathB := strings.Split(filepath.Clean(pathB), sep)
+ lenA := len(splitedPathA)
+ lenB := len(splitedPathB)
+
+ if lenA > lenB {
+ if (lenA - lenB) == 1 {
+ // if pathA is longer but not end with separator
+ if splitedPathA[lenA-1] != "" {
+ return false, nil
+ }
+ splitedPathA = splitedPathA[:lenA-1]
+ } else {
+ return false, nil
+ }
+ }
+
+ for i, partA := range splitedPathA {
+ if partA != splitedPathB[i] {
+ return false, nil
+ }
+ }
+
+ return true, nil
+}
+
+func deviceValid(d rspec.LinuxDevice) bool {
+ switch d.Type {
+ case "b", "c", "u":
+ if d.Major <= 0 || d.Minor <= 0 {
+ return false
+ }
+ case "p":
+ if d.Major > 0 || d.Minor > 0 {
+ return false
+ }
+ default:
+ return false
+ }
+ return true
+}
+
+func seccompActionValid(secc rspec.LinuxSeccompAction) bool {
+ switch secc {
+ case "":
+ case rspec.ActKill:
+ case rspec.ActTrap:
+ case rspec.ActErrno:
+ case rspec.ActTrace:
+ case rspec.ActAllow:
+ default:
+ return false
+ }
+ return true
+}
+
+func syscallValid(s rspec.LinuxSyscall) bool {
+ if !seccompActionValid(s.Action) {
+ return false
+ }
+ for index := 0; index < len(s.Args); index++ {
+ arg := s.Args[index]
+ switch arg.Op {
+ case rspec.OpNotEqual:
+ case rspec.OpLessThan:
+ case rspec.OpLessEqual:
+ case rspec.OpEqualTo:
+ case rspec.OpGreaterEqual:
+ case rspec.OpGreaterThan:
+ case rspec.OpMaskedEqual:
+ default:
+ return false
+ }
+ }
+ return true
+}
+
+func isStruct(t reflect.Type) bool {
+ return t.Kind() == reflect.Struct
+}
+
+func isStructPtr(t reflect.Type) bool {
+ return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
+}
+
+func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, parent string) (errs error) {
+ mandatory := !strings.Contains(tagField.Tag.Get("json"), "omitempty")
+ switch field.Kind() {
+ case reflect.Ptr:
+ if mandatory && field.IsNil() {
+ errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name))
+ }
+ case reflect.String:
+ if mandatory && (field.Len() == 0) {
+ errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name))
+ }
+ case reflect.Slice:
+ if mandatory && (field.IsNil() || field.Len() == 0) {
+ errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name))
+ return
+ }
+ for index := 0; index < field.Len(); index++ {
+ mValue := field.Index(index)
+ if mValue.CanInterface() {
+ errs = multierror.Append(errs, checkMandatory(mValue.Interface()))
+ }
+ }
+ case reflect.Map:
+ if mandatory && (field.IsNil() || field.Len() == 0) {
+ errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name))
+ return
+ }
+ keys := field.MapKeys()
+ for index := 0; index < len(keys); index++ {
+ mValue := field.MapIndex(keys[index])
+ if mValue.CanInterface() {
+ errs = multierror.Append(errs, checkMandatory(mValue.Interface()))
+ }
+ }
+ default:
+ }
+
+ return
+}
+
+func checkMandatory(obj interface{}) (errs error) {
+ objT := reflect.TypeOf(obj)
+ objV := reflect.ValueOf(obj)
+ if isStructPtr(objT) {
+ objT = objT.Elem()
+ objV = objV.Elem()
+ } else if !isStruct(objT) {
+ return
+ }
+
+ for i := 0; i < objT.NumField(); i++ {
+ t := objT.Field(i).Type
+ if isStructPtr(t) && objV.Field(i).IsNil() {
+ if !strings.Contains(objT.Field(i).Tag.Get("json"), "omitempty") {
+ errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", objT.Name(), objT.Field(i).Name))
+ }
+ } else if (isStruct(t) || isStructPtr(t)) && objV.Field(i).CanInterface() {
+ errs = multierror.Append(errs, checkMandatory(objV.Field(i).Interface()))
+ } else {
+ errs = multierror.Append(errs, checkMandatoryUnit(objV.Field(i), objT.Field(i), objT.Name()))
+ }
+
+ }
+ return
+}
+
+// CheckMandatoryFields checks mandatory field of container's config file
+func (v *Validator) CheckMandatoryFields() error {
+ logrus.Debugf("check mandatory fields")
+
+ return checkMandatory(v.spec)
+}