Skip to content

Commit

Permalink
config: validate URLs at config load time (prometheus#1468)
Browse files Browse the repository at this point in the history
* config: validate URLs at config load time

Signed-off-by: Simon Pasquier <[email protected]>

* Address Brian and Lucas comments

Signed-off-by: Simon Pasquier <[email protected]>

* Shallow copy of URL instead of reparsing it

Signed-off-by: Simon Pasquier <[email protected]>

* Unshadow net/url package

Signed-off-by: Simon Pasquier <[email protected]>

* Make a deep-copy of URL struct

Signed-off-by: Simon Pasquier <[email protected]>
  • Loading branch information
simonpasquier authored and mxinden committed Aug 14, 2018
1 parent cfbdf41 commit 2261953
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 95 deletions.
180 changes: 135 additions & 45 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"net/url"
"path/filepath"
"regexp"
"strings"
Expand All @@ -39,7 +40,7 @@ func (s Secret) MarshalYAML() (interface{}, error) {
return nil, nil
}

//UnmarshalYAML implements the yaml.Unmarshaler interface for Secrets.
// UnmarshalYAML implements the yaml.Unmarshaler interface for Secret.
func (s *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain Secret
return unmarshal((*plain)(s))
Expand All @@ -50,6 +51,87 @@ func (s Secret) MarshalJSON() ([]byte, error) {
return json.Marshal("<secret>")
}

// URL is a custom type that 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() (interface{}, error) {
if u.URL != nil {
return u.URL.String(), nil
}
return nil, nil
}

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

// MarshalJSON implements the json.Marshaler interface for URL.
func (u URL) MarshalJSON() ([]byte, error) {
if u.URL != nil {
return json.Marshal(u.URL.String())
}
return nil, 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 := url.Parse(s)
if err != nil {
return err
}
u.URL = urlp
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() (interface{}, error) {
if s.URL != nil {
return "<secret>", nil
}
return nil, nil
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for SecretURL.
func (s *SecretURL) UnmarshalYAML(unmarshal func(interface{}) error) error {
return unmarshal((*URL)(s))
}

// MarshalJSON implements the json.Marshaler interface for SecretURL.
func (s SecretURL) MarshalJSON() ([]byte, error) {
return json.Marshal("<secret>")
}

// UnmarshalJSON implements the json.Marshaler interface for SecretURL.
func (s *SecretURL) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, (*URL)(s))
}

// Load parses the YAML input s into a Config.
func Load(s string) (*Config, error) {
cfg := &Config{}
Expand Down Expand Up @@ -188,8 +270,8 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
if sc.HTTPConfig == nil {
sc.HTTPConfig = c.Global.HTTPConfig
}
if sc.APIURL == "" {
if c.Global.SlackAPIURL == "" {
if sc.APIURL == nil {
if c.Global.SlackAPIURL == nil {
return fmt.Errorf("no global Slack API URL set")
}
sc.APIURL = c.Global.SlackAPIURL
Expand All @@ -199,14 +281,14 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
if hc.HTTPConfig == nil {
hc.HTTPConfig = c.Global.HTTPConfig
}
if hc.APIURL == "" {
if c.Global.HipchatAPIURL == "" {
if hc.APIURL == nil {
if c.Global.HipchatAPIURL == nil {
return fmt.Errorf("no global Hipchat API URL set")
}
hc.APIURL = c.Global.HipchatAPIURL
}
if !strings.HasSuffix(hc.APIURL, "/") {
hc.APIURL += "/"
if !strings.HasSuffix(hc.APIURL.Path, "/") {
hc.APIURL.Path += "/"
}
if hc.AuthToken == "" {
if c.Global.HipchatAuthToken == "" {
Expand All @@ -224,8 +306,8 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
if pdc.HTTPConfig == nil {
pdc.HTTPConfig = c.Global.HTTPConfig
}
if pdc.URL == "" {
if c.Global.PagerdutyURL == "" {
if pdc.URL == nil {
if c.Global.PagerdutyURL == nil {
return fmt.Errorf("no global PagerDuty URL set")
}
pdc.URL = c.Global.PagerdutyURL
Expand All @@ -235,14 +317,14 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
if ogc.HTTPConfig == nil {
ogc.HTTPConfig = c.Global.HTTPConfig
}
if ogc.APIURL == "" {
if c.Global.OpsGenieAPIURL == "" {
if ogc.APIURL == nil {
if c.Global.OpsGenieAPIURL == nil {
return fmt.Errorf("no global OpsGenie URL set")
}
ogc.APIURL = c.Global.OpsGenieAPIURL
}
if !strings.HasSuffix(ogc.APIURL, "/") {
ogc.APIURL += "/"
if !strings.HasSuffix(ogc.APIURL.Path, "/") {
ogc.APIURL.Path += "/"
}
if ogc.APIKey == "" {
if c.Global.OpsGenieAPIKey == "" {
Expand All @@ -256,8 +338,8 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
wcc.HTTPConfig = c.Global.HTTPConfig
}

if wcc.APIURL == "" {
if c.Global.WeChatAPIURL == "" {
if wcc.APIURL == nil {
if c.Global.WeChatAPIURL == nil {
return fmt.Errorf("no global Wechat URL set")
}
wcc.APIURL = c.Global.WeChatAPIURL
Expand All @@ -277,22 +359,22 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
wcc.CorpID = c.Global.WeChatAPICorpID
}

if !strings.HasSuffix(wcc.APIURL, "/") {
wcc.APIURL += "/"
if !strings.HasSuffix(wcc.APIURL.Path, "/") {
wcc.APIURL.Path += "/"
}
}
for _, voc := range rcv.VictorOpsConfigs {
if voc.HTTPConfig == nil {
voc.HTTPConfig = c.Global.HTTPConfig
}
if voc.APIURL == "" {
if c.Global.VictorOpsAPIURL == "" {
if voc.APIURL == nil {
if c.Global.VictorOpsAPIURL == nil {
return fmt.Errorf("no global VictorOps URL set")
}
voc.APIURL = c.Global.VictorOpsAPIURL
}
if !strings.HasSuffix(voc.APIURL, "/") {
voc.APIURL += "/"
if !strings.HasSuffix(voc.APIURL.Path, "/") {
voc.APIURL.Path += "/"
}
if voc.APIKey == "" {
if c.Global.VictorOpsAPIKey == "" {
Expand Down Expand Up @@ -344,11 +426,19 @@ var DefaultGlobalConfig = GlobalConfig{

SMTPHello: "localhost",
SMTPRequireTLS: true,
PagerdutyURL: "https://events.pagerduty.com/v2/enqueue",
HipchatAPIURL: "https://api.hipchat.com/",
OpsGenieAPIURL: "https://api.opsgenie.com/",
WeChatAPIURL: "https://qyapi.weixin.qq.com/cgi-bin/",
VictorOpsAPIURL: "https://alert.victorops.com/integrations/generic/20131114/alert/",
PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"),
HipchatAPIURL: mustParseURL("https://api.hipchat.com/"),
OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"),
WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"),
VictorOpsAPIURL: mustParseURL("https://alert.victorops.com/integrations/generic/20131114/alert/"),
}

func mustParseURL(s string) *URL {
u, err := url.Parse(s)
if err != nil {
panic(err)
}
return &URL{u}
}

// GlobalConfig defines configuration parameters that are valid globally
Expand All @@ -360,25 +450,25 @@ type GlobalConfig struct {

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

SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"`
SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,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"`
HipchatAPIURL string `yaml:"hipchat_api_url,omitempty" json:"hipchat_api_url,omitempty"`
HipchatAuthToken Secret `yaml:"hipchat_auth_token,omitempty" json:"hipchat_auth_token,omitempty"`
OpsGenieAPIURL string `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"`
OpsGenieAPIKey Secret `yaml:"opsgenie_api_key,omitempty" json:"opsgenie_api_key,omitempty"`
WeChatAPIURL string `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"`
WeChatAPISecret Secret `yaml:"wechat_api_secret,omitempty" json:"wechat_api_secret,omitempty"`
WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"`
VictorOpsAPIURL string `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"`
VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"`
SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"`
SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,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 *SecretURL `yaml:"slack_api_url,omitempty" json:"slack_api_url,omitempty"`
PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_url,omitempty"`
HipchatAPIURL *URL `yaml:"hipchat_api_url,omitempty" json:"hipchat_api_url,omitempty"`
HipchatAuthToken Secret `yaml:"hipchat_auth_token,omitempty" json:"hipchat_auth_token,omitempty"`
OpsGenieAPIURL *URL `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"`
OpsGenieAPIKey Secret `yaml:"opsgenie_api_key,omitempty" json:"opsgenie_api_key,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"`
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"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
Expand Down
Loading

0 comments on commit 2261953

Please sign in to comment.