@@ -6,15 +6,18 @@ import (
66 "net/http"
77 "os"
88 "path/filepath"
9+ "reflect"
910
1011 "github.com/cortexproject/cortex/pkg/alertmanager/alerts"
1112 "github.com/cortexproject/cortex/pkg/tenant"
1213 "github.com/cortexproject/cortex/pkg/util"
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"`
@@ -146,28 +154,52 @@ func validateUserConfig(logger log.Logger, cfg alerts.AlertConfigDesc) error {
146154 return err
147155 }
148156
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+
149176 // Create templates on disk in a temporary directory.
150177 // Note: This means the validation will succeed if we can write to tmp but
151178 // not to configured data dir, and on the flipside, it'll fail if we can't write
152179 // to tmpDir. Ignoring both cases for now as they're ultra rare but will revisit if
153180 // we see this in the wild.
154- tmpDir , err := ioutil .TempDir ("" , "validate-config" )
181+ userTmpDir , err := ioutil .TempDir ("" , "validate-config-" + cfg . User )
155182 if err != nil {
156183 return err
157184 }
158- defer os .RemoveAll (tmpDir )
185+ defer os .RemoveAll (userTmpDir )
159186
160187 for _ , tmpl := range cfg .Templates {
161- _ , err := createTemplateFile ( tmpDir , cfg . User , tmpl .Filename , tmpl . Body )
188+ templateFilepath , err := safeTemplateFilepath ( userTmpDir , tmpl .Filename )
162189 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 )
165197 }
166198 }
167199
168200 templateFiles := make ([]string , len (amCfg .Templates ))
169201 for i , t := range amCfg .Templates {
170- templateFiles [i ] = filepath .Join (tmpDir , "templates" , cfg . User , t )
202+ templateFiles [i ] = filepath .Join (userTmpDir , t )
171203 }
172204
173205 _ , err = template .FromGlobs (templateFiles ... )
@@ -182,3 +214,97 @@ func validateUserConfig(logger log.Logger, cfg alerts.AlertConfigDesc) error {
182214
183215 return nil
184216}
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