From d982e37c4a750d9ac60a31e9f5932e5076cb9a57 Mon Sep 17 00:00:00 2001 From: mpl Date: Thu, 31 Aug 2017 02:19:51 +0200 Subject: [PATCH] pkg/serverinit: catch typos in high-level server config Some errors such are missing fields, or wrong values, or conflicting values were already caught logically when the high-level configuration is transformed into the low-level configuration. Or later when initializing handlers. However, there was no mechanism to catch typos such as "httsCert" instead of "httpsCert", or "leveldb" instead of "levelDB" in the high-level configuration. Such errors would result in misconfiguration (e.g. use of Let's Encrypt instead of the desired HTTPS certificate) which can then even go unnoticed since the server still starts. Therefore, this change generates a fake serverconfig.Config with all its fields set, so that its JSON encoding results in a list of all the possible configuration fields. This allows to compare the given configuration fields with that list, and catch invalid names. Change-Id: I4d6e61462353e52938db93ba332df2d52225e750 --- pkg/serverinit/serverinit.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pkg/serverinit/serverinit.go b/pkg/serverinit/serverinit.go index 4d27566e5..aa38cc8e2 100644 --- a/pkg/serverinit/serverinit.go +++ b/pkg/serverinit/serverinit.go @@ -31,6 +31,7 @@ import ( "net/http" "net/http/pprof" "os" + "reflect" "regexp" "runtime" "runtime/debug" @@ -492,6 +493,16 @@ func load(filename string, opener func(filename string) (jsonconfig.File, error) return nil, fmt.Errorf("Could not unmarshal into a serverconfig.Config: %v", err) } + // At this point, conf.Obj.UnknownKeys() contains all the names found in + // the given high-level configuration. We check them against + // highLevelConfFields(), which gives us all the possible valid + // configuration names, to catch typos or invalid names. + allFields := highLevelConfFields() + for _, v := range conf.Obj.UnknownKeys() { + if _, ok := allFields[v]; !ok { + return nil, fmt.Errorf("unknown high-level configuration parameter: %q", v) + } + } conf, err = genLowLevelConfig(&hiLevelConf) if err != nil { return nil, fmt.Errorf( @@ -777,3 +788,27 @@ func logsHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "no such logs", 404) } } + +// highLevelConfFields returns all the possible fields of a serverconfig.Config, +// in their JSON form. This allows checking that the parameters in the high-level +// server configuration file are at least valid names, which is useful to catch +// typos. +func highLevelConfFields() map[string]bool { + knownFields := make(map[string]bool) + var c serverconfig.Config + s := reflect.ValueOf(&c).Elem() + for i := 0; i < s.NumField(); i++ { + f := s.Type().Field(i) + jsonTag, ok := f.Tag.Lookup("json") + if !ok { + panic(fmt.Sprintf("%q field in serverconfig.Config does not have a json tag", f.Name)) + } + jsonFields := strings.Split(strings.TrimSuffix(strings.TrimPrefix(jsonTag, `"`), `"`), ",") + jsonName := jsonFields[0] + if jsonName == "" { + panic(fmt.Sprintf("no json field name for %q field in serverconfig.Config", f.Name)) + } + knownFields[jsonName] = true + } + return knownFields +}