Skip to content

Commit 95fd826

Browse files
authored
Merge release-1.7 into release-1.8
2 parents 51662ea + a6b0ffc commit 95fd826

File tree

6 files changed

+599
-44
lines changed

6 files changed

+599
-44
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@
102102
* [BUGFIX] Alertmanager: Ensure that experimental `/api/v1/alerts` endpoints work when `-http.prefix` is empty. #3905
103103
* [BUGFIX] Chunk store: fix panic in inverted index when deleted fingerprint is no longer in the index. #3543
104104

105+
## 1.7.1 / 2021-04-27
106+
107+
* [CHANGE] Fix for CVE-2021-31232: Local file disclosure vulnerability when `-experimental.alertmanager.enable-api` is used. The HTTP basic auth `password_file` can be used as an attack vector to send any file content via a webhook. The alertmanager templates can be used as an attack vector to send any file content because the alertmanager can load any text file specified in the templates list.
108+
105109
## 1.7.0 / 2021-02-23
106110

107111
Note the blocks storage compactor runs a migration task at startup in this version, which can take many minutes and use a lot of RAM.

pkg/alertmanager/alertmanager.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,13 @@ func clusterWait(p *cluster.Peer, timeout time.Duration) func() time.Duration {
204204
// ApplyConfig applies a new configuration to an Alertmanager.
205205
func (am *Alertmanager) ApplyConfig(userID string, conf *config.Config, rawCfg string) error {
206206
templateFiles := make([]string, len(conf.Templates))
207-
if len(conf.Templates) > 0 {
208-
for i, t := range conf.Templates {
209-
templateFiles[i] = filepath.Join(am.cfg.DataDir, "templates", userID, t)
207+
for i, t := range conf.Templates {
208+
templateFilepath, err := safeTemplateFilepath(filepath.Join(am.cfg.DataDir, "templates", userID), t)
209+
if err != nil {
210+
return err
210211
}
212+
213+
templateFiles[i] = templateFilepath
211214
}
212215

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

pkg/alertmanager/api.go

Lines changed: 135 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/alertspb"
1112
"github.com/cortexproject/cortex/pkg/tenant"
1213
util_log "github.com/cortexproject/cortex/pkg/util/log"
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"`
@@ -148,28 +156,52 @@ func validateUserConfig(logger log.Logger, cfg alertspb.AlertConfigDesc) error {
148156
return err
149157
}
150158

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+
151178
// Create templates on disk in a temporary directory.
152179
// Note: This means the validation will succeed if we can write to tmp but
153180
// not to configured data dir, and on the flipside, it'll fail if we can't write
154181
// to tmpDir. Ignoring both cases for now as they're ultra rare but will revisit if
155182
// we see this in the wild.
156-
tmpDir, err := ioutil.TempDir("", "validate-config")
183+
userTmpDir, err := ioutil.TempDir("", "validate-config-"+cfg.User)
157184
if err != nil {
158185
return err
159186
}
160-
defer os.RemoveAll(tmpDir)
187+
defer os.RemoveAll(userTmpDir)
161188

162189
for _, tmpl := range cfg.Templates {
163-
_, err := createTemplateFile(tmpDir, cfg.User, tmpl.Filename, tmpl.Body)
190+
templateFilepath, err := safeTemplateFilepath(userTmpDir, tmpl.Filename)
164191
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)
167199
}
168200
}
169201

170202
templateFiles := make([]string, len(amCfg.Templates))
171203
for i, t := range amCfg.Templates {
172-
templateFiles[i] = filepath.Join(tmpDir, "templates", cfg.User, t)
204+
templateFiles[i] = filepath.Join(userTmpDir, t)
173205
}
174206

175207
_, err = template.FromGlobs(templateFiles...)
@@ -184,3 +216,100 @@ func validateUserConfig(logger log.Logger, cfg alertspb.AlertConfigDesc) error {
184216

185217
return nil
186218
}
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

Comments
 (0)