7
7
"net/http"
8
8
"os"
9
9
"path/filepath"
10
-
11
- "github.com/pkg/errors"
10
+ "reflect"
12
11
13
12
"github.com/cortexproject/cortex/pkg/alertmanager/alertspb"
14
13
"github.com/cortexproject/cortex/pkg/tenant"
@@ -18,8 +17,10 @@ import (
18
17
19
18
"github.com/go-kit/kit/log"
20
19
"github.com/go-kit/kit/log/level"
20
+ "github.com/pkg/errors"
21
21
"github.com/prometheus/alertmanager/config"
22
22
"github.com/prometheus/alertmanager/template"
23
+ commoncfg "github.com/prometheus/common/config"
23
24
"gopkg.in/yaml.v2"
24
25
)
25
26
@@ -35,6 +36,14 @@ const (
35
36
fetchConcurrency = 16
36
37
)
37
38
39
+ var (
40
+ errPasswordFileNotAllowed = errors .New ("setting password_file, bearer_token_file and credentials_file is not allowed" )
41
+ errProxyURLNotAllowed = errors .New ("setting proxy_url is not allowed" )
42
+ errTLSFileNotAllowed = errors .New ("setting TLS ca_file, cert_file and key_file is not allowed" )
43
+ errSlackAPIURLFileNotAllowed = errors .New ("setting Slack api_url_file and global slack_api_url_file is not allowed" )
44
+ errVictorOpsAPIKeyFileNotAllowed = errors .New ("setting VictorOps api_key_file is not allowed" )
45
+ )
46
+
38
47
// UserConfig is used to communicate a users alertmanager configs
39
48
type UserConfig struct {
40
49
TemplateFiles map [string ]string `yaml:"template_files"`
@@ -156,6 +165,25 @@ func validateUserConfig(logger log.Logger, cfg alertspb.AlertConfigDesc) error {
156
165
return err
157
166
}
158
167
168
+ // Validate the config recursively scanning it.
169
+ if err := validateAlertmanagerConfig (amCfg ); err != nil {
170
+ return err
171
+ }
172
+
173
+ // Validate templates referenced in the alertmanager config.
174
+ for _ , name := range amCfg .Templates {
175
+ if err := validateTemplateFilename (name ); err != nil {
176
+ return err
177
+ }
178
+ }
179
+
180
+ // Validate template files.
181
+ for _ , tmpl := range cfg .Templates {
182
+ if err := validateTemplateFilename (tmpl .Filename ); err != nil {
183
+ return err
184
+ }
185
+ }
186
+
159
187
// Create templates on disk in a temporary directory.
160
188
// Note: This means the validation will succeed if we can write to tmp but
161
189
// not to configured data dir, and on the flipside, it'll fail if we can't write
@@ -168,10 +196,15 @@ func validateUserConfig(logger log.Logger, cfg alertspb.AlertConfigDesc) error {
168
196
defer os .RemoveAll (userTempDir )
169
197
170
198
for _ , tmpl := range cfg .Templates {
171
- _ , err := storeTemplateFile (userTempDir , tmpl .Filename , tmpl . Body )
199
+ templateFilepath , err := safeTemplateFilepath (userTempDir , tmpl .Filename )
172
200
if err != nil {
173
- level .Error (logger ).Log ("msg" , "unable to create template file" , "err" , err , "user" , cfg .User )
174
- return fmt .Errorf ("unable to create template file '%s'" , tmpl .Filename )
201
+ level .Error (logger ).Log ("msg" , "unable to create template file path" , "err" , err , "user" , cfg .User )
202
+ return err
203
+ }
204
+
205
+ if _ , err = storeTemplateFile (templateFilepath , tmpl .Body ); err != nil {
206
+ level .Error (logger ).Log ("msg" , "unable to store template file" , "err" , err , "user" , cfg .User )
207
+ return fmt .Errorf ("unable to store template file '%s'" , tmpl .Filename )
175
208
}
176
209
}
177
210
@@ -237,3 +270,149 @@ func (am *MultitenantAlertmanager) ListAllConfigs(w http.ResponseWriter, r *http
237
270
close (iter )
238
271
<- done
239
272
}
273
+
274
+ // validateAlertmanagerConfig recursively scans the input config looking for data types for which
275
+ // we have a specific validation and, whenever encountered, it runs their validation. Returns the
276
+ // first error or nil if validation succeeds.
277
+ func validateAlertmanagerConfig (cfg interface {}) error {
278
+ v := reflect .ValueOf (cfg )
279
+ t := v .Type ()
280
+
281
+ // Skip invalid, the zero value or a nil pointer (checked by zero value).
282
+ if ! v .IsValid () || v .IsZero () {
283
+ return nil
284
+ }
285
+
286
+ // If the input config is a pointer then we need to get its value.
287
+ // At this point the pointer value can't be nil.
288
+ if v .Kind () == reflect .Ptr {
289
+ v = v .Elem ()
290
+ t = v .Type ()
291
+ }
292
+
293
+ // Check if the input config is a data type for which we have a specific validation.
294
+ // At this point the value can't be a pointer anymore.
295
+ switch t {
296
+ case reflect .TypeOf (config.GlobalConfig {}):
297
+ if err := validateGlobalConfig (v .Interface ().(config.GlobalConfig )); err != nil {
298
+ return err
299
+ }
300
+
301
+ case reflect .TypeOf (commoncfg.HTTPClientConfig {}):
302
+ if err := validateReceiverHTTPConfig (v .Interface ().(commoncfg.HTTPClientConfig )); err != nil {
303
+ return err
304
+ }
305
+
306
+ case reflect .TypeOf (commoncfg.TLSConfig {}):
307
+ if err := validateReceiverTLSConfig (v .Interface ().(commoncfg.TLSConfig )); err != nil {
308
+ return err
309
+ }
310
+
311
+ case reflect .TypeOf (config.SlackConfig {}):
312
+ if err := validateSlackConfig (v .Interface ().(config.SlackConfig )); err != nil {
313
+ return err
314
+ }
315
+
316
+ case reflect .TypeOf (config.VictorOpsConfig {}):
317
+ if err := validateVictorOpsConfig (v .Interface ().(config.VictorOpsConfig )); err != nil {
318
+ return err
319
+ }
320
+ }
321
+
322
+ // If the input config is a struct, recursively iterate on all fields.
323
+ if t .Kind () == reflect .Struct {
324
+ for i := 0 ; i < t .NumField (); i ++ {
325
+ field := t .Field (i )
326
+ fieldValue := v .FieldByIndex (field .Index )
327
+
328
+ // Skip any field value which can't be converted to interface (eg. primitive types).
329
+ if fieldValue .CanInterface () {
330
+ if err := validateAlertmanagerConfig (fieldValue .Interface ()); err != nil {
331
+ return err
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ if t .Kind () == reflect .Slice || t .Kind () == reflect .Array {
338
+ for i := 0 ; i < v .Len (); i ++ {
339
+ fieldValue := v .Index (i )
340
+
341
+ // Skip any field value which can't be converted to interface (eg. primitive types).
342
+ if fieldValue .CanInterface () {
343
+ if err := validateAlertmanagerConfig (fieldValue .Interface ()); err != nil {
344
+ return err
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ if t .Kind () == reflect .Map {
351
+ for _ , key := range v .MapKeys () {
352
+ fieldValue := v .MapIndex (key )
353
+
354
+ // Skip any field value which can't be converted to interface (eg. primitive types).
355
+ if fieldValue .CanInterface () {
356
+ if err := validateAlertmanagerConfig (fieldValue .Interface ()); err != nil {
357
+ return err
358
+ }
359
+ }
360
+ }
361
+ }
362
+
363
+ return nil
364
+ }
365
+
366
+ // validateReceiverHTTPConfig validates the HTTP config and returns an error if it contains
367
+ // settings not allowed by Cortex.
368
+ func validateReceiverHTTPConfig (cfg commoncfg.HTTPClientConfig ) error {
369
+ if cfg .BasicAuth != nil && cfg .BasicAuth .PasswordFile != "" {
370
+ return errPasswordFileNotAllowed
371
+ }
372
+ if cfg .Authorization != nil && cfg .Authorization .CredentialsFile != "" {
373
+ return errPasswordFileNotAllowed
374
+ }
375
+ if cfg .BearerTokenFile != "" {
376
+ return errPasswordFileNotAllowed
377
+ }
378
+ if cfg .ProxyURL .URL != nil {
379
+ return errProxyURLNotAllowed
380
+ }
381
+ return validateReceiverTLSConfig (cfg .TLSConfig )
382
+ }
383
+
384
+ // validateReceiverTLSConfig validates the TLS config and returns an error if it contains
385
+ // settings not allowed by Cortex.
386
+ func validateReceiverTLSConfig (cfg commoncfg.TLSConfig ) error {
387
+ if cfg .CAFile != "" || cfg .CertFile != "" || cfg .KeyFile != "" {
388
+ return errTLSFileNotAllowed
389
+ }
390
+ return nil
391
+ }
392
+
393
+ // validateGlobalConfig validates the Global config and returns an error if it contains
394
+ // settings now allowed by Cortex.
395
+ func validateGlobalConfig (cfg config.GlobalConfig ) error {
396
+ if cfg .SlackAPIURLFile != "" {
397
+ return errSlackAPIURLFileNotAllowed
398
+ }
399
+ return nil
400
+ }
401
+
402
+ // validateSlackConfig validates the Slack config and returns an error if it contains
403
+ // settings now allowed by Cortex.
404
+ func validateSlackConfig (cfg config.SlackConfig ) error {
405
+ if cfg .APIURLFile != "" {
406
+ return errSlackAPIURLFileNotAllowed
407
+ }
408
+ return nil
409
+ }
410
+
411
+ // validateVictorOpsConfig validates the VictorOps config and returns an error if it contains
412
+ // settings now allowed by Cortex.
413
+ func validateVictorOpsConfig (cfg config.VictorOpsConfig ) error {
414
+ if cfg .APIKeyFile != "" {
415
+ return errVictorOpsAPIKeyFileNotAllowed
416
+ }
417
+ return nil
418
+ }
0 commit comments