Omit empty config fields and show regex upon re-marshalling to elide secrets (#864)

* Omit empty config fields upon remarshalling to elide secrets

* added test checking for empty or null fields and blank regexps
This commit is contained in:
Conor Broderick 2017-06-20 18:09:14 +01:00 committed by stuart nelson
parent 6b5fb2dbc9
commit b5ad65fa32
4 changed files with 186 additions and 82 deletions

View File

@ -303,19 +303,19 @@ type GlobalConfig struct {
// if it has not been updated.
ResolveTimeout model.Duration `yaml:"resolve_timeout" json:"resolve_timeout"`
SMTPFrom string `yaml:"smtp_from" json:"smtp_from"`
SMTPSmarthost string `yaml:"smtp_smarthost" json:"smtp_smarthost"`
SMTPAuthUsername string `yaml:"smtp_auth_username" json:"smtp_auth_username"`
SMTPAuthPassword Secret `yaml:"smtp_auth_password" json:"smtp_auth_password"`
SMTPAuthSecret Secret `yaml:"smtp_auth_secret" json:"smtp_auth_secret"`
SMTPAuthIdentity string `yaml:"smtp_auth_identity" json:"smtp_auth_identity"`
SMTPRequireTLS bool `yaml:"smtp_require_tls" json:"smtp_require_tls"`
SlackAPIURL Secret `yaml:"slack_api_url" json:"slack_api_url"`
PagerdutyURL string `yaml:"pagerduty_url" json:"pagerduty_url"`
HipchatURL string `yaml:"hipchat_url" json:"hipchat_url"`
HipchatAuthToken Secret `yaml:"hipchat_auth_token" json:"hipchat_auth_token"`
OpsGenieAPIHost string `yaml:"opsgenie_api_host" json:"opsgenie_api_host"`
VictorOpsAPIURL string `yaml:"victorops_api_url" json:"victorops_api_url"`
SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"`
SMTPSmarthost string `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,omitempty"`
SMTPAuthUsername string `yaml:"smtp_auth_username,omitempty" json:"smtp_auth_username,omitempty"`
SMTPAuthPassword Secret `yaml:"smtp_auth_password,omitempty" json:"smtp_auth_password,omitempty"`
SMTPAuthSecret Secret `yaml:"smtp_auth_secret,omitempty" json:"smtp_auth_secret,omitempty"`
SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,omitempty"`
SMTPRequireTLS bool `yaml:"smtp_require_tls,omitempty" json:"smtp_require_tls,omitempty"`
SlackAPIURL Secret `yaml:"slack_api_url,omitempty" json:"slack_api_url,omitempty"`
PagerdutyURL string `yaml:"pagerduty_url,omitempty" json:"pagerduty_url,omitempty"`
HipchatURL string `yaml:"hipchat_url,omitempty" json:"hipchat_url,omitempty"`
HipchatAuthToken Secret `yaml:"hipchat_auth_token,omitempty" json:"hipchat_auth_token,omitempty"`
OpsGenieAPIHost string `yaml:"opsgenie_api_host,omitempty" json:"opsgenie_api_host,omitempty"`
VictorOpsAPIURL string `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
@ -386,19 +386,19 @@ func (r *Route) UnmarshalYAML(unmarshal func(interface{}) error) error {
type InhibitRule struct {
// SourceMatch defines a set of labels that have to equal the given
// value for source alerts.
SourceMatch map[string]string `yaml:"source_match" json:"source_match"`
SourceMatch map[string]string `yaml:"source_match,omitempty" json:"source_match,omitempty"`
// SourceMatchRE defines pairs like SourceMatch but does regular expression
// matching.
SourceMatchRE map[string]Regexp `yaml:"source_match_re" json:"source_match_re"`
SourceMatchRE map[string]Regexp `yaml:"source_match_re,omitempty" json:"source_match_re,omitempty"`
// TargetMatch defines a set of labels that have to equal the given
// value for target alerts.
TargetMatch map[string]string `yaml:"target_match" json:"target_match"`
TargetMatch map[string]string `yaml:"target_match,omitempty" json:"target_match,omitempty"`
// TargetMatchRE defines pairs like TargetMatch but does regular expression
// matching.
TargetMatchRE map[string]Regexp `yaml:"target_match_re" json:"target_match_re"`
TargetMatchRE map[string]Regexp `yaml:"target_match_re,omitempty" json:"target_match_re,omitempty"`
// A set of labels that must be equal between the source and target alert
// for them to be a match.
Equal model.LabelNames `yaml:"equal" json:"equal"`
Equal model.LabelNames `yaml:"equal,omitempty" json:"equal,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
@ -488,8 +488,8 @@ func (re *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
// MarshalYAML implements the yaml.Marshaler interface.
func (re *Regexp) MarshalYAML() (interface{}, error) {
if re != nil {
func (re Regexp) MarshalYAML() (interface{}, error) {
if re.Regexp != nil {
return re.String(), nil
}
return nil, nil

View File

@ -15,11 +15,13 @@ package config
import (
"encoding/json"
"fmt"
"reflect"
"regexp"
"strings"
"testing"
"time"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)
@ -69,14 +71,13 @@ receivers:
func TestHideConfigSecrets(t *testing.T) {
c, _, err := LoadFile("testdata/conf.good.yml")
if err != nil {
t.Errorf("Error parsing %s: %s", "testdata/good.yml", err)
t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
}
// String method must not reveal authentication credentials.
s := c.String()
secretRe := regexp.MustCompile("<secret>")
matches := secretRe.FindAllStringIndex(s, -1)
fmt.Println(len(matches))
if len(matches) != 14 || strings.Contains(s, "mysecret") {
t.Fatal("config's String method reveals authentication credentials.")
}
@ -85,7 +86,7 @@ func TestHideConfigSecrets(t *testing.T) {
func TestJSONMarshal(t *testing.T) {
c, _, err := LoadFile("testdata/conf.good.yml")
if err != nil {
t.Errorf("Error parsing %s: %s", "testdata/good.yml", err)
t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
}
_, err = json.Marshal(c)
@ -114,7 +115,7 @@ func TestJSONMarshalSecret(t *testing.T) {
func TestJSONUnmarshalMarshaled(t *testing.T) {
c, _, err := LoadFile("testdata/conf.good.yml")
if err != nil {
t.Errorf("Error parsing %s: %s", "testdata/good.yml", err)
t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err)
}
plainCfg, err := json.Marshal(c)
@ -128,3 +129,79 @@ func TestJSONUnmarshalMarshaled(t *testing.T) {
t.Fatal("JSON Unmarshaling failed:", err)
}
}
func TestEmptyFieldsAndRegex(t *testing.T) {
boolFoo := true
var regexpFoo Regexp
regexpFoo.Regexp, _ = regexp.Compile("^(?:^(foo1|foo2|baz)$)$")
var expectedConf = Config{
Global: &GlobalConfig{
ResolveTimeout: model.Duration(5 * time.Minute),
SMTPSmarthost: "localhost:25",
SMTPFrom: "alertmanager@example.org",
HipchatAuthToken: "mysecret",
HipchatURL: "https://hipchat.foobar.org/",
SlackAPIURL: "mysecret",
SMTPRequireTLS: true,
PagerdutyURL: "https://events.pagerduty.com/generic/2010-04-15/create_event.json",
OpsGenieAPIHost: "https://api.opsgenie.com/",
VictorOpsAPIURL: "https://alert.victorops.com/integrations/generic/20131114/alert/",
},
Templates: []string{
"/etc/alertmanager/template/*.tmpl",
},
Route: &Route{
Receiver: "team-X-mails",
GroupBy: []model.LabelName{
"alertname",
"cluster",
"service",
},
Routes: []*Route{
{
Receiver: "team-X-mails",
MatchRE: map[string]Regexp{
"service": regexpFoo,
},
},
},
},
Receivers: []*Receiver{
{
Name: "team-X-mails",
EmailConfigs: []*EmailConfig{
{
To: "team-X+alerts@example.org",
From: "alertmanager@example.org",
Smarthost: "localhost:25",
HTML: "{{ template \"email.default.html\" . }}",
RequireTLS: &boolFoo,
},
},
},
},
}
config, _, err := LoadFile("testdata/conf.empty-fields.yml")
if err != nil {
t.Errorf("Error parsing %s: %s", "testdata/conf.empty-fields.yml", err)
}
configGot, err := yaml.Marshal(config)
if err != nil {
t.Fatal("YAML Marshaling failed:", err)
}
configExp, err := yaml.Marshal(expectedConf)
if err != nil {
t.Fatalf("%s", err)
}
if !reflect.DeepEqual(configGot, configExp) {
t.Fatalf("%s: unexpected config result: \n\n%s\n expected\n\n%s", "testdata/conf.empty-fields.yml", configGot, configExp)
}
}

View File

@ -131,15 +131,15 @@ type EmailConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
// Email address to notify.
To string `yaml:"to" json:"to"`
From string `yaml:"from" json:"from"`
To string `yaml:"to,omitempty" json:"to,omitempty"`
From string `yaml:"from,omitempty" json:"from,omitempty"`
Smarthost string `yaml:"smarthost,omitempty" json:"smarthost,omitempty"`
AuthUsername string `yaml:"auth_username" json:"auth_username"`
AuthPassword Secret `yaml:"auth_password" json:"auth_password"`
AuthSecret Secret `yaml:"auth_secret" json:"auth_secret"`
AuthIdentity string `yaml:"auth_identity" json:"auth_identity"`
Headers map[string]string `yaml:"headers" json:"headers"`
HTML string `yaml:"html" json:"html"`
AuthUsername string `yaml:"auth_username,omitempty" json:"auth_username,omitempty"`
AuthPassword Secret `yaml:"auth_password,omitempty" json:"auth_password,omitempty"`
AuthSecret Secret `yaml:"auth_secret,omitempty" json:"auth_secret,omitempty"`
AuthIdentity string `yaml:"auth_identity,omitempty" json:"auth_identity,omitempty"`
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
HTML string `yaml:"html,omitempty" json:"html,omitempty"`
RequireTLS *bool `yaml:"require_tls,omitempty" json:"require_tls,omitempty"`
// Catches all undefined fields and must be empty after parsing.
@ -174,12 +174,12 @@ func (c *EmailConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type PagerdutyConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
ServiceKey Secret `yaml:"service_key" json:"service_key"`
URL string `yaml:"url" json:"url"`
Client string `yaml:"client" json:"client"`
ClientURL string `yaml:"client_url" json:"client_url"`
Description string `yaml:"description" json:"description"`
Details map[string]string `yaml:"details" json:"details"`
ServiceKey Secret `yaml:"service_key,omitempty" json:"service_key,omitempty"`
URL string `yaml:"url,omitempty" json:"url,omitempty"`
Client string `yaml:"client,omitempty" json:"client,omitempty"`
ClientURL string `yaml:"client_url,omitempty" json:"client_url,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
@ -202,20 +202,20 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
type SlackConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
APIURL Secret `yaml:"api_url" json:"api_url"`
APIURL Secret `yaml:"api_url,omitempty" json:"api_url,omitempty"`
// Slack channel override, (like #other-channel or @username).
Channel string `yaml:"channel" json:"channel"`
Username string `yaml:"username" json:"username"`
Color string `yaml:"color" json:"color"`
Channel string `yaml:"channel,omitempty" json:"channel,omitempty"`
Username string `yaml:"username,omitempty" json:"username,omitempty"`
Color string `yaml:"color,omitempty" json:"color,omitempty"`
Title string `yaml:"title" json:"title"`
TitleLink string `yaml:"title_link" json:"title_link"`
Pretext string `yaml:"pretext" json:"pretext"`
Text string `yaml:"text" json:"text"`
Fallback string `yaml:"fallback" json:"fallback"`
IconEmoji string `yaml:"icon_emoji" json:"icon_emoji"`
IconURL string `yaml:"icon_url" json:"icon_url"`
Title string `yaml:"title,omitempty" json:"title,omitempty"`
TitleLink string `yaml:"title_link,omitempty" json:"title_link,omitempty"`
Pretext string `yaml:"pretext,omitempty" json:"pretext,omitempty"`
Text string `yaml:"text,omitempty" json:"text,omitempty"`
Fallback string `yaml:"fallback,omitempty" json:"fallback,omitempty"`
IconEmoji string `yaml:"icon_emoji,omitempty" json:"icon_emoji,omitempty"`
IconURL string `yaml:"icon_url,omitempty" json:"icon_url,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
@ -235,17 +235,17 @@ func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type HipchatConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
APIURL string `yaml:"api_url" json:"api_url"`
AuthToken Secret `yaml:"auth_token" json:"auth_token"`
RoomID string `yaml:"room_id" json:"room_id"`
From string `yaml:"from" json:"from"`
Notify bool `yaml:"notify" json:"notify"`
Message string `yaml:"message" json:"message"`
MessageFormat string `yaml:"message_format" json:"message_format"`
Color string `yaml:"color" json:"color"`
APIURL string `yaml:"api_url,omitempty" json:"api_url,omitempty"`
AuthToken Secret `yaml:"auth_token,omitempty" json:"auth_token,omitempty"`
RoomID string `yaml:"room_id,omitempty" json:"room_id,omitempty"`
From string `yaml:"from,omitempty" json:"from,omitempty"`
Notify bool `yaml:"notify,omitempty" json:"notify,omitempty"`
Message string `yaml:"message,omitempty" json:"message,omitempty"`
MessageFormat string `yaml:"message_format,omitempty" json:"message_format,omitempty"`
Color string `yaml:"color,omitempty" json:"color,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
XXX map[string]interface{} `yaml:",inline" ,json:"-"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
@ -290,15 +290,15 @@ func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type OpsGenieConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
APIKey Secret `yaml:"api_key" json:"api_key"`
APIHost string `yaml:"api_host" json:"api_host"`
Message string `yaml:"message" json:"message"`
Description string `yaml:"description" json:"description"`
Source string `yaml:"source" json:"source"`
Details map[string]string `yaml:"details" json:"details"`
Teams string `yaml:"teams" json:"teams"`
Tags string `yaml:"tags" json:"tags"`
Note string `yaml:"note" json:"note"`
APIKey Secret `yaml:"api_key,omitempty" json:"api_key,omitempty"`
APIHost string `yaml:"api_host,omitempty" json:"api_host,omitempty"`
Message string `yaml:"message,omitempty" json:"message,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Source string `yaml:"source,omitempty" json:"source,omitempty"`
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
Teams string `yaml:"teams,omitempty" json:"teams,omitempty"`
Tags string `yaml:"tags,omitempty" json:"tags,omitempty"`
Note string `yaml:"note,omitempty" json:"note,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
@ -321,12 +321,12 @@ func (c *OpsGenieConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
type VictorOpsConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
APIKey Secret `yaml:"api_key" json:"api_key"`
APIURL string `yaml:"api_url" json:"api_url"`
RoutingKey string `yaml:"routing_key" json:"routing_key"`
MessageType string `yaml:"message_type" json:"message_type"`
StateMessage string `yaml:"state_message" json:"state_message"`
MonitoringTool string `yaml:"monitoring_tool" json:"monitoring_tool"`
APIKey Secret `yaml:"api_key,omitempty" json:"api_key,omitempty"`
APIURL string `yaml:"api_url,omitempty" json:"api_url,omitempty"`
RoutingKey string `yaml:"routing_key,omitempty" json:"routing_key,omitempty"`
MessageType string `yaml:"message_type,omitempty" json:"message_type,omitempty"`
StateMessage string `yaml:"state_message,omitempty" json:"state_message,omitempty"`
MonitoringTool string `yaml:"monitoring_tool,omitempty" json:"monitoring_tool,omitempty"`
XXX map[string]interface{} `yaml:",inline" json:"-"`
}
@ -364,14 +364,14 @@ func (d duration) MarshalText() ([]byte, error) {
type PushoverConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
UserKey Secret `yaml:"user_key" json:"user_key"`
Token Secret `yaml:"token" json:"token"`
Title string `yaml:"title" json:"title"`
Message string `yaml:"message" json:"message"`
URL string `yaml:"url" json:"url"`
Priority string `yaml:"priority" json:"priority"`
Retry duration `yaml:"retry" json:"retry"`
Expire duration `yaml:"expire" json:"expire"`
UserKey Secret `yaml:"user_key,omitempty" json:"user_key,omitempty"`
Token Secret `yaml:"token,omitempty" json:"token,omitempty"`
Title string `yaml:"title,omitempty" json:"title,omitempty"`
Message string `yaml:"message,omitempty" json:"message,omitempty"`
URL string `yaml:"url,omitempty" json:"url,omitempty"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
Retry duration `yaml:"retry,omitempty" json:"retry,omitempty"`
Expire duration `yaml:"expire,omitempty" json:"expire,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`

27
config/testdata/conf.empty-fields.yml vendored Normal file
View File

@ -0,0 +1,27 @@
global:
smtp_smarthost: 'localhost:25'
smtp_from: 'alertmanager@example.org'
smtp_auth_username: ''
smtp_auth_password: ''
hipchat_auth_token: 'mysecret'
hipchat_url: 'https://hipchat.foobar.org/'
slack_api_url: 'mysecret'
templates:
- '/etc/alertmanager/template/*.tmpl'
route:
group_by: ['alertname', 'cluster', 'service']
receiver: team-X-mails
routes:
- match_re:
service: ^(foo1|foo2|baz)$
receiver: team-X-mails
receivers:
- name: 'team-X-mails'
email_configs:
- to: 'team-X+alerts@example.org'