diff --git a/config/config.go b/config/config.go index 890592ca90..f86c65633e 100644 --- a/config/config.go +++ b/config/config.go @@ -39,6 +39,11 @@ const secretToken = "" var secretTokenJSON string +// MarshalSecretValue if set to true will expose Secret type +// through the marshal interfaces. Useful for outside projects +// that load and marshal the Alertmanager config. +var MarshalSecretValue bool = commoncfg.MarshalSecretValue + func init() { b, err := json.Marshal(secretToken) if err != nil { @@ -52,6 +57,9 @@ type Secret string // MarshalYAML implements the yaml.Marshaler interface for Secret. func (s Secret) MarshalYAML() (interface{}, error) { + if MarshalSecretValue { + return string(s), nil + } if s != "" { return secretToken, nil } @@ -66,6 +74,12 @@ func (s *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error { // MarshalJSON implements the json.Marshaler interface for Secret. func (s Secret) MarshalJSON() ([]byte, error) { + if MarshalSecretValue { + return json.Marshal(string(s)) + } + if len(s) == 0 { + return json.Marshal("") + } return json.Marshal(secretToken) } @@ -130,6 +144,9 @@ type SecretURL URL // MarshalYAML implements the yaml.Marshaler interface for SecretURL. func (s SecretURL) MarshalYAML() (interface{}, error) { if s.URL != nil { + if MarshalSecretValue { + return s.URL.String(), nil + } return secretToken, nil } return nil, nil @@ -153,6 +170,12 @@ func (s *SecretURL) UnmarshalYAML(unmarshal func(interface{}) error) error { // MarshalJSON implements the json.Marshaler interface for SecretURL. func (s SecretURL) MarshalJSON() ([]byte, error) { + if s.URL == nil { + return json.Marshal("") + } + if MarshalSecretValue { + return json.Marshal(s.URL.String()) + } return json.Marshal(secretToken) } @@ -167,6 +190,9 @@ func (s *SecretURL) UnmarshalJSON(data []byte) error { } // Redact the secret URL in case of errors if err := json.Unmarshal(data, (*URL)(s)); err != nil { + if MarshalSecretValue { + return err + } return errors.New(strings.ReplaceAll(err.Error(), string(data), "[REDACTED]")) } diff --git a/config/config_test.go b/config/config_test.go index 15edfeed2d..93a525d807 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -521,6 +521,22 @@ func TestHideConfigSecrets(t *testing.T) { } } +func TestShowMarshalSecretValues(t *testing.T) { + MarshalSecretValue = true + defer func() { MarshalSecretValue = false }() + + c, err := LoadFile("testdata/conf.good.yml") + if err != nil { + t.Fatalf("Error parsing %s: %s", "testdata/conf.good.yml", err) + } + + // String method must reveal authentication credentials. + s := c.String() + if strings.Count(s, "") > 0 || !strings.Contains(s, "mysecret") { + t.Fatal("config's String method must reveal authentication credentials when MarshalSecretValue = true.") + } +} + func TestJSONMarshal(t *testing.T) { c, err := LoadFile("testdata/conf.good.yml") if err != nil { @@ -533,7 +549,7 @@ func TestJSONMarshal(t *testing.T) { } } -func TestJSONMarshalSecret(t *testing.T) { +func TestJSONMarshalHideSecret(t *testing.T) { test := struct { S Secret }{ @@ -550,7 +566,24 @@ func TestJSONMarshalSecret(t *testing.T) { require.Equal(t, "{\"S\":\"\\u003csecret\\u003e\"}", string(c), "Secret not properly elided.") } -func TestMarshalSecretURL(t *testing.T) { +func TestJSONMarshalShowSecret(t *testing.T) { + MarshalSecretValue = true + defer func() { MarshalSecretValue = false }() + + test := struct { + S Secret + }{ + S: Secret("test"), + } + + c, err := json.Marshal(test) + if err != nil { + t.Fatal(err) + } + require.Equal(t, "{\"S\":\"test\"}", string(c), "config's String method must reveal authentication credentials when MarshalSecretValue = true.") +} + +func TestJSONMarshalHideSecretURL(t *testing.T) { urlp, err := url.Parse("http://example.com/") if err != nil { t.Fatal(err) @@ -584,6 +617,23 @@ func TestMarshalSecretURL(t *testing.T) { } } +func TestJSONMarshalShowSecretURL(t *testing.T) { + MarshalSecretValue = true + defer func() { MarshalSecretValue = false }() + + 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) + } + require.Equal(t, "\"http://example.com/\"", string(c), "config's String method must reveal authentication credentials when MarshalSecretValue = true.") +} + func TestUnmarshalSecretURL(t *testing.T) { b := []byte(`"http://example.com/se cret"`) var u SecretURL @@ -611,6 +661,18 @@ func TestHideSecretURL(t *testing.T) { require.NotContains(t, err.Error(), "wrongurl") } +func TestShowMarshalSecretURL(t *testing.T) { + MarshalSecretValue = true + defer func() { 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