mirror of
https://github.com/prometheus/alertmanager.git
synced 2026-02-05 15:45:34 +01:00
feat: add config directive to pass wechat api secret via file (#4734)
Ref: #2498 Signed-off-by: Christoph Maser <christoph.maser+github@gmail.com>
This commit is contained in:
@@ -499,6 +499,10 @@ func (c *Config) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
return errors.New("at most one of rocketchat_token_id & rocketchat_token_id_file must be configured")
|
||||
}
|
||||
|
||||
if c.Global.WeChatAPISecret != "" && len(c.Global.WeChatAPISecretFile) > 0 {
|
||||
return errors.New("at most one of wechat_api_secret & wechat_api_secret_file must be configured")
|
||||
}
|
||||
|
||||
names := map[string]struct{}{}
|
||||
|
||||
for _, rcv := range c.Receivers {
|
||||
@@ -637,11 +641,12 @@ func (c *Config) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
wcc.APIURL = c.Global.WeChatAPIURL
|
||||
}
|
||||
|
||||
if wcc.APISecret == "" {
|
||||
if c.Global.WeChatAPISecret == "" {
|
||||
return errors.New("no global Wechat ApiSecret set")
|
||||
if wcc.APISecret == "" && len(wcc.APISecretFile) == 0 {
|
||||
if c.Global.WeChatAPISecret == "" && len(c.Global.WeChatAPISecretFile) == 0 {
|
||||
return errors.New("no global Wechat Api Secret set either inline or in a file")
|
||||
}
|
||||
wcc.APISecret = c.Global.WeChatAPISecret
|
||||
wcc.APISecretFile = c.Global.WeChatAPISecretFile
|
||||
}
|
||||
|
||||
if wcc.CorpID == "" {
|
||||
@@ -992,6 +997,7 @@ type GlobalConfig struct {
|
||||
OpsGenieAPIKeyFile string `yaml:"opsgenie_api_key_file,omitempty" json:"opsgenie_api_key_file,omitempty"`
|
||||
WeChatAPIURL *URL `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"`
|
||||
WeChatAPISecret Secret `yaml:"wechat_api_secret,omitempty" json:"wechat_api_secret,omitempty"`
|
||||
WeChatAPISecretFile string `yaml:"wechat_api_secret_file,omitempty" json:"wechat_api_secret_file,omitempty"`
|
||||
WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"`
|
||||
VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"`
|
||||
VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"`
|
||||
|
||||
@@ -1703,3 +1703,48 @@ func TestInhibitRuleEqual(t *testing.T) {
|
||||
r = c.InhibitRules[0]
|
||||
require.Equal(t, []string{"qux🙂", "corge"}, r.Equal)
|
||||
}
|
||||
|
||||
func TestWechatNoAPIURL(t *testing.T) {
|
||||
_, err := LoadFile("testdata/conf.wechat-no-api-secret.yml")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.wechat-no-api-url.yml", err)
|
||||
}
|
||||
if err.Error() != "no global Wechat Api Secret set either inline or in a file" {
|
||||
t.Errorf("Expected: %s\nGot: %s", "no global Wechat Api Secret set either inline or in a file", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWechatBothAPIURLAndFile(t *testing.T) {
|
||||
_, err := LoadFile("testdata/conf.wechat-both-file-and-secret.yml")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.wechat-both-file-and-secret.yml", err)
|
||||
}
|
||||
if err.Error() != "at most one of wechat_api_secret & wechat_api_secret_file must be configured" {
|
||||
t.Errorf("Expected: %s\nGot: %s", "at most one of wechat_api_secret & wechat_api_secret_file must be configured", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWechatGlobalAPISecretFile(t *testing.T) {
|
||||
conf, err := LoadFile("testdata/conf.wechat-default-api-secret-file.yml")
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing %s: %s", "testdata/conf.wechat-default-api-secret-file.yml", err)
|
||||
}
|
||||
|
||||
// no override
|
||||
firstConfig := conf.Receivers[0].WechatConfigs[0]
|
||||
if firstConfig.APISecretFile != "/global_file" || string(firstConfig.APISecret) != "" {
|
||||
t.Fatalf("Invalid Wechat API Secret file: %s\nExpected: %s", firstConfig.APISecretFile, "/global_file")
|
||||
}
|
||||
|
||||
// override the file
|
||||
secondConfig := conf.Receivers[0].WechatConfigs[1]
|
||||
if secondConfig.APISecretFile != "/override_file" || string(secondConfig.APISecret) != "" {
|
||||
t.Fatalf("Invalid Wechat API Secret file: %s\nExpected: %s", secondConfig.APISecretFile, "/override_file")
|
||||
}
|
||||
|
||||
// override the global file with an inline URL
|
||||
thirdConfig := conf.Receivers[0].WechatConfigs[2]
|
||||
if string(thirdConfig.APISecret) != "my_inline_secret" || thirdConfig.APISecretFile != "" {
|
||||
t.Fatalf("Invalid Wechat API Secret: %s\nExpected: %s", string(thirdConfig.APISecret), "my_inline_secret")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,15 +664,16 @@ type WechatConfig struct {
|
||||
|
||||
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
|
||||
|
||||
APISecret Secret `yaml:"api_secret,omitempty" json:"api_secret,omitempty"`
|
||||
CorpID string `yaml:"corp_id,omitempty" json:"corp_id,omitempty"`
|
||||
Message string `yaml:"message,omitempty" json:"message,omitempty"`
|
||||
APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"`
|
||||
ToUser string `yaml:"to_user,omitempty" json:"to_user,omitempty"`
|
||||
ToParty string `yaml:"to_party,omitempty" json:"to_party,omitempty"`
|
||||
ToTag string `yaml:"to_tag,omitempty" json:"to_tag,omitempty"`
|
||||
AgentID string `yaml:"agent_id,omitempty" json:"agent_id,omitempty"`
|
||||
MessageType string `yaml:"message_type,omitempty" json:"message_type,omitempty"`
|
||||
APISecret Secret `yaml:"api_secret,omitempty" json:"api_secret,omitempty"`
|
||||
APISecretFile string `yaml:"api_secret_file,omitempty" json:"api_secret_file,omitempty"`
|
||||
CorpID string `yaml:"corp_id,omitempty" json:"corp_id,omitempty"`
|
||||
Message string `yaml:"message,omitempty" json:"message,omitempty"`
|
||||
APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"`
|
||||
ToUser string `yaml:"to_user,omitempty" json:"to_user,omitempty"`
|
||||
ToParty string `yaml:"to_party,omitempty" json:"to_party,omitempty"`
|
||||
ToTag string `yaml:"to_tag,omitempty" json:"to_tag,omitempty"`
|
||||
AgentID string `yaml:"agent_id,omitempty" json:"agent_id,omitempty"`
|
||||
MessageType string `yaml:"message_type,omitempty" json:"message_type,omitempty"`
|
||||
}
|
||||
|
||||
const wechatValidTypesRe = `^(text|markdown)$`
|
||||
@@ -695,6 +696,10 @@ func (c *WechatConfig) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
return fmt.Errorf("weChat message type %q does not match valid options %s", c.MessageType, wechatValidTypesRe)
|
||||
}
|
||||
|
||||
if c.APISecret != "" && len(c.APISecretFile) > 0 {
|
||||
return errors.New("at most one of api_secret & api_secret_file must be configured")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
10
config/testdata/conf.wechat-both-file-and-secret.yml
vendored
Normal file
10
config/testdata/conf.wechat-both-file-and-secret.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
global:
|
||||
wechat_api_secret: "http://mysecret.example.com/"
|
||||
wechat_api_secret_file: '/global_file'
|
||||
route:
|
||||
receiver: 'wechat-notifications'
|
||||
group_by: [alertname, datacenter, app]
|
||||
receivers:
|
||||
- name: 'wechat-notifications'
|
||||
wechat_configs:
|
||||
- {}
|
||||
15
config/testdata/conf.wechat-default-api-secret-file.yml
vendored
Normal file
15
config/testdata/conf.wechat-default-api-secret-file.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
global:
|
||||
wechat_api_secret_file: '/global_file'
|
||||
wechat_api_corp_id: 'my_corp_id'
|
||||
route:
|
||||
receiver: 'wechat-notifications'
|
||||
group_by: [alertname, datacenter, app]
|
||||
receivers:
|
||||
- name: 'wechat-notifications'
|
||||
wechat_configs:
|
||||
# Use global
|
||||
- {}
|
||||
# Override global with other file
|
||||
- api_secret_file: '/override_file'
|
||||
# Override global with inline API secret
|
||||
- api_secret: 'my_inline_secret'
|
||||
7
config/testdata/conf.wechat-no-api-secret.yml
vendored
Normal file
7
config/testdata/conf.wechat-no-api-secret.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
route:
|
||||
receiver: 'wechat-notifications'
|
||||
group_by: [alertname, datacenter, app]
|
||||
receivers:
|
||||
- name: 'wechat-notifications'
|
||||
wechat_configs:
|
||||
- {}
|
||||
@@ -116,6 +116,7 @@ global:
|
||||
[ rocketchat_token_id_file: <filepath> ]
|
||||
[ wechat_api_url: <string> | default = "https://qyapi.weixin.qq.com/cgi-bin/" ]
|
||||
[ wechat_api_secret: <secret> ]
|
||||
[ wechat_api_secret_file: <string> ]
|
||||
[ wechat_api_corp_id: <string> ]
|
||||
[ telegram_api_url: <string> | default = "https://api.telegram.org" ]
|
||||
[ webex_api_url: <string> | default = "https://webexapis.com/v1/messages" ]
|
||||
@@ -1875,8 +1876,9 @@ API](https://developers.weixin.qq.com/doc/offiaccount/en/Message_Management/Serv
|
||||
# Whether to notify about resolved alerts.
|
||||
[ send_resolved: <boolean> | default = false ]
|
||||
|
||||
# The API key to use when talking to the WeChat API.
|
||||
# The API key to use when talking to the WeChat API. Either api_secret or api_secret_file should be set.
|
||||
[ api_secret: <secret> | default = global.wechat_api_secret ]
|
||||
[ api_secret_file: <string> | default = global.wechat_api_secret_file ]
|
||||
|
||||
# The WeChat API URL.
|
||||
[ api_url: <string> | default = global.wechat_api_url ]
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
@@ -99,7 +101,11 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
|
||||
// Refresh AccessToken over 2 hours
|
||||
if n.accessToken == "" || time.Since(n.accessTokenAt) > 2*time.Hour {
|
||||
parameters := url.Values{}
|
||||
parameters.Add("corpsecret", tmpl(string(n.conf.APISecret)))
|
||||
apiSecret, err := n.getApiSecret()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
parameters.Add("corpsecret", tmpl(apiSecret))
|
||||
parameters.Add("corpid", tmpl(string(n.conf.CorpID)))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("templating error: %w", err)
|
||||
@@ -196,3 +202,14 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
|
||||
|
||||
return false, errors.New(weResp.Error)
|
||||
}
|
||||
|
||||
func (n *Notifier) getApiSecret() (string, error) {
|
||||
if len(n.conf.APISecretFile) > 0 {
|
||||
content, err := os.ReadFile(n.conf.APISecretFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(content)), nil
|
||||
}
|
||||
return string(n.conf.APISecret), nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package wechat
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
@@ -90,3 +91,34 @@ func TestWechatMessageTypeSelector(t *testing.T) {
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, secret, token)
|
||||
}
|
||||
|
||||
func TestGetApiSecretFromSecret(t *testing.T) {
|
||||
n := &Notifier{conf: &config.WechatConfig{APISecret: config.Secret("shhh")}}
|
||||
s, err := n.getApiSecret()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "shhh", s)
|
||||
}
|
||||
|
||||
func TestGetApiSecretFromFile(t *testing.T) {
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "wechat-secret-*")
|
||||
require.NoError(t, err)
|
||||
secretContent := "file-secret\n"
|
||||
_, err = tmpFile.WriteString(secretContent)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, tmpFile.Close())
|
||||
|
||||
n := &Notifier{conf: &config.WechatConfig{APISecretFile: tmpFile.Name()}}
|
||||
s, err := n.getApiSecret()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "file-secret", s)
|
||||
}
|
||||
|
||||
func TestGetApiSecretFromMissingFile(t *testing.T) {
|
||||
n := &Notifier{conf: &config.WechatConfig{APISecretFile: "/non/existent/wechat-secret.txt"}}
|
||||
s, err := n.getApiSecret()
|
||||
var pathErr *os.PathError
|
||||
require.ErrorAs(t, err, &pathErr)
|
||||
require.Equal(t, "/non/existent/wechat-secret.txt", pathErr.Path)
|
||||
require.ErrorIs(t, err, os.ErrNotExist)
|
||||
require.Empty(t, s)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user