Skip to content
Closed
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
172 changes: 172 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,137 @@ import (
"github.com/prometheus/alertmanager/tracing"
)

<<<<<<< HEAD
=======
const secretToken = "<secret>"

var secretTokenJSON string

func init() {
b, err := json.Marshal(secretToken)
if err != nil {
panic(err)
}
secretTokenJSON = string(b)
}

// URL is a custom type that represents an HTTP or HTTPS URL and allows validation at configuration load time.
type URL struct {
*url.URL
}

// Copy makes a deep-copy of the struct.
func (u *URL) Copy() *URL {
v := *u.URL
return &URL{&v}
}

// MarshalYAML implements the yaml.Marshaler interface for URL.
func (u URL) MarshalYAML() (any, error) {
if u.URL != nil {
return u.String(), nil
}
return nil, nil
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for URL.
func (u *URL) UnmarshalYAML(unmarshal func(any) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
urlp, err := parseURL(s)
if err != nil {
return err
}
u.URL = urlp.URL
return nil
}

// MarshalJSON implements the json.Marshaler interface for URL.
func (u URL) MarshalJSON() ([]byte, error) {
if u.URL != nil {
return json.Marshal(u.String())
}
return []byte("null"), nil
}

// UnmarshalJSON implements the json.Marshaler interface for URL.
func (u *URL) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
urlp, err := parseURL(s)
if err != nil {
return err
}
u.URL = urlp.URL
return nil
}

// SecretURL is a URL that must not be revealed on marshaling.
type SecretURL URL

// MarshalYAML implements the yaml.Marshaler interface for SecretURL.
func (s SecretURL) MarshalYAML() (any, error) {
if s.URL != nil {
if commoncfg.MarshalSecretValue {
return s.String(), nil
}
return secretToken, nil
}
return nil, nil
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for SecretURL.
func (s *SecretURL) UnmarshalYAML(unmarshal func(any) error) error {
var str string
if err := unmarshal(&str); err != nil {
return err
}
// In order to deserialize a previously serialized configuration (eg from
// the Alertmanager API with amtool), `<secret>` needs to be treated
// specially, as it isn't a valid URL.
if str == secretToken {
s.URL = &url.URL{}
return nil
}
return unmarshal((*URL)(s))
}

// MarshalJSON implements the json.Marshaler interface for SecretURL.
func (s SecretURL) MarshalJSON() ([]byte, error) {
if s.URL == nil {
return json.Marshal("")
}
if commoncfg.MarshalSecretValue {
return json.Marshal(s.String())
}
return json.Marshal(secretToken)
}

// UnmarshalJSON implements the json.Marshaler interface for SecretURL.
func (s *SecretURL) UnmarshalJSON(data []byte) error {
// In order to deserialize a previously serialized configuration (eg from
// the Alertmanager API with amtool), `<secret>` needs to be treated
// specially, as it isn't a valid URL.
if string(data) == secretToken || string(data) == secretTokenJSON {
s.URL = &url.URL{}
return nil
}
// Redact the secret URL in case of errors
if err := json.Unmarshal(data, (*URL)(s)); err != nil {
if commoncfg.MarshalSecretValue {
return err
}
return errors.New(strings.ReplaceAll(err.Error(), string(data), "[REDACTED]"))
}

return nil
}

>>>>>>> 87c1d1c3 (refactor: replace config.Secret with commoncfg.Secret)
// containsTemplating checks if the string contains template syntax.
func containsTemplating(s string) (bool, error) {
if !strings.Contains(s, "{{") {
Expand Down Expand Up @@ -801,6 +932,7 @@ type GlobalConfig struct {

HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`

<<<<<<< HEAD
JiraAPIURL *amcommoncfg.URL `yaml:"jira_api_url,omitempty" json:"jira_api_url,omitempty"`
SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"`
SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"`
Expand Down Expand Up @@ -841,6 +973,46 @@ type GlobalConfig struct {
RocketchatTokenIDFile string `yaml:"rocketchat_token_id_file,omitempty" json:"rocketchat_token_id_file,omitempty"`
MattermostWebhookURL *amcommoncfg.SecretURL `yaml:"mattermost_webhook_url,omitempty" json:"mattermost_webhook_url,omitempty"`
MattermostWebhookURLFile string `yaml:"mattermost_webhook_url_file,omitempty" json:"mattermost_webhook_url_file,omitempty"`
=======
JiraAPIURL *URL `yaml:"jira_api_url,omitempty" json:"jira_api_url,omitempty"`
SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"`
SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"`
SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,omitempty"`
SMTPAuthUsername string `yaml:"smtp_auth_username,omitempty" json:"smtp_auth_username,omitempty"`
SMTPAuthPassword commoncfg.Secret `yaml:"smtp_auth_password,omitempty" json:"smtp_auth_password,omitempty"`
SMTPAuthPasswordFile string `yaml:"smtp_auth_password_file,omitempty" json:"smtp_auth_password_file,omitempty"`
SMTPAuthSecret commoncfg.Secret `yaml:"smtp_auth_secret,omitempty" json:"smtp_auth_secret,omitempty"`
SMTPAuthSecretFile string `yaml:"smtp_auth_secret_file,omitempty" json:"smtp_auth_secret_file,omitempty"`
SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,omitempty"`
SMTPRequireTLS bool `yaml:"smtp_require_tls" json:"smtp_require_tls,omitempty"`
SMTPTLSConfig *commoncfg.TLSConfig `yaml:"smtp_tls_config,omitempty" json:"smtp_tls_config,omitempty"`
SMTPForceImplicitTLS *bool `yaml:"smtp_force_implicit_tls,omitempty" json:"smtp_force_implicit_tls,omitempty"`
SlackAPIURL *SecretURL `yaml:"slack_api_url,omitempty" json:"slack_api_url,omitempty"`
SlackAPIURLFile string `yaml:"slack_api_url_file,omitempty" json:"slack_api_url_file,omitempty"`
SlackAppToken commoncfg.Secret `yaml:"slack_app_token,omitempty" json:"slack_app_token,omitempty"`
SlackAppTokenFile string `yaml:"slack_app_token_file,omitempty" json:"slack_app_token_file,omitempty"`
SlackAppURL *URL `yaml:"slack_app_url,omitempty" json:"slack_app_url,omitempty"`
PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_url,omitempty"`
OpsGenieAPIURL *URL `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"`
OpsGenieAPIKey commoncfg.Secret `yaml:"opsgenie_api_key,omitempty" json:"opsgenie_api_key,omitempty"`
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 commoncfg.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 commoncfg.Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"`
VictorOpsAPIKeyFile string `yaml:"victorops_api_key_file,omitempty" json:"victorops_api_key_file,omitempty"`
TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_api_url,omitempty"`
TelegramBotToken commoncfg.Secret `yaml:"telegram_bot_token,omitempty" json:"telegram_bot_token,omitempty"`
TelegramBotTokenFile string `yaml:"telegram_bot_token_file,omitempty" json:"telegram_bot_token_file,omitempty"`
WebexAPIURL *URL `yaml:"webex_api_url,omitempty" json:"webex_api_url,omitempty"`
RocketchatAPIURL *URL `yaml:"rocketchat_api_url,omitempty" json:"rocketchat_api_url,omitempty"`
RocketchatToken *commoncfg.Secret `yaml:"rocketchat_token,omitempty" json:"rocketchat_token,omitempty"`
RocketchatTokenFile string `yaml:"rocketchat_token_file,omitempty" json:"rocketchat_token_file,omitempty"`
RocketchatTokenID *commoncfg.Secret `yaml:"rocketchat_token_id,omitempty" json:"rocketchat_token_id,omitempty"`
RocketchatTokenIDFile string `yaml:"rocketchat_token_id_file,omitempty" json:"rocketchat_token_id_file,omitempty"`
>>>>>>> 87c1d1c3 (refactor: replace config.Secret with commoncfg.Secret)
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for GlobalConfig.
Expand Down
199 changes: 199 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,205 @@ func TestJSONMarshal(t *testing.T) {
}
}

<<<<<<< HEAD
=======
func TestJSONMarshalHideSecretURL(t *testing.T) {
urlp, err := url.Parse("http://example.com/")
if err != nil {
t.Fatal(err)
}
u := &SecretURL{urlp}

c, err := json.Marshal(u)
if err != nil {
t.Fatal(err)
}
// u003c -> "<"
// u003e -> ">"
require.Equal(t, "\"\\u003csecret\\u003e\"", string(c), "SecretURL not properly elided in JSON.")
// Check that the marshaled data can be unmarshaled again.
out := &SecretURL{}
err = json.Unmarshal(c, out)
if err != nil {
t.Fatal(err)
}

c, err = yaml.Marshal(u)
if err != nil {
t.Fatal(err)
}
require.Equal(t, "<secret>\n", string(c), "SecretURL not properly elided in YAML.")
// Check that the marshaled data can be unmarshaled again.
out = &SecretURL{}
err = yaml.Unmarshal(c, &out)
if err != nil {
t.Fatal(err)
}
}

func TestUnmarshalSecretURL(t *testing.T) {
b := []byte(`"http://example.com/se cret"`)
var u SecretURL

err := json.Unmarshal(b, &u)
if err != nil {
t.Fatal(err)
}
require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in JSON.")

err = yaml.Unmarshal(b, &u)
if err != nil {
t.Fatal(err)
}

require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in YAML.")
}

func TestHideSecretURL(t *testing.T) {
b := []byte(`"://wrongurl/"`)
var u SecretURL

err := json.Unmarshal(b, &u)
require.Error(t, err)
require.NotContains(t, err.Error(), "wrongurl")
}

func TestShowMarshalSecretURL(t *testing.T) {
commoncfg.MarshalSecretValue = true
defer func() { commoncfg.MarshalSecretValue = false }()

b := []byte(`"://wrongurl/"`)
var u SecretURL

err := json.Unmarshal(b, &u)
require.Error(t, err)
require.Contains(t, err.Error(), "wrongurl")
}

func TestMarshalURL(t *testing.T) {
for name, tc := range map[string]struct {
input *URL
expectedJSON string
expectedYAML string
}{
"url": {
input: mustParseURL("http://example.com/"),
expectedJSON: "\"http://example.com/\"",
expectedYAML: "http://example.com/\n",
},

"wrapped nil value": {
input: &URL{},
expectedJSON: "null",
expectedYAML: "null\n",
},

"wrapped empty URL": {
input: &URL{&url.URL{}},
expectedJSON: "\"\"",
expectedYAML: "\"\"\n",
},
} {
t.Run(name, func(t *testing.T) {
j, err := json.Marshal(tc.input)
require.NoError(t, err)
require.Equal(t, tc.expectedJSON, string(j), "URL not properly marshaled into JSON.")

y, err := yaml.Marshal(tc.input)
require.NoError(t, err)
require.Equal(t, tc.expectedYAML, string(y), "URL not properly marshaled into YAML.")
})
}
}

func TestUnmarshalNilURL(t *testing.T) {
b := []byte(`null`)

{
var u URL
err := json.Unmarshal(b, &u)
require.Error(t, err, "unsupported scheme \"\" for URL")
}

{
var u URL
err := yaml.Unmarshal(b, &u)
require.NoError(t, err)
}
}

func TestUnmarshalEmptyURL(t *testing.T) {
b := []byte(`""`)

{
var u URL
err := json.Unmarshal(b, &u)
require.Error(t, err, "unsupported scheme \"\" for URL")
require.Equal(t, (*url.URL)(nil), u.URL)
}

{
var u URL
err := yaml.Unmarshal(b, &u)
require.Error(t, err, "unsupported scheme \"\" for URL")
require.Equal(t, (*url.URL)(nil), u.URL)
}
}

func TestUnmarshalURL(t *testing.T) {
b := []byte(`"http://example.com/a b"`)
var u URL

err := json.Unmarshal(b, &u)
if err != nil {
t.Fatal(err)
}
require.Equal(t, "http://example.com/a%20b", u.String(), "URL not properly unmarshaled in JSON.")

err = yaml.Unmarshal(b, &u)
if err != nil {
t.Fatal(err)
}
require.Equal(t, "http://example.com/a%20b", u.String(), "URL not properly unmarshaled in YAML.")
}

func TestUnmarshalInvalidURL(t *testing.T) {
for _, b := range [][]byte{
[]byte(`"://example.com"`),
[]byte(`"http:example.com"`),
[]byte(`"telnet://example.com"`),
} {
var u URL

err := json.Unmarshal(b, &u)
if err == nil {
t.Errorf("Expected an error unmarshaling %q from JSON", string(b))
}

err = yaml.Unmarshal(b, &u)
if err == nil {
t.Errorf("Expected an error unmarshaling %q from YAML", string(b))
}
t.Logf("%s", err)
}
}

func TestUnmarshalRelativeURL(t *testing.T) {
b := []byte(`"/home"`)
var u URL

err := json.Unmarshal(b, &u)
if err == nil {
t.Errorf("Expected an error parsing URL")
}

err = yaml.Unmarshal(b, &u)
if err == nil {
t.Errorf("Expected an error parsing URL")
}
}

>>>>>>> 87c1d1c3 (refactor: replace config.Secret with commoncfg.Secret)
func TestMarshalRegexpWithNilValue(t *testing.T) {
r := &Regexp{}

Expand Down
Loading