From 6850d134fbd6b9d5aa43aaed44d28e210f26efaf Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Mon, 9 Oct 2023 23:17:17 +0200 Subject: [PATCH] Add basic jsonschema validation --- go.mod | 1 + go.sum | 2 + internal/definitions/config/config.go | 5 +++ internal/validation/schema.go | 40 +++++++++++++++++++ .../validation/schemas/config.schema.json | 30 ++++++++++++++ .../validation/schemas/system.schema.json | 26 ++++++++++++ internal/validation/validation.go | 22 ++++++++++ 7 files changed, 126 insertions(+) create mode 100644 internal/validation/schema.go create mode 100644 internal/validation/schemas/config.schema.json create mode 100644 internal/validation/schemas/system.schema.json create mode 100644 internal/validation/validation.go diff --git a/go.mod b/go.mod index 8c85b3b..8d9c773 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/mattn/go-runewidth v0.0.14 // indirect github.com/pterm/pterm v0.12.61 // indirect github.com/rivo/uniseg v0.4.4 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.3.0 // indirect github.com/tredoe/osutil v1.3.6 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index 13f6810..d1001b4 100644 --- a/go.sum +++ b/go.sum @@ -104,6 +104,8 @@ github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.0 h1:uIkTLo0AGRc8l7h5l9r+GcYi9qfVPt6lD4/bhmzfiKo= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= diff --git a/internal/definitions/config/config.go b/internal/definitions/config/config.go index 18e970e..c94c7a3 100644 --- a/internal/definitions/config/config.go +++ b/internal/definitions/config/config.go @@ -12,6 +12,7 @@ import ( "nfsense.net/nfsense/internal/definitions/service" "nfsense.net/nfsense/internal/definitions/system" "nfsense.net/nfsense/internal/definitions/vpn" + "nfsense.net/nfsense/internal/validation" ) type Config struct { @@ -39,6 +40,10 @@ func (c *Config) Clone() *Config { } func ValidateConfig(conf *Config) error { + err := validation.ValidateConfig(*conf) + if err != nil { + return err + } val := validator.New() val.RegisterValidation("test", nilIfOtherNil) return val.Struct(conf) diff --git a/internal/validation/schema.go b/internal/validation/schema.go new file mode 100644 index 0000000..e722ef4 --- /dev/null +++ b/internal/validation/schema.go @@ -0,0 +1,40 @@ +package validation + +import ( + "embed" + "fmt" + "path/filepath" + + "github.com/santhosh-tekuri/jsonschema/v5" +) + +//go:embed schemas/*.schema.json +var schemasFS embed.FS +var schema *jsonschema.Schema + +func init() { + all, err := schemasFS.ReadDir("schemas") + if err != nil { + panic(fmt.Errorf("Reading Schemas: %w", err)) + } + + c := jsonschema.NewCompiler() + + for _, f := range all { + data, err := schemasFS.Open(filepath.Join("schemas", f.Name())) + if err != nil { + panic(fmt.Errorf("Reading Schema: %w", err)) + } + err = c.AddResource("https://nfsense.net/"+f.Name(), data) + if err != nil { + panic(fmt.Errorf("Adding Schema: %w", err)) + } + } + + s, err := c.Compile("https://nfsense.net/config.schema.json") + if err != nil { + panic(fmt.Errorf("Reading Schemas: %w", err)) + } + + schema = s +} diff --git a/internal/validation/schemas/config.schema.json b/internal/validation/schemas/config.schema.json new file mode 100644 index 0000000..7347858 --- /dev/null +++ b/internal/validation/schemas/config.schema.json @@ -0,0 +1,30 @@ +{ + "$id": "https://nfsense.net/config.schema.json", + "title": "Config", + "type": "object", + "properties": { + "config_version": { + "type": "number" + }, + "firewall": { + "type": ["number","string","boolean","object","array", "null"] + }, + "object": { + "type": ["number","string","boolean","object","array", "null"] + }, + "network": { + "type": ["number","string","boolean","object","array", "null"] + }, + "service": { + "type": ["number","string","boolean","object","array", "null"] + }, + "vpn": { + "type": ["number","string","boolean","object","array", "null"] + }, + "system": { + "description": "System Settings", + "$ref": "https://nfsense.net/system.schema.json" + } + }, + "required": ["config_version", "firewall", "object", "network", "service", "vpn", "system"] +} \ No newline at end of file diff --git a/internal/validation/schemas/system.schema.json b/internal/validation/schemas/system.schema.json new file mode 100644 index 0000000..d66fe90 --- /dev/null +++ b/internal/validation/schemas/system.schema.json @@ -0,0 +1,26 @@ +{ + "$id": "https://nfsense.net/system.schema.json", + "title": "System", + "type": "object", + "properties": { + "users": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "comment": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "salt": { + "type": "string" + } + }, + "required": ["hash", "salt"] + } + } + }, + "required": ["users"] +} \ No newline at end of file diff --git a/internal/validation/validation.go b/internal/validation/validation.go new file mode 100644 index 0000000..a279ea9 --- /dev/null +++ b/internal/validation/validation.go @@ -0,0 +1,22 @@ +package validation + +import ( + "encoding/json" + "fmt" +) + +func ValidateConfig(conf any) error { + + // TODO find a better way validate config since jsonschema only takes a map[string]interface{} + data, err := json.Marshal(conf) + if err != nil { + panic(fmt.Errorf("Marshal Error: %w", err)) + } + var clone any + err = json.Unmarshal(data, &clone) + if err != nil { + panic(fmt.Errorf("Unmarshal Error: %w", err)) + } + + return schema.Validate(clone) +}