@@ -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/alerts"
11
12
"github.com/cortexproject/cortex/pkg/tenant"
12
13
"github.com/cortexproject/cortex/pkg/util"
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"`
@@ -146,28 +154,52 @@ func validateUserConfig(logger log.Logger, cfg alerts.AlertConfigDesc) error {
146
154
return err
147
155
}
148
156
157
+ // Validate the config recursively scanning it.
158
+ if err := validateAlertmanagerConfig (amCfg ); err != nil {
159
+ return err
160
+ }
161
+
162
+ // Validate templates referenced in the alertmanager config.
163
+ for _ , name := range amCfg .Templates {
164
+ if err := validateTemplateFilename (name ); err != nil {
165
+ return err
166
+ }
167
+ }
168
+
169
+ // Validate template files.
170
+ for _ , tmpl := range cfg .Templates {
171
+ if err := validateTemplateFilename (tmpl .Filename ); err != nil {
172
+ return err
173
+ }
174
+ }
175
+
149
176
// Create templates on disk in a temporary directory.
150
177
// Note: This means the validation will succeed if we can write to tmp but
151
178
// not to configured data dir, and on the flipside, it'll fail if we can't write
152
179
// to tmpDir. Ignoring both cases for now as they're ultra rare but will revisit if
153
180
// we see this in the wild.
154
- tmpDir , err := ioutil .TempDir ("" , "validate-config" )
181
+ userTmpDir , err := ioutil .TempDir ("" , "validate-config-" + cfg . User )
155
182
if err != nil {
156
183
return err
157
184
}
158
- defer os .RemoveAll (tmpDir )
185
+ defer os .RemoveAll (userTmpDir )
159
186
160
187
for _ , tmpl := range cfg .Templates {
161
- _ , err := createTemplateFile ( tmpDir , cfg . User , tmpl .Filename , tmpl . Body )
188
+ templateFilepath , err := safeTemplateFilepath ( userTmpDir , tmpl .Filename )
162
189
if err != nil {
163
- level .Error (logger ).Log ("msg" , "unable to create template file" , "err" , err , "user" , cfg .User )
164
- return fmt .Errorf ("unable to create template file '%s'" , tmpl .Filename )
190
+ level .Error (logger ).Log ("msg" , "unable to create template file path" , "err" , err , "user" , cfg .User )
191
+ return err
192
+ }
193
+
194
+ if _ , err = storeTemplateFile (templateFilepath , tmpl .Body ); err != nil {
195
+ level .Error (logger ).Log ("msg" , "unable to store template file" , "err" , err , "user" , cfg .User )
196
+ return fmt .Errorf ("unable to store template file '%s'" , tmpl .Filename )
165
197
}
166
198
}
167
199
168
200
templateFiles := make ([]string , len (amCfg .Templates ))
169
201
for i , t := range amCfg .Templates {
170
- templateFiles [i ] = filepath .Join (tmpDir , "templates" , cfg . User , t )
202
+ templateFiles [i ] = filepath .Join (userTmpDir , t )
171
203
}
172
204
173
205
_ , err = template .FromGlobs (templateFiles ... )
@@ -182,3 +214,97 @@ func validateUserConfig(logger log.Logger, cfg alerts.AlertConfigDesc) error {
182
214
183
215
return nil
184
216
}
217
+
218
+ // validateAlertmanagerConfig recursively scans the input config looking for data types for which
219
+ // we have a specific validation and, whenever encountered, it runs their validation. Returns the
220
+ // first error or nil if validation succeeds.
221
+ func validateAlertmanagerConfig (cfg interface {}) error {
222
+ v := reflect .ValueOf (cfg )
223
+ t := v .Type ()
224
+
225
+ // Skip invalid, the zero value or a nil pointer (checked by zero value).
226
+ if ! v .IsValid () || v .IsZero () {
227
+ return nil
228
+ }
229
+
230
+ // If the input config is a pointer then we need to get its value.
231
+ // At this point the pointer value can't be nil.
232
+ if v .Kind () == reflect .Ptr {
233
+ v = v .Elem ()
234
+ t = v .Type ()
235
+ }
236
+
237
+ // Check if the input config is a data type for which we have a specific validation.
238
+ // At this point the value can't be a pointer anymore.
239
+ switch t {
240
+ case reflect .TypeOf (commoncfg.HTTPClientConfig {}):
241
+ return validateReceiverHTTPConfig (v .Interface ().(commoncfg.HTTPClientConfig ))
242
+
243
+ case reflect .TypeOf (commoncfg.TLSConfig {}):
244
+ return validateReceiverTLSConfig (v .Interface ().(commoncfg.TLSConfig ))
245
+ }
246
+
247
+ // If the input config is a struct, recursively iterate on all fields.
248
+ if t .Kind () == reflect .Struct {
249
+ for i := 0 ; i < t .NumField (); i ++ {
250
+ field := t .Field (i )
251
+ fieldValue := v .FieldByIndex (field .Index )
252
+
253
+ // Skip any field value which can't be converted to interface (eg. primitive types).
254
+ if fieldValue .CanInterface () {
255
+ if err := validateAlertmanagerConfig (fieldValue .Interface ()); err != nil {
256
+ return err
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ if t .Kind () == reflect .Slice || t .Kind () == reflect .Array {
263
+ for i := 0 ; i < v .Len (); i ++ {
264
+ fieldValue := v .Index (i )
265
+
266
+ // Skip any field value which can't be converted to interface (eg. primitive types).
267
+ if fieldValue .CanInterface () {
268
+ if err := validateAlertmanagerConfig (fieldValue .Interface ()); err != nil {
269
+ return err
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ if t .Kind () == reflect .Map {
276
+ for _ , key := range v .MapKeys () {
277
+ fieldValue := v .MapIndex (key )
278
+
279
+ // Skip any field value which can't be converted to interface (eg. primitive types).
280
+ if fieldValue .CanInterface () {
281
+ if err := validateAlertmanagerConfig (fieldValue .Interface ()); err != nil {
282
+ return err
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ return nil
289
+ }
290
+
291
+ // validateReceiverHTTPConfig validates the HTTP config and returns an error if it contains
292
+ // settings not allowed by Cortex.
293
+ func validateReceiverHTTPConfig (cfg commoncfg.HTTPClientConfig ) error {
294
+ if cfg .BasicAuth != nil && cfg .BasicAuth .PasswordFile != "" {
295
+ return errPasswordFileNotAllowed
296
+ }
297
+ if cfg .BearerTokenFile != "" {
298
+ return errPasswordFileNotAllowed
299
+ }
300
+ return validateReceiverTLSConfig (cfg .TLSConfig )
301
+ }
302
+
303
+ // validateReceiverTLSConfig validates the TLS config and returns an error if it contains
304
+ // settings not allowed by Cortex.
305
+ func validateReceiverTLSConfig (cfg commoncfg.TLSConfig ) error {
306
+ if cfg .CAFile != "" || cfg .CertFile != "" || cfg .KeyFile != "" {
307
+ return errTLSFileNotAllowed
308
+ }
309
+ return nil
310
+ }
0 commit comments