From 02038f512d845e48c80364dcfb221c55fee9acaa Mon Sep 17 00:00:00 2001 From: Thomas Gazagnaire Date: Tue, 11 Apr 2017 23:08:58 +0200 Subject: [PATCH] Add Yaml validation Fix #1292 Transform the Yaml struct to JSON and validate it against a JSON schema. Signed-off-by: Thomas Gazagnaire --- src/cmd/moby/config.go | 48 ++++++++++++++++- src/cmd/moby/schema.go | 113 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 src/cmd/moby/schema.go diff --git a/src/cmd/moby/config.go b/src/cmd/moby/config.go index 11a9eafe4..76a45b02d 100644 --- a/src/cmd/moby/config.go +++ b/src/cmd/moby/config.go @@ -15,6 +15,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/engine-api/types" "github.com/opencontainers/runtime-spec/specs-go" + "github.com/xeipuuv/gojsonschema" "gopkg.in/yaml.v2" ) @@ -80,11 +81,56 @@ type MobyImage struct { Sysctl map[string]string } +// recursively convert the map keys from `interface{}` to `string` +// this is needed to convert "yaml" interfaces to "JSON" interfaces +// see http://stackoverflow.com/questions/40737122/convert-yaml-to-json-without-struct-golang#answer-40737676 +func convert(i interface{}) interface{} { + switch x := i.(type) { + case map[interface{}]interface{}: + m2 := map[string]interface{}{} + for k, v := range x { + m2[k.(string)] = convert(v) + } + return m2 + case []interface{}: + for i, v := range x { + x[i] = convert(v) + } + } + return i +} + // NewConfig parses a config file func NewConfig(config []byte) (*Moby, error) { m := Moby{} - err := yaml.Unmarshal(config, &m) + // Parse raw yaml + var rawYaml interface{} + err := yaml.Unmarshal(config, &rawYaml) + if err != nil { + return &m, err + } + + // Convert to raw JSON + rawJSON := convert(rawYaml) + + // Validate raw yaml with JSON schema + schemaLoader := gojsonschema.NewStringLoader(schema) + documentLoader := gojsonschema.NewGoLoader(rawJSON) + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + if err != nil { + return &m, err + } + if !result.Valid() { + fmt.Printf("The configuration file is invalid:\n") + for _, desc := range result.Errors() { + fmt.Printf("- %s\n", desc) + } + return &m, fmt.Errorf("invalid configuration file") + } + + // Parse yaml + err = yaml.Unmarshal(config, &m) if err != nil { return &m, err } diff --git a/src/cmd/moby/schema.go b/src/cmd/moby/schema.go new file mode 100644 index 000000000..31913893b --- /dev/null +++ b/src/cmd/moby/schema.go @@ -0,0 +1,113 @@ +package main + +var schema = string(` +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Moby Config", + "additionalProperties": false, + "definitions": { + "kernel": { + "type": "object", + "additionalProperties": false, + "properties": { + "image": { "type": "string"}, + "cmdline": { "type": "string"} + } + }, + "file": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": {"type": "string"}, + "directory": {"type": "boolean"}, + "contents": {"type": "string"} + } + }, + "files": { + "type": "array", + "items": { "$ref": "#/definitions/file" } + }, + "output": { + "type": "object", + "additionalProperties": false, + "properties": { + "format": {"type": "string"}, + "project": {"type": "string"}, + "bucket": {"type": "string"}, + "family": {"type": "string"}, + "keys": {"type": "string"}, + "public": {"type": "boolean"}, + "replace": {"type": "boolean"} + } + }, + "outputs": { + "type": "array", + "items": { "$ref": "#/definitions/output" } + }, + "trust": { + "type": "object", + "additionalProperties": false, + "properties": { + "image": { "$ref": "#/definitions/strings" }, + "org": { "$ref": "#/definitions/strings" } + } + }, + "strings": { + "type": "array", + "items": {"type": "string"} + }, + "image": { + "type": "object", + "additionalProperties": false, + "required": ["name", "image"], + "properties": { + "name": {"type": "string"}, + "image": {"type": "string"}, + "capabilities": { "$ref": "#/definitions/strings" }, + "mounts": { "$ref": "#/definitions/strings" }, + "binds": { "$ref": "#/definitions/strings" }, + "tmpfs": { "$ref": "#/definitions/strings" }, + "command": { "$ref": "#/definitions/strings" }, + "env": { "$ref": "#/definitions/strings" }, + "cwd": { "type": "string"}, + "net": { "type": "string"}, + "pid": { "type": "string"}, + "ipc": { "type": "string"}, + "uts": { "type": "string"}, + "readonly": { "type": "boolean"}, + "maskedPaths": { "$ref": "#/definitions/strings" }, + "readonlyPaths": { "$ref": "#/definitions/strings" }, + "uid": {"type": "integer"}, + "gid": {"type": "integer"}, + "additionalGids": { + "type": "array", + "items": { "type": "integer" } + }, + "noNewPrivileges": {"type": "boolean"}, + "hostname": {"type": "string"}, + "oomScoreAdj": {"type": "integer"}, + "disableOOMKiller": {"type": "boolean"}, + "rootfsPropagation": {"type": "string"}, + "cgroupsPath": {"type": "string"}, + "sysctl": { + "type": "array", + "items": { "$ref": "#/definitions/strings" } + } + } + }, + "images": { + "type": "array", + "items": { "$ref": "#/definitions/image" } + } + }, + "properties": { + "kernel": { "$ref": "#/definitions/kernel" }, + "init": { "$ref": "#/definitions/strings" }, + "onboot": { "$ref": "#/definitions/images" }, + "services": { "$ref": "#/definitions/images" }, + "trust": { "$ref": "#/definitions/trust" }, + "files": { "$ref": "#/definitions/files" }, + "outputs": { "$ref": "#/definitions/outputs" } + } +} +`)