@@ -6,15 +6,18 @@ import (
6
6
"net/http"
7
7
"os"
8
8
"path/filepath"
9
+ "reflect"
9
10
10
11
"github.com/cortexproject/cortex/pkg/alertmanager/alertspb"
11
12
"github.com/cortexproject/cortex/pkg/tenant"
12
13
util_log "github.com/cortexproject/cortex/pkg/util/log"
13
14
14
15
"github.com/go-kit/kit/log"
15
16
"github.com/go-kit/kit/log/level"
17
+ "github.com/pkg/errors"
16
18
"github.com/prometheus/alertmanager/config"
17
19
"github.com/prometheus/alertmanager/template"
20
+ commoncfg "github.com/prometheus/common/config"
18
21
"gopkg.in/yaml.v2"
19
22
)
20
23
@@ -27,6 +30,11 @@ const (
27
30
errNoOrgID = "unable to determine the OrgID"
28
31
)
29
32
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
+
30
38
// UserConfig is used to communicate a users alertmanager configs
31
39
type UserConfig struct {
32
40
TemplateFiles map [string ]string `yaml:"template_files"`
@@ -148,28 +156,52 @@ func validateUserConfig(logger log.Logger, cfg alertspb.AlertConfigDesc) error {
148
156
return err
149
157
}
150
158
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
+
151
178
// Create templates on disk in a temporary directory.
152
179
// Note: This means the validation will succeed if we can write to tmp but
153
180
// not to configured data dir, and on the flipside, it'll fail if we can't write
154
181
// to tmpDir. Ignoring both cases for now as they're ultra rare but will revisit if
155
182
// we see this in the wild.
156
- tmpDir , err := ioutil .TempDir ("" , "validate-config" )
183
+ userTmpDir , err := ioutil .TempDir ("" , "validate-config-" + cfg . User )
157
184
if err != nil {
158
185
return err
159
186
}
160
- defer os .RemoveAll (tmpDir )
187
+ defer os .RemoveAll (userTmpDir )
161
188
162
189
for _ , tmpl := range cfg .Templates {
163
- _ , err := createTemplateFile ( tmpDir , cfg . User , tmpl .Filename , tmpl . Body )
190
+ templateFilepath , err := safeTemplateFilepath ( userTmpDir , tmpl .Filename )
164
191
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 )
167
199
}
168
200
}
169
201
170
202
templateFiles := make ([]string , len (amCfg .Templates ))
171
203
for i , t := range amCfg .Templates {
172
- templateFiles [i ] = filepath .Join (tmpDir , "templates" , cfg . User , t )
204
+ templateFiles [i ] = filepath .Join (userTmpDir , t )
173
205
}
174
206
175
207
_ , err = template .FromGlobs (templateFiles ... )
@@ -184,3 +216,100 @@ func validateUserConfig(logger log.Logger, cfg alertspb.AlertConfigDesc) error {
184
216
185
217
return nil
186
218
}
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