Allow additional arguments to be passed into hooks
If a packager wants to be able to support addititional arguments on his hook this will allow them to setup the configuration with these arguments. For example this would allow a hook developer to add support for a --debug flag to change the level of debugging in his hook. In order to complete this task, I had to vendor in the latest github.com://opencontainers/runtime-tools, which caused me to have to fix a Mount and Capability interface calls Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
parent
41aaf4e3d8
commit
23d20c9db5
45 changed files with 7145 additions and 672 deletions
534
vendor/github.com/opencontainers/runtime-tools/validate/validate.go
generated
vendored
534
vendor/github.com/opencontainers/runtime-tools/validate/validate.go
generated
vendored
|
@ -20,33 +20,41 @@ import (
|
|||
"github.com/blang/semver"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
rspec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
osFilepath "github.com/opencontainers/runtime-tools/filepath"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
|
||||
"github.com/opencontainers/runtime-tools/specerror"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
const specConfig = "config.json"
|
||||
|
||||
var (
|
||||
defaultRlimits = []string{
|
||||
// http://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html
|
||||
posixRlimits = []string{
|
||||
"RLIMIT_AS",
|
||||
"RLIMIT_CORE",
|
||||
"RLIMIT_CPU",
|
||||
"RLIMIT_DATA",
|
||||
"RLIMIT_FSIZE",
|
||||
"RLIMIT_LOCKS",
|
||||
"RLIMIT_NOFILE",
|
||||
"RLIMIT_STACK",
|
||||
}
|
||||
|
||||
// https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/getrlimit.2?h=man-pages-4.13
|
||||
linuxRlimits = append(posixRlimits, []string{
|
||||
"RLIMIT_MEMLOCK",
|
||||
"RLIMIT_MSGQUEUE",
|
||||
"RLIMIT_NICE",
|
||||
"RLIMIT_NOFILE",
|
||||
"RLIMIT_NPROC",
|
||||
"RLIMIT_RSS",
|
||||
"RLIMIT_RTPRIO",
|
||||
"RLIMIT_RTTIME",
|
||||
"RLIMIT_SIGPENDING",
|
||||
"RLIMIT_STACK",
|
||||
}
|
||||
}...)
|
||||
|
||||
configSchemaTemplate = "https://raw.githubusercontent.com/opencontainers/runtime-spec/v%s/schema/config-schema.json"
|
||||
)
|
||||
|
||||
// Validator represents a validator for runtime bundle
|
||||
|
@ -58,23 +66,20 @@ type Validator struct {
|
|||
}
|
||||
|
||||
// NewValidator creates a Validator
|
||||
func NewValidator(spec *rspec.Spec, bundlePath string, hostSpecific bool, platform string) Validator {
|
||||
func NewValidator(spec *rspec.Spec, bundlePath string, hostSpecific bool, platform string) (Validator, error) {
|
||||
if hostSpecific && platform != runtime.GOOS {
|
||||
platform = runtime.GOOS
|
||||
return Validator{}, fmt.Errorf("When hostSpecific is set, platform must be same as the host platform")
|
||||
}
|
||||
return Validator{
|
||||
spec: spec,
|
||||
bundlePath: bundlePath,
|
||||
HostSpecific: hostSpecific,
|
||||
platform: platform,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
@ -86,7 +91,7 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string)
|
|||
configPath := filepath.Join(bundlePath, specConfig)
|
||||
content, err := ioutil.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return Validator{}, specerror.NewError(specerror.ConfigFileExistence, err, rspec.Version)
|
||||
return Validator{}, specerror.NewError(specerror.ConfigInRootBundleDir, err, rspec.Version)
|
||||
}
|
||||
if !utf8.Valid(content) {
|
||||
return Validator{}, fmt.Errorf("%q is not encoded in UTF-8", configPath)
|
||||
|
@ -96,21 +101,68 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string)
|
|||
return Validator{}, err
|
||||
}
|
||||
|
||||
return NewValidator(&spec, bundlePath, hostSpecific, platform), nil
|
||||
return NewValidator(&spec, bundlePath, hostSpecific, platform)
|
||||
}
|
||||
|
||||
// CheckAll checks all parts of runtime bundle
|
||||
func (v *Validator) CheckAll() (errs error) {
|
||||
func (v *Validator) CheckAll() error {
|
||||
var errs *multierror.Error
|
||||
errs = multierror.Append(errs, v.CheckJSONSchema())
|
||||
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())
|
||||
if v.platform == "linux" || v.platform == "solaris" {
|
||||
errs = multierror.Append(errs, v.CheckHooks())
|
||||
}
|
||||
|
||||
return
|
||||
return errs.ErrorOrNil()
|
||||
}
|
||||
|
||||
// JSONSchemaURL returns the URL for the JSON Schema specifying the
|
||||
// configuration format. It consumes configSchemaTemplate, but we
|
||||
// provide it as a function to isolate consumers from inconsistent
|
||||
// naming as runtime-spec evolves.
|
||||
func JSONSchemaURL(version string) (url string, err error) {
|
||||
ver, err := semver.Parse(version)
|
||||
if err != nil {
|
||||
return "", specerror.NewError(specerror.SpecVersionInSemVer, err, rspec.Version)
|
||||
}
|
||||
configRenamedToConfigSchemaVersion, err := semver.Parse("1.0.0-rc2") // config.json became config-schema.json in 1.0.0-rc2
|
||||
if ver.Compare(configRenamedToConfigSchemaVersion) == -1 {
|
||||
return "", fmt.Errorf("unsupported configuration version (older than %s)", configRenamedToConfigSchemaVersion)
|
||||
}
|
||||
return fmt.Sprintf(configSchemaTemplate, version), nil
|
||||
}
|
||||
|
||||
// CheckJSONSchema validates the configuration against the
|
||||
// runtime-spec JSON Schema, using the version of the schema that
|
||||
// matches the configuration's declared version.
|
||||
func (v *Validator) CheckJSONSchema() (errs error) {
|
||||
url, err := JSONSchemaURL(v.spec.Version)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
return errs
|
||||
}
|
||||
|
||||
schemaLoader := gojsonschema.NewReferenceLoader(url)
|
||||
documentLoader := gojsonschema.NewGoLoader(v.spec)
|
||||
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
return errs
|
||||
}
|
||||
|
||||
if !result.Valid() {
|
||||
for _, resultError := range result.Errors() {
|
||||
errs = multierror.Append(errs, errors.New(resultError.String()))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// CheckRoot checks status of v.spec.Root
|
||||
|
@ -120,13 +172,30 @@ func (v *Validator) CheckRoot() (errs error) {
|
|||
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))
|
||||
specerror.NewError(specerror.RootOnHyperVNotSet, 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))
|
||||
specerror.NewError(specerror.RootOnNonHyperVRequired, fmt.Errorf("for non-Hyper-V containers, Root must be set"), rspec.Version))
|
||||
return
|
||||
}
|
||||
|
||||
if v.platform == "windows" {
|
||||
matched, err := regexp.MatchString(`\\\\[?]\\Volume[{][a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}[}]\\`, v.spec.Root.Path)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
} else if !matched {
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(specerror.RootPathOnWindowsGUID, fmt.Errorf("root.path is %q, but it MUST be a volume GUID path when target platform is windows", v.spec.Root.Path), rspec.Version))
|
||||
}
|
||||
|
||||
if v.spec.Root.Readonly {
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(specerror.RootReadonlyOnWindowsFalse, fmt.Errorf("root.readonly field MUST be omitted or false when target platform is windows"), rspec.Version))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -138,7 +207,7 @@ func (v *Validator) CheckRoot() (errs error) {
|
|||
|
||||
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))
|
||||
specerror.NewError(specerror.RootPathOnPosixConvention, fmt.Errorf("path name should be the conventional 'rootfs'"), rspec.Version))
|
||||
}
|
||||
|
||||
var rootfsPath string
|
||||
|
@ -158,10 +227,10 @@ func (v *Validator) CheckRoot() (errs error) {
|
|||
|
||||
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))
|
||||
specerror.NewError(specerror.RootPathExist, 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))
|
||||
specerror.NewError(specerror.RootPathExist, fmt.Errorf("root.path %q is not a directory", rootfsPath), rspec.Version))
|
||||
}
|
||||
|
||||
rootParent := filepath.Dir(absRootPath)
|
||||
|
@ -170,13 +239,6 @@ func (v *Validator) CheckRoot() (errs error) {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -188,7 +250,7 @@ func (v *Validator) CheckSemVer() (errs error) {
|
|||
_, 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))
|
||||
specerror.NewError(specerror.SpecVersionInSemVer, 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))
|
||||
|
@ -201,19 +263,29 @@ func (v *Validator) CheckSemVer() (errs error) {
|
|||
func (v *Validator) CheckHooks() (errs error) {
|
||||
logrus.Debugf("check hooks")
|
||||
|
||||
if v.platform != "linux" && v.platform != "solaris" {
|
||||
errs = multierror.Append(errs, fmt.Errorf("For %q platform, the configuration structure does not support hooks", v.platform))
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
errs = multierror.Append(errs, v.checkEventHooks("prestart", v.spec.Hooks.Prestart, v.HostSpecific))
|
||||
errs = multierror.Append(errs, v.checkEventHooks("poststart", v.spec.Hooks.Poststart, v.HostSpecific))
|
||||
errs = multierror.Append(errs, v.checkEventHooks("poststop", 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))
|
||||
func (v *Validator) checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (errs error) {
|
||||
for i, hook := range hooks {
|
||||
if !osFilepath.IsAbs(v.platform, hook.Path) {
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(
|
||||
specerror.PosixHooksPathAbs,
|
||||
fmt.Errorf("hooks.%s[%d].path %v: is not absolute path",
|
||||
hookType, i, hook.Path),
|
||||
rspec.Version))
|
||||
}
|
||||
|
||||
if hostSpecific {
|
||||
|
@ -245,8 +317,12 @@ func (v *Validator) CheckProcess() (errs error) {
|
|||
}
|
||||
|
||||
process := v.spec.Process
|
||||
if !filepath.IsAbs(process.Cwd) {
|
||||
errs = multierror.Append(errs, fmt.Errorf("cwd %q is not an absolute path", process.Cwd))
|
||||
if !osFilepath.IsAbs(v.platform, process.Cwd) {
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(
|
||||
specerror.ProcCwdAbs,
|
||||
fmt.Errorf("cwd %q is not an absolute path", process.Cwd),
|
||||
rspec.Version))
|
||||
}
|
||||
|
||||
for _, env := range process.Env {
|
||||
|
@ -256,9 +332,13 @@ func (v *Validator) CheckProcess() (errs error) {
|
|||
}
|
||||
|
||||
if len(process.Args) == 0 {
|
||||
errs = multierror.Append(errs, fmt.Errorf("args must not be empty"))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(
|
||||
specerror.ProcArgsOneEntryRequired,
|
||||
fmt.Errorf("args must not be empty"),
|
||||
rspec.Version))
|
||||
} else {
|
||||
if filepath.IsAbs(process.Args[0]) {
|
||||
if filepath.IsAbs(process.Args[0]) && v.spec.Root != nil {
|
||||
var rootfsPath string
|
||||
if filepath.IsAbs(v.spec.Root.Path) {
|
||||
rootfsPath = v.spec.Root.Path
|
||||
|
@ -280,12 +360,15 @@ func (v *Validator) CheckProcess() (errs error) {
|
|||
}
|
||||
}
|
||||
|
||||
if v.spec.Process.Capabilities != nil {
|
||||
errs = multierror.Append(errs, v.CheckCapabilities())
|
||||
if v.platform == "linux" || v.platform == "solaris" {
|
||||
errs = multierror.Append(errs, v.CheckRlimits())
|
||||
}
|
||||
errs = multierror.Append(errs, v.CheckRlimits())
|
||||
|
||||
if v.platform == "linux" {
|
||||
if v.spec.Process.Capabilities != nil {
|
||||
errs = multierror.Append(errs, v.CheckCapabilities())
|
||||
}
|
||||
|
||||
if len(process.ApparmorProfile) > 0 {
|
||||
profilePath := filepath.Join(v.bundlePath, v.spec.Root.Path, "/etc/apparmor.d", process.ApparmorProfile)
|
||||
_, err := os.Stat(profilePath)
|
||||
|
@ -300,60 +383,61 @@ func (v *Validator) CheckProcess() (errs error) {
|
|||
|
||||
// CheckCapabilities checks v.spec.Process.Capabilities
|
||||
func (v *Validator) CheckCapabilities() (errs error) {
|
||||
if v.platform != "linux" {
|
||||
errs = multierror.Append(errs, fmt.Errorf("For %q platform, the configuration structure does not support process.capabilities", v.platform))
|
||||
return
|
||||
}
|
||||
|
||||
process := v.spec.Process
|
||||
if v.platform == "linux" {
|
||||
var effective, permitted, inheritable, ambient bool
|
||||
caps := make(map[string][]string)
|
||||
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 _, 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))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 set == "inheritable" {
|
||||
inheritable = true
|
||||
continue
|
||||
}
|
||||
if effective && !permitted {
|
||||
errs = multierror.Append(errs, fmt.Errorf("effective capability %q is not allowed, as it's not permitted", capability))
|
||||
if set == "permitted" {
|
||||
permitted = true
|
||||
continue
|
||||
}
|
||||
if ambient && !(effective && inheritable) {
|
||||
errs = multierror.Append(errs, fmt.Errorf("ambient capability %q is not allowed, as it's not permitted and inheribate", capability))
|
||||
if set == "ambient" {
|
||||
ambient = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Warnf("process.capabilities validation not yet implemented for OS %q", v.platform)
|
||||
if effective && !permitted {
|
||||
errs = multierror.Append(errs, fmt.Errorf("effective capability %q is not allowed, as it's not permitted", capability))
|
||||
}
|
||||
if ambient && !(permitted && inheritable) {
|
||||
errs = multierror.Append(errs, fmt.Errorf("ambient capability %q is not allowed, as it's not permitted and inheribate", capability))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -361,11 +445,21 @@ func (v *Validator) CheckCapabilities() (errs error) {
|
|||
|
||||
// CheckRlimits checks v.spec.Process.Rlimits
|
||||
func (v *Validator) CheckRlimits() (errs error) {
|
||||
if v.platform != "linux" && v.platform != "solaris" {
|
||||
errs = multierror.Append(errs, fmt.Errorf("For %q platform, the configuration structure does not support process.rlimits", v.platform))
|
||||
return
|
||||
}
|
||||
|
||||
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,
|
||||
specerror.NewError(
|
||||
specerror.PosixProcRlimitsErrorOnDup,
|
||||
fmt.Errorf("rlimit can not contain the same type %q",
|
||||
process.Rlimits[index].Type),
|
||||
rspec.Version))
|
||||
}
|
||||
}
|
||||
errs = multierror.Append(errs, v.rlimitValid(rlimit))
|
||||
|
@ -429,31 +523,33 @@ func (v *Validator) CheckMounts() (errs error) {
|
|||
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)
|
||||
}
|
||||
if !osFilepath.IsAbs(v.platform, mountA.Destination) {
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(
|
||||
specerror.MountsDestAbs,
|
||||
fmt.Errorf("mounts[%d].destination %q is not absolute",
|
||||
i,
|
||||
mountA.Destination),
|
||||
rspec.Version))
|
||||
}
|
||||
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)
|
||||
nested, err := osFilepath.IsAncestor(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))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(
|
||||
specerror.MountsDestOnWindowsNotNested,
|
||||
fmt.Errorf("on Windows, %v nested within %v is forbidden",
|
||||
mountB.Destination, mountA.Destination),
|
||||
rspec.Version))
|
||||
}
|
||||
if i > j {
|
||||
logrus.Warnf("%v will be covered by %v", mountB.Destination, mountA.Destination)
|
||||
|
@ -476,7 +572,11 @@ func (v *Validator) CheckPlatform() (errs error) {
|
|||
|
||||
if v.platform == "windows" {
|
||||
if v.spec.Windows == nil {
|
||||
errs = multierror.Append(errs, errors.New("'windows' MUST be set when platform is `windows`"))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(
|
||||
specerror.PlatformSpecConfOnWindowsSet,
|
||||
fmt.Errorf("'windows' MUST be set when platform is `windows`"),
|
||||
rspec.Version))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -506,14 +606,14 @@ func (v *Validator) CheckLinux() (errs error) {
|
|||
|
||||
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))
|
||||
if ns.Path != "" && !osFilepath.IsAbs(v.platform, ns.Path) {
|
||||
errs = multierror.Append(errs, specerror.NewError(specerror.NSPathAbs, fmt.Errorf("namespace.path %q is not an absolute path", ns.Path), rspec.Version))
|
||||
}
|
||||
|
||||
tmpItem := nsTypeList[ns.Type]
|
||||
tmpItem.num = tmpItem.num + 1
|
||||
if tmpItem.num > 1 {
|
||||
errs = multierror.Append(errs, fmt.Errorf("duplicated namespace %q", ns.Type))
|
||||
errs = multierror.Append(errs, specerror.NewError(specerror.NSErrorOnDup, fmt.Errorf("duplicated namespace %q", ns.Type), rspec.Version))
|
||||
}
|
||||
|
||||
if len(ns.Path) == 0 {
|
||||
|
@ -524,10 +624,6 @@ func (v *Validator) CheckLinux() (errs error) {
|
|||
|
||||
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 {
|
||||
|
@ -572,7 +668,8 @@ func (v *Validator) CheckLinux() (errs error) {
|
|||
} else {
|
||||
fStat, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
errs = multierror.Append(errs, fmt.Errorf("cannot determine state for device %s", device.Path))
|
||||
errs = multierror.Append(errs, specerror.NewError(specerror.DevicesAvailable,
|
||||
fmt.Errorf("cannot determine state for device %s", device.Path), rspec.Version))
|
||||
continue
|
||||
}
|
||||
var devType string
|
||||
|
@ -587,7 +684,8 @@ func (v *Validator) CheckLinux() (errs error) {
|
|||
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))
|
||||
errs = multierror.Append(errs, specerror.NewError(specerror.DevicesFileNotMatch,
|
||||
fmt.Errorf("unmatched %s already exists in filesystem", device.Path), rspec.Version))
|
||||
continue
|
||||
}
|
||||
if devType != "p" {
|
||||
|
@ -595,7 +693,8 @@ func (v *Validator) CheckLinux() (errs error) {
|
|||
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))
|
||||
errs = multierror.Append(errs, specerror.NewError(specerror.DevicesFileNotMatch,
|
||||
fmt.Errorf("unmatched %s already exists in filesystem", device.Path), rspec.Version))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -603,19 +702,22 @@ func (v *Validator) CheckLinux() (errs error) {
|
|||
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))
|
||||
errs = multierror.Append(errs, specerror.NewError(specerror.DevicesFileNotMatch,
|
||||
fmt.Errorf("unmatched %s already exists in filesystem", device.Path), rspec.Version))
|
||||
continue
|
||||
}
|
||||
}
|
||||
if device.UID != nil {
|
||||
if *device.UID != fStat.Uid {
|
||||
errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
|
||||
errs = multierror.Append(errs, specerror.NewError(specerror.DevicesFileNotMatch,
|
||||
fmt.Errorf("unmatched %s already exists in filesystem", device.Path), rspec.Version))
|
||||
continue
|
||||
}
|
||||
}
|
||||
if device.GID != nil {
|
||||
if *device.GID != fStat.Gid {
|
||||
errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path))
|
||||
errs = multierror.Append(errs, specerror.NewError(specerror.DevicesFileNotMatch,
|
||||
fmt.Errorf("unmatched %s already exists in filesystem", device.Path), rspec.Version))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -641,33 +743,23 @@ func (v *Validator) CheckLinux() (errs error) {
|
|||
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))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(
|
||||
specerror.MaskedPathsAbs,
|
||||
fmt.Errorf("maskedPath %v is not an absolute path", maskedPath),
|
||||
rspec.Version))
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
errs = multierror.Append(errs,
|
||||
specerror.NewError(
|
||||
specerror.ReadonlyPathsAbs,
|
||||
fmt.Errorf("readonlyPath %v is not an absolute path", readonlyPath),
|
||||
rspec.Version))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -709,7 +801,7 @@ func (v *Validator) CheckLinuxResources() (errs error) {
|
|||
}
|
||||
for index := 0; index < len(r.Devices); index++ {
|
||||
switch r.Devices[index].Type {
|
||||
case "a", "b", "c":
|
||||
case "a", "b", "c", "":
|
||||
default:
|
||||
errs = multierror.Append(errs, fmt.Errorf("type of devices %s is invalid", r.Devices[index].Type))
|
||||
}
|
||||
|
@ -728,47 +820,6 @@ func (v *Validator) CheckLinuxResources() (errs error) {
|
|||
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
|
||||
|
@ -825,12 +876,19 @@ func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (errs error) {
|
|||
}
|
||||
|
||||
if v.platform == "linux" {
|
||||
for _, val := range defaultRlimits {
|
||||
for _, val := range linuxRlimits {
|
||||
if val == rlimit.Type {
|
||||
return
|
||||
}
|
||||
}
|
||||
errs = multierror.Append(errs, fmt.Errorf("rlimit type %q is invalid", rlimit.Type))
|
||||
errs = multierror.Append(errs, specerror.NewError(specerror.PosixProcRlimitsTypeValueError, fmt.Errorf("rlimit type %q may not be valid", rlimit.Type), v.spec.Version))
|
||||
} else if v.platform == "solaris" {
|
||||
for _, val := range posixRlimits {
|
||||
if val == rlimit.Type {
|
||||
return
|
||||
}
|
||||
}
|
||||
errs = multierror.Append(errs, specerror.NewError(specerror.PosixProcRlimitsTypeValueError, fmt.Errorf("rlimit type %q may not be valid", rlimit.Type), v.spec.Version))
|
||||
} else {
|
||||
logrus.Warnf("process.rlimits validation not yet implemented for platform %q", v.platform)
|
||||
}
|
||||
|
@ -838,85 +896,6 @@ func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (errs error) {
|
|||
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":
|
||||
|
@ -924,7 +903,7 @@ func deviceValid(d rspec.LinuxDevice) bool {
|
|||
return false
|
||||
}
|
||||
case "p":
|
||||
if d.Major > 0 || d.Minor > 0 {
|
||||
if d.Major != 0 || d.Minor != 0 {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
|
@ -933,41 +912,6 @@ func deviceValid(d rspec.LinuxDevice) bool {
|
|||
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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue