Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update config to allow showing secret values when marshaled #4158

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
26 changes: 26 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ const secretToken = "<secret>"

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 {
Expand All @@ -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
}
Expand All @@ -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)
}

Expand Down Expand Up @@ -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
Expand All @@ -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)
}

Expand All @@ -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]"))
}

Expand Down
66 changes: 64 additions & 2 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, "<secret>") > 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 {
Expand All @@ -533,7 +549,7 @@ func TestJSONMarshal(t *testing.T) {
}
}

func TestJSONMarshalSecret(t *testing.T) {
func TestJSONMarshalHideSecret(t *testing.T) {
test := struct {
S Secret
}{
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading