Skip to content

Commit ef75b8b

Browse files
authored
* Improve alertmanager template checks. * Added HTTP and TLS validation * Use reflection to scan alertmanager config for validation * Fix validation of template files. Signed-off-by: Marco Pracucci <[email protected]> Signed-off-by: Peter Štibraný <[email protected]>
1 parent 3a3015e commit ef75b8b

File tree

5 files changed

+563
-47
lines changed

5 files changed

+563
-47
lines changed

pkg/alertmanager/alertmanager.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,13 @@ func clusterWait(p *cluster.Peer, timeout time.Duration) func() time.Duration {
209209
// ApplyConfig applies a new configuration to an Alertmanager.
210210
func (am *Alertmanager) ApplyConfig(userID string, conf *config.Config, rawCfg string) error {
211211
templateFiles := make([]string, len(conf.Templates))
212-
if len(conf.Templates) > 0 {
213-
for i, t := range conf.Templates {
214-
templateFiles[i] = filepath.Join(am.cfg.DataDir, "templates", userID, t)
212+
for i, t := range conf.Templates {
213+
templateFilepath, err := safeTemplateFilepath(filepath.Join(am.cfg.DataDir, "templates", userID), t)
214+
if err != nil {
215+
return err
215216
}
217+
218+
templateFiles[i] = templateFilepath
216219
}
217220

218221
tmpl, err := template.FromGlobs(templateFiles...)

pkg/alertmanager/api.go

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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
3139
type 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

Comments
 (0)