@@ -6,15 +6,18 @@ import (
66 "net/http"
77 "os"
88 "path/filepath"
9+ "reflect"
910
1011 "github.com/cortexproject/cortex/pkg/alertmanager/alertspb"
1112 "github.com/cortexproject/cortex/pkg/tenant"
1213 util_log "github.com/cortexproject/cortex/pkg/util/log"
1314
1415 "github.com/go-kit/kit/log"
1516 "github.com/go-kit/kit/log/level"
17+ "github.com/pkg/errors"
1618 "github.com/prometheus/alertmanager/config"
1719 "github.com/prometheus/alertmanager/template"
20+ commoncfg "github.com/prometheus/common/config"
1821 "gopkg.in/yaml.v2"
1922)
2023
@@ -27,6 +30,11 @@ const (
2730 errNoOrgID = "unable to determine the OrgID"
2831)
2932
33+ var (
34+ errPasswordFileNotAllowed = errors .New ("setting password_file, bearer_token_file and credentials_file is not allowed" )
35+ errTLSFileNotAllowed = errors .New ("setting TLS ca_file, cert_file and key_file is not allowed" )
36+ )
37+
3038// UserConfig is used to communicate a users alertmanager configs
3139type UserConfig struct {
3240 TemplateFiles map [string ]string `yaml:"template_files"`
@@ -148,28 +156,52 @@ func validateUserConfig(logger log.Logger, cfg alertspb.AlertConfigDesc) error {
148156 return err
149157 }
150158
159+ // Validate the config recursively scanning it.
160+ if err := validateAlertmanagerConfig (amCfg ); err != nil {
161+ return err
162+ }
163+
164+ // Validate templates referenced in the alertmanager config.
165+ for _ , name := range amCfg .Templates {
166+ if err := validateTemplateFilename (name ); err != nil {
167+ return err
168+ }
169+ }
170+
171+ // Validate template files.
172+ for _ , tmpl := range cfg .Templates {
173+ if err := validateTemplateFilename (tmpl .Filename ); err != nil {
174+ return err
175+ }
176+ }
177+
151178 // Create templates on disk in a temporary directory.
152179 // Note: This means the validation will succeed if we can write to tmp but
153180 // not to configured data dir, and on the flipside, it'll fail if we can't write
154181 // to tmpDir. Ignoring both cases for now as they're ultra rare but will revisit if
155182 // we see this in the wild.
156- tmpDir , err := ioutil .TempDir ("" , "validate-config" )
183+ userTmpDir , err := ioutil .TempDir ("" , "validate-config-" + cfg . User )
157184 if err != nil {
158185 return err
159186 }
160- defer os .RemoveAll (tmpDir )
187+ defer os .RemoveAll (userTmpDir )
161188
162189 for _ , tmpl := range cfg .Templates {
163- _ , err := createTemplateFile ( tmpDir , cfg . User , tmpl .Filename , tmpl . Body )
190+ templateFilepath , err := safeTemplateFilepath ( userTmpDir , tmpl .Filename )
164191 if err != nil {
165- level .Error (logger ).Log ("msg" , "unable to create template file" , "err" , err , "user" , cfg .User )
166- return fmt .Errorf ("unable to create template file '%s'" , tmpl .Filename )
192+ level .Error (logger ).Log ("msg" , "unable to create template file path" , "err" , err , "user" , cfg .User )
193+ return err
194+ }
195+
196+ if _ , err = storeTemplateFile (templateFilepath , tmpl .Body ); err != nil {
197+ level .Error (logger ).Log ("msg" , "unable to store template file" , "err" , err , "user" , cfg .User )
198+ return fmt .Errorf ("unable to store template file '%s'" , tmpl .Filename )
167199 }
168200 }
169201
170202 templateFiles := make ([]string , len (amCfg .Templates ))
171203 for i , t := range amCfg .Templates {
172- templateFiles [i ] = filepath .Join (tmpDir , "templates" , cfg . User , t )
204+ templateFiles [i ] = filepath .Join (userTmpDir , t )
173205 }
174206
175207 _ , err = template .FromGlobs (templateFiles ... )
@@ -184,3 +216,100 @@ func validateUserConfig(logger log.Logger, cfg alertspb.AlertConfigDesc) error {
184216
185217 return nil
186218}
219+
220+ // validateAlertmanagerConfig recursively scans the input config looking for data types for which
221+ // we have a specific validation and, whenever encountered, it runs their validation. Returns the
222+ // first error or nil if validation succeeds.
223+ func validateAlertmanagerConfig (cfg interface {}) error {
224+ v := reflect .ValueOf (cfg )
225+ t := v .Type ()
226+
227+ // Skip invalid, the zero value or a nil pointer (checked by zero value).
228+ if ! v .IsValid () || v .IsZero () {
229+ return nil
230+ }
231+
232+ // If the input config is a pointer then we need to get its value.
233+ // At this point the pointer value can't be nil.
234+ if v .Kind () == reflect .Ptr {
235+ v = v .Elem ()
236+ t = v .Type ()
237+ }
238+
239+ // Check if the input config is a data type for which we have a specific validation.
240+ // At this point the value can't be a pointer anymore.
241+ switch t {
242+ case reflect .TypeOf (commoncfg.HTTPClientConfig {}):
243+ return validateReceiverHTTPConfig (v .Interface ().(commoncfg.HTTPClientConfig ))
244+
245+ case reflect .TypeOf (commoncfg.TLSConfig {}):
246+ return validateReceiverTLSConfig (v .Interface ().(commoncfg.TLSConfig ))
247+ }
248+
249+ // If the input config is a struct, recursively iterate on all fields.
250+ if t .Kind () == reflect .Struct {
251+ for i := 0 ; i < t .NumField (); i ++ {
252+ field := t .Field (i )
253+ fieldValue := v .FieldByIndex (field .Index )
254+
255+ // Skip any field value which can't be converted to interface (eg. primitive types).
256+ if fieldValue .CanInterface () {
257+ if err := validateAlertmanagerConfig (fieldValue .Interface ()); err != nil {
258+ return err
259+ }
260+ }
261+ }
262+ }
263+
264+ if t .Kind () == reflect .Slice || t .Kind () == reflect .Array {
265+ for i := 0 ; i < v .Len (); i ++ {
266+ fieldValue := v .Index (i )
267+
268+ // Skip any field value which can't be converted to interface (eg. primitive types).
269+ if fieldValue .CanInterface () {
270+ if err := validateAlertmanagerConfig (fieldValue .Interface ()); err != nil {
271+ return err
272+ }
273+ }
274+ }
275+ }
276+
277+ if t .Kind () == reflect .Map {
278+ for _ , key := range v .MapKeys () {
279+ fieldValue := v .MapIndex (key )
280+
281+ // Skip any field value which can't be converted to interface (eg. primitive types).
282+ if fieldValue .CanInterface () {
283+ if err := validateAlertmanagerConfig (fieldValue .Interface ()); err != nil {
284+ return err
285+ }
286+ }
287+ }
288+ }
289+
290+ return nil
291+ }
292+
293+ // validateReceiverHTTPConfig validates the HTTP config and returns an error if it contains
294+ // settings not allowed by Cortex.
295+ func validateReceiverHTTPConfig (cfg commoncfg.HTTPClientConfig ) error {
296+ if cfg .BasicAuth != nil && cfg .BasicAuth .PasswordFile != "" {
297+ return errPasswordFileNotAllowed
298+ }
299+ if cfg .Authorization != nil && cfg .Authorization .CredentialsFile != "" {
300+ return errPasswordFileNotAllowed
301+ }
302+ if cfg .BearerTokenFile != "" {
303+ return errPasswordFileNotAllowed
304+ }
305+ return validateReceiverTLSConfig (cfg .TLSConfig )
306+ }
307+
308+ // validateReceiverTLSConfig validates the TLS config and returns an error if it contains
309+ // settings not allowed by Cortex.
310+ func validateReceiverTLSConfig (cfg commoncfg.TLSConfig ) error {
311+ if cfg .CAFile != "" || cfg .CertFile != "" || cfg .KeyFile != "" {
312+ return errTLSFileNotAllowed
313+ }
314+ return nil
315+ }
0 commit comments