Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
* [BUGFIX] Alertmanager: Fix panic in `validateAlertmanagerConfig` when receiver config traversal encounters nil interface values. #7751
* [BUGFIX] Parquet Converter: Fix `auto_forget_delay` having no effect. The ring lifecycler was created without the auto-forget delegate, so unhealthy instances were never automatically removed from the ring. #7752
* [BUGFIX] Alertmanager: Reject the global `mattermost_webhook_url_file` setting in per-tenant configs, consistent with every other global `*_file` setting. #7768
* [BUGFIX] Alertmanager: Tighten per-tenant config validation to reject additional file-based settings. #7767

## 1.21.1 2026-06-04

Expand Down
27 changes: 27 additions & 0 deletions pkg/alertmanager/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ var (
errOAuth2CertificateKeyFileNotAllowed = errors.New("setting OAuth2 client_certificate_key_file is not allowed")
errOAuth2SecretFileNotAllowed = errors.New("setting OAuth2 client_secret_file is not allowed")
errTLSFileNotAllowed = errors.New("setting TLS ca_file, cert_file and key_file is not allowed")
errHTTPHeadersFilesNotAllowed = errors.New("setting http_headers files is not allowed")
errSlackAPIURLFileNotAllowed = errors.New("setting Slack api_url_file and global slack_api_url_file is not allowed")
errSlackAppTokenFileNotAllowed = errors.New("setting Slack slack_app_token_file and global slack_app_token_file is not allowed")
errVictorOpsAPIKeyFileNotAllowed = errors.New("setting VictorOps api_key_file and global victorops_api_key_file is not allowed")
Expand Down Expand Up @@ -475,9 +476,35 @@ func validateReceiverHTTPConfig(cfg commoncfg.HTTPClientConfig) error {
if cfg.OAuth2 != nil && cfg.OAuth2.ClientSecretFile != "" {
return errOAuth2SecretFileNotAllowed
}
if err := validateReceiverHTTPHeaders(cfg.HTTPHeaders); err != nil {
return err
}
return validateReceiverTLSConfig(cfg.TLSConfig)
}

// validateReceiverHTTPHeaders validates the configured HTTP headers and returns an error
// if any of them sources its value from a file on the Alertmanager host.
//
// commoncfg.Header.Files is a list of paths that headersRoundTripper.RoundTrip os.ReadFile()s
// at notification time, injecting the contents into an outbound request whose URL the tenant
// also controls. That is the same "tenant config reads a host file" primitive the rest of the
// *_file denylist in this file exists to block, originally added for CVE-2021-31232 (#4129)
// and extended per-receiver for CVE-2022-23536, so it has to be blocked here too.
//
// Only Files is rejected. Values and Secrets are literals supplied inline by the tenant; they
// read nothing from the host and remain allowed, so ordinary header use keeps working.
func validateReceiverHTTPHeaders(headers *commoncfg.Headers) error {
if headers == nil {
return nil
}
for _, header := range headers.Headers {
if len(header.Files) > 0 {
return errHTTPHeadersFilesNotAllowed
}
}
return nil
}

// validateReceiverTLSConfig validates the TLS config and returns an error if it contains
// settings not allowed by Cortex.
func validateReceiverTLSConfig(cfg commoncfg.TLSConfig) error {
Expand Down
131 changes: 131 additions & 0 deletions pkg/alertmanager/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,68 @@ alertmanager_config: |
`,
err: errors.Wrap(errTLSFileNotAllowed, "error validating Alertmanager config"),
},
{
name: "Should return error if receiver's http_headers files is set",
cfg: `
alertmanager_config: |
receivers:
- name: default-receiver
webhook_configs:
- url: http://localhost
http_config:
http_headers:
X-Canary:
files:
- /var/run/secrets/kubernetes.io/serviceaccount/token

route:
receiver: 'default-receiver'
`,
err: errors.Wrap(errHTTPHeadersFilesNotAllowed, "error validating Alertmanager config"),
},
{
name: "Should return error if global http_headers files is set",
cfg: `
alertmanager_config: |
global:
http_config:
http_headers:
X-Canary:
files:
- /secrets

receivers:
- name: default-receiver
webhook_configs:
- url: http://localhost

route:
receiver: 'default-receiver'
`,
err: errors.Wrap(errHTTPHeadersFilesNotAllowed, "error validating Alertmanager config"),
},
{
name: "Should pass if receiver's http_headers only uses values and secrets",
cfg: `
alertmanager_config: |
receivers:
- name: default-receiver
webhook_configs:
- url: http://localhost
http_config:
http_headers:
X-Canary:
values:
- canary
X-Token:
secrets:
- sekret

route:
receiver: 'default-receiver'
`,
err: nil,
},
{
name: "Should return error if global opsgenie_api_key_file is set",
cfg: `
Expand Down Expand Up @@ -1376,6 +1438,75 @@ func TestValidateAlertmanagerConfig(t *testing.T) {
},
expected: errTLSFileNotAllowed,
},
"*HTTPClientConfig with http_headers files": {
input: &commoncfg.HTTPClientConfig{
HTTPHeaders: &commoncfg.Headers{
Headers: map[string]commoncfg.Header{
"X-Canary": {Files: []string{"/secrets"}},
},
},
},
expected: errHTTPHeadersFilesNotAllowed,
},
"struct containing *HTTPClientConfig with http_headers files as direct child": {
input: config.GlobalConfig{
HTTPConfig: &commoncfg.HTTPClientConfig{
HTTPHeaders: &commoncfg.Headers{
Headers: map[string]commoncfg.Header{
"X-Canary": {Files: []string{"/secrets"}},
},
},
},
},
expected: errHTTPHeadersFilesNotAllowed,
},
"struct containing *HTTPClientConfig with http_headers files as nested child within a slice": {
input: config.Config{
Receivers: []config.Receiver{{
Name: "test",
WebhookConfigs: []*webhook.WebhookConfig{{
HTTPConfig: &commoncfg.HTTPClientConfig{
HTTPHeaders: &commoncfg.Headers{
Headers: map[string]commoncfg.Header{
"X-Canary": {Files: []string{"/secrets"}},
},
},
},
}}},
},
},
expected: errHTTPHeadersFilesNotAllowed,
},
"*HTTPClientConfig with http_headers values only": {
input: &commoncfg.HTTPClientConfig{
HTTPHeaders: &commoncfg.Headers{
Headers: map[string]commoncfg.Header{
"X-Canary": {Values: []string{"value"}},
},
},
},
expected: nil,
},
"*HTTPClientConfig with http_headers secrets only": {
input: &commoncfg.HTTPClientConfig{
HTTPHeaders: &commoncfg.Headers{
Headers: map[string]commoncfg.Header{
"X-Canary": {Secrets: []commoncfg.Secret{"secret"}},
},
},
},
expected: nil,
},
"*HTTPClientConfig with http_headers empty files": {
input: &commoncfg.HTTPClientConfig{
HTTPHeaders: &commoncfg.Headers{
Headers: map[string]commoncfg.Header{
"X-Canary": {Values: []string{"value"}, Files: []string{}},
},
},
},
expected: nil,
},
"GlobalConfig with mattermost_webhook_url_file": {
input: config.GlobalConfig{
MattermostWebhookURLFile: "/secrets",
Expand Down