Alerting: Hide extra Prometheus configuration secrets (#106916)

This commit is contained in:
Alexander Akhmetov
2025-06-18 17:01:19 +02:00
committed by GitHub
parent f6c4191a01
commit 4b46da9214
10 changed files with 400 additions and 58 deletions
@@ -605,9 +605,18 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetAlertmanagerConfig(c *
return response.Error(http.StatusNotFound, "Alertmanager configuration not found", nil)
}
respBody := apimodels.AlertmanagerUserConfig{
AlertmanagerConfig: extraCfg.AlertmanagerConfig,
TemplateFiles: extraCfg.TemplateFiles,
// Parse the configuration into our Gettable struct which will automatically
// sanitize secrets and exclude global settings when marshaled back to YAML.
var prometheusConfig amconfig.Config
if err := yaml.Unmarshal([]byte(extraCfg.AlertmanagerConfig), &prometheusConfig); err != nil {
return response.Error(http.StatusBadRequest, "Invalid Alertmanager configuration format", err)
}
respBody := apimodels.GettableAlertmanagerUserConfig{
AlertmanagerConfig: apimodels.GettableAlertmanagerConfig{
Config: prometheusConfig,
},
TemplateFiles: extraCfg.TemplateFiles,
}
resp := response.YAML(http.StatusOK, respBody)
@@ -1622,6 +1622,7 @@ func TestRouteConvertPrometheusGetAlertmanagerConfig(t *testing.T) {
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
// Create a config with secrets to check that they will be hided in the response.
expectedConfig := apimodels.GettableUserConfig{
ExtraConfigs: []apimodels.ExtraConfiguration{
{
@@ -1630,9 +1631,14 @@ func TestRouteConvertPrometheusGetAlertmanagerConfig(t *testing.T) {
"test.tmpl": "{{ define \"test\" }}Hello{{ end }}",
},
AlertmanagerConfig: `route:
receiver: default
receiver: webhook
receivers:
- name: default`,
- name: webhook
webhook_configs:
- url: "http://localhost/webhook"
http_config:
bearer_token: "some-token"
`,
},
},
}
@@ -1645,11 +1651,25 @@ receivers:
require.Equal(t, http.StatusOK, response.Status())
expectedResponse := `alertmanager_config: |-
expectedResponse := `alertmanager_config:
route:
receiver: default
receiver: webhook
continue: false
receivers:
- name: default
- name: webhook
webhook_configs:
- url: "<secret>"
url_file: ""
http_config:
authorization:
type: "Bearer"
credentials: "<secret>"
enable_http2: true
follow_redirects: true
send_resolved: true
max_alerts: 0
timeout: "0s"
templates: []
template_files:
test.tmpl: '{{ define "test" }}Hello{{ end }}'`
+63 -1
View File
@@ -742,12 +742,18 @@
"type": "array"
},
"mute_time_intervals": {
"description": "MuteTimeIntervals is deprecated and will be removed before Alertmanager 1.0.",
"description": "Deprecated. Remove before v1.0 release.",
"items": {
"$ref": "#/definitions/MuteTimeInterval"
},
"type": "array"
},
"receivers": {
"items": {
"$ref": "#/definitions/Receiver"
},
"type": "array"
},
"route": {
"$ref": "#/definitions/Route"
},
@@ -1438,6 +1444,62 @@
"title": "Frames is a slice of Frame pointers.",
"type": "array"
},
"GettableAlertmanagerConfig": {
"properties": {
"global": {
"$ref": "#/definitions/GlobalConfig"
},
"inhibit_rules": {
"items": {
"$ref": "#/definitions/InhibitRule"
},
"type": "array"
},
"mute_time_intervals": {
"description": "Deprecated. Remove before v1.0 release.",
"items": {
"$ref": "#/definitions/MuteTimeInterval"
},
"type": "array"
},
"receivers": {
"items": {
"$ref": "#/definitions/Receiver"
},
"type": "array"
},
"route": {
"$ref": "#/definitions/Route"
},
"templates": {
"items": {
"type": "string"
},
"type": "array"
},
"time_intervals": {
"items": {
"$ref": "#/definitions/TimeInterval"
},
"type": "array"
}
},
"type": "object"
},
"GettableAlertmanagerUserConfig": {
"properties": {
"alertmanager_config": {
"$ref": "#/definitions/GettableAlertmanagerConfig"
},
"template_files": {
"additionalProperties": {
"type": "string"
},
"type": "object"
}
},
"type": "object"
},
"GettableAlertmanagers": {
"properties": {
"data": {
@@ -1,6 +1,9 @@
package definitions
import (
"encoding/json"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/common/model"
)
@@ -350,3 +353,30 @@ type AlertmanagerUserConfig struct {
AlertmanagerConfig string `yaml:"alertmanager_config" json:"alertmanager_config"`
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
}
// GettableAlertmanagerUserConfig is like AlertmanagerUserConfig but uses the normal config structure
// that automatically sanitizes secrets when marshaled to YAML/JSON.
// swagger:model
type GettableAlertmanagerUserConfig struct {
AlertmanagerConfig GettableAlertmanagerConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
}
type GettableAlertmanagerConfig struct {
config.Config `yaml:",inline" json:",inline"`
}
func (c GettableAlertmanagerConfig) MarshalYAML() (any, error) {
type base config.Config
cfg := base(c.Config)
cfg.Global = nil // not used in Grafana
return cfg, nil
}
func (c GettableAlertmanagerConfig) MarshalJSON() ([]byte, error) {
type base config.Config
cfg := base(c.Config)
cfg.Global = nil // not used in Grafana
return json.Marshal(cfg)
}
+57 -7
View File
@@ -1438,6 +1438,62 @@
"title": "Frames is a slice of Frame pointers.",
"type": "array"
},
"GettableAlertmanagerConfig": {
"properties": {
"global": {
"$ref": "#/definitions/GlobalConfig"
},
"inhibit_rules": {
"items": {
"$ref": "#/definitions/InhibitRule"
},
"type": "array"
},
"mute_time_intervals": {
"description": "Deprecated. Remove before v1.0 release.",
"items": {
"$ref": "#/definitions/MuteTimeInterval"
},
"type": "array"
},
"receivers": {
"items": {
"$ref": "#/definitions/Receiver"
},
"type": "array"
},
"route": {
"$ref": "#/definitions/Route"
},
"templates": {
"items": {
"type": "string"
},
"type": "array"
},
"time_intervals": {
"items": {
"$ref": "#/definitions/TimeInterval"
},
"type": "array"
}
},
"type": "object"
},
"GettableAlertmanagerUserConfig": {
"properties": {
"alertmanager_config": {
"$ref": "#/definitions/GettableAlertmanagerConfig"
},
"template_files": {
"additionalProperties": {
"type": "string"
},
"type": "object"
}
},
"type": "object"
},
"GettableAlertmanagers": {
"properties": {
"data": {
@@ -3709,7 +3765,6 @@
"type": "object"
},
"Route": {
"description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.",
"properties": {
"active_time_intervals": {
"items": {
@@ -3751,12 +3806,6 @@
},
"type": "array"
},
"object_matchers": {
"$ref": "#/definitions/ObjectMatchers"
},
"provenance": {
"$ref": "#/definitions/Provenance"
},
"receiver": {
"type": "string"
},
@@ -3770,6 +3819,7 @@
"type": "array"
}
},
"title": "A Route is a node that contains definitions of how to handle alerts.",
"type": "object"
},
"RouteExport": {
+57 -7
View File
@@ -5727,6 +5727,62 @@
"$ref": "#/definitions/Frame"
}
},
"GettableAlertmanagerConfig": {
"type": "object",
"properties": {
"global": {
"$ref": "#/definitions/GlobalConfig"
},
"inhibit_rules": {
"type": "array",
"items": {
"$ref": "#/definitions/InhibitRule"
}
},
"mute_time_intervals": {
"description": "Deprecated. Remove before v1.0 release.",
"type": "array",
"items": {
"$ref": "#/definitions/MuteTimeInterval"
}
},
"receivers": {
"type": "array",
"items": {
"$ref": "#/definitions/Receiver"
}
},
"route": {
"$ref": "#/definitions/Route"
},
"templates": {
"type": "array",
"items": {
"type": "string"
}
},
"time_intervals": {
"type": "array",
"items": {
"$ref": "#/definitions/TimeInterval"
}
}
}
},
"GettableAlertmanagerUserConfig": {
"type": "object",
"properties": {
"alertmanager_config": {
"$ref": "#/definitions/GettableAlertmanagerConfig"
},
"template_files": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"GettableAlertmanagers": {
"type": "object",
"properties": {
@@ -7999,8 +8055,8 @@
}
},
"Route": {
"description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.",
"type": "object",
"title": "A Route is a node that contains definitions of how to handle alerts.",
"properties": {
"active_time_intervals": {
"type": "array",
@@ -8042,12 +8098,6 @@
"type": "string"
}
},
"object_matchers": {
"$ref": "#/definitions/ObjectMatchers"
},
"provenance": {
"$ref": "#/definitions/Provenance"
},
"receiver": {
"type": "string"
},
@@ -11,19 +11,15 @@ import (
)
const testAlertmanagerConfigYAML = `
global:
smtp_smarthost: localhost:587
smtp_from: alertmanager@example.org
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: web.hook
receiver: webhook
receivers:
- name: web.hook
- name: webhook
webhook_configs:
- url: 'http://127.0.0.1:5001/'
@@ -53,8 +49,17 @@ func TestIntegrationConvertPrometheusAlertmanagerEndpoints(t *testing.T) {
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
cleanup := func(identifier string) {
deleteHeaders := map[string]string{
"X-Grafana-Alerting-Config-Identifier": identifier,
}
_, status, _ := apiClient.RawConvertPrometheusDeleteAlertmanagerConfig(t, deleteHeaders)
require.Equal(t, http.StatusAccepted, status)
}
t.Run("create and get alertmanager configuration", func(t *testing.T) {
identifier := "test-create-get-config"
defer cleanup(identifier)
mergeMatchers := "environment=production,team=backend"
headers := map[string]string{
@@ -81,19 +86,15 @@ func TestIntegrationConvertPrometheusAlertmanagerEndpoints(t *testing.T) {
require.Contains(t, retrievedConfig.TemplateFiles, "test.tmpl")
require.Equal(t, `{{ define "test.template" }}Test template{{ end }}`, retrievedConfig.TemplateFiles["test.tmpl"])
// Verify the configuration contains expected content
require.Contains(t, retrievedConfig.AlertmanagerConfig, "smtp_smarthost: localhost:587")
require.Contains(t, retrievedConfig.AlertmanagerConfig, "web.hook")
// Cleanup
deleteHeaders := map[string]string{
"X-Grafana-Alerting-Config-Identifier": identifier,
}
apiClient.ConvertPrometheusDeleteAlertmanagerConfig(t, deleteHeaders)
require.Len(t, retrievedConfig.AlertmanagerConfig.Receivers, 1)
require.Equal(t, "webhook", retrievedConfig.AlertmanagerConfig.Receivers[0].Name)
require.Len(t, retrievedConfig.AlertmanagerConfig.Receivers[0].WebhookConfigs, 1)
require.Equal(t, "", retrievedConfig.AlertmanagerConfig.Receivers[0].WebhookConfigs[0].URL.String())
})
t.Run("delete alertmanager configuration", func(t *testing.T) {
identifier := "test-delete-config"
defer cleanup(identifier)
mergeMatchers := "environment=production,team=backend"
headers := map[string]string{
@@ -194,6 +195,7 @@ func TestIntegrationConvertPrometheusAlertmanagerEndpoints(t *testing.T) {
t.Run("update existing configuration", func(t *testing.T) {
identifier := "test-update-config"
defer cleanup(identifier)
headers := map[string]string{
"Content-Type": "application/yaml",
@@ -213,19 +215,15 @@ func TestIntegrationConvertPrometheusAlertmanagerEndpoints(t *testing.T) {
// Update the same configuration with new content
updatedConfigYAML := `
global:
smtp_smarthost: localhost:25
smtp_from: updated@example.org
route:
group_by: ['service']
group_wait: 5s
group_interval: 5s
repeat_interval: 30m
receiver: updated.hook
receiver: updated-webhook
receivers:
- name: updated.hook
- name: updated-webhook
webhook_configs:
- url: 'http://127.0.0.1:8080/updated'
`
@@ -245,21 +243,20 @@ receivers:
"X-Grafana-Alerting-Config-Identifier": identifier,
}
retrievedConfig := apiClient.ConvertPrometheusGetAlertmanagerConfig(t, getHeaders)
require.NotEmpty(t, retrievedConfig.AlertmanagerConfig)
require.Contains(t, retrievedConfig.AlertmanagerConfig, "updated@example.org")
require.Contains(t, retrievedConfig.AlertmanagerConfig, "updated.hook")
require.Contains(t, retrievedConfig.TemplateFiles, "updated.tmpl")
require.Equal(t, `{{ define "updated.template" }}Updated Config{{ end }}`, retrievedConfig.TemplateFiles["updated.tmpl"])
deleteHeaders := map[string]string{
"X-Grafana-Alerting-Config-Identifier": identifier,
}
apiClient.ConvertPrometheusDeleteAlertmanagerConfig(t, deleteHeaders)
require.NotEmpty(t, retrievedConfig.AlertmanagerConfig)
require.Len(t, retrievedConfig.AlertmanagerConfig.Receivers, 1)
require.Equal(t, "updated-webhook", retrievedConfig.AlertmanagerConfig.Receivers[0].Name)
require.Len(t, retrievedConfig.AlertmanagerConfig.Receivers[0].WebhookConfigs, 1)
require.Equal(t, "", retrievedConfig.AlertmanagerConfig.Receivers[0].WebhookConfigs[0].URL.String())
require.Equal(t, `{{ define "updated.template" }}Updated Config{{ end }}`, retrievedConfig.TemplateFiles["updated.tmpl"])
})
t.Run("multiple extra configurations conflict", func(t *testing.T) {
firstIdentifier := "first-config"
secondIdentifier := "second-config"
defer cleanup(firstIdentifier)
// Create first configuration
firstHeaders := map[string]string{
+3 -3
View File
@@ -1300,7 +1300,7 @@ func (a apiClient) RawConvertPrometheusPostAlertmanagerConfig(t *testing.T, amCf
return sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
}
func (a apiClient) ConvertPrometheusGetAlertmanagerConfig(t *testing.T, headers map[string]string) apimodels.AlertmanagerUserConfig {
func (a apiClient) ConvertPrometheusGetAlertmanagerConfig(t *testing.T, headers map[string]string) apimodels.GettableAlertmanagerUserConfig {
t.Helper()
config, status, raw := a.RawConvertPrometheusGetAlertmanagerConfig(t, headers)
@@ -1309,7 +1309,7 @@ func (a apiClient) ConvertPrometheusGetAlertmanagerConfig(t *testing.T, headers
return config
}
func (a apiClient) RawConvertPrometheusGetAlertmanagerConfig(t *testing.T, headers map[string]string) (apimodels.AlertmanagerUserConfig, int, string) {
func (a apiClient) RawConvertPrometheusGetAlertmanagerConfig(t *testing.T, headers map[string]string) (apimodels.GettableAlertmanagerUserConfig, int, string) {
t.Helper()
path := "%s/api/convert/api/v1/alerts"
@@ -1321,7 +1321,7 @@ func (a apiClient) RawConvertPrometheusGetAlertmanagerConfig(t *testing.T, heade
req.Header.Set(key, value)
}
config, status, raw := sendRequestYAML[apimodels.AlertmanagerUserConfig](t, req, http.StatusOK)
config, status, raw := sendRequestYAML[apimodels.GettableAlertmanagerUserConfig](t, req, http.StatusOK)
return config, status, raw
}
+63 -1
View File
@@ -13895,12 +13895,18 @@
}
},
"mute_time_intervals": {
"description": "MuteTimeIntervals is deprecated and will be removed before Alertmanager 1.0.",
"description": "Deprecated. Remove before v1.0 release.",
"type": "array",
"items": {
"$ref": "#/definitions/MuteTimeInterval"
}
},
"receivers": {
"type": "array",
"items": {
"$ref": "#/definitions/Receiver"
}
},
"route": {
"$ref": "#/definitions/Route"
},
@@ -15887,6 +15893,62 @@
}
}
},
"GettableAlertmanagerConfig": {
"type": "object",
"properties": {
"global": {
"$ref": "#/definitions/GlobalConfig"
},
"inhibit_rules": {
"type": "array",
"items": {
"$ref": "#/definitions/InhibitRule"
}
},
"mute_time_intervals": {
"description": "Deprecated. Remove before v1.0 release.",
"type": "array",
"items": {
"$ref": "#/definitions/MuteTimeInterval"
}
},
"receivers": {
"type": "array",
"items": {
"$ref": "#/definitions/Receiver"
}
},
"route": {
"$ref": "#/definitions/Route"
},
"templates": {
"type": "array",
"items": {
"type": "string"
}
},
"time_intervals": {
"type": "array",
"items": {
"$ref": "#/definitions/TimeInterval"
}
}
}
},
"GettableAlertmanagerUserConfig": {
"type": "object",
"properties": {
"alertmanager_config": {
"$ref": "#/definitions/GettableAlertmanagerConfig"
},
"template_files": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"GettableAlertmanagers": {
"type": "object",
"properties": {
+63 -1
View File
@@ -3944,12 +3944,18 @@
"type": "array"
},
"mute_time_intervals": {
"description": "MuteTimeIntervals is deprecated and will be removed before Alertmanager 1.0.",
"description": "Deprecated. Remove before v1.0 release.",
"items": {
"$ref": "#/components/schemas/MuteTimeInterval"
},
"type": "array"
},
"receivers": {
"items": {
"$ref": "#/components/schemas/Receiver"
},
"type": "array"
},
"route": {
"$ref": "#/components/schemas/Route"
},
@@ -5938,6 +5944,62 @@
},
"type": "object"
},
"GettableAlertmanagerConfig": {
"properties": {
"global": {
"$ref": "#/components/schemas/GlobalConfig"
},
"inhibit_rules": {
"items": {
"$ref": "#/components/schemas/InhibitRule"
},
"type": "array"
},
"mute_time_intervals": {
"description": "Deprecated. Remove before v1.0 release.",
"items": {
"$ref": "#/components/schemas/MuteTimeInterval"
},
"type": "array"
},
"receivers": {
"items": {
"$ref": "#/components/schemas/Receiver"
},
"type": "array"
},
"route": {
"$ref": "#/components/schemas/Route"
},
"templates": {
"items": {
"type": "string"
},
"type": "array"
},
"time_intervals": {
"items": {
"$ref": "#/components/schemas/TimeInterval"
},
"type": "array"
}
},
"type": "object"
},
"GettableAlertmanagerUserConfig": {
"properties": {
"alertmanager_config": {
"$ref": "#/components/schemas/GettableAlertmanagerConfig"
},
"template_files": {
"additionalProperties": {
"type": "string"
},
"type": "object"
}
},
"type": "object"
},
"GettableAlertmanagers": {
"properties": {
"data": {