From 978c5b352bce3861e0211081624ef1e5c0f2f82f Mon Sep 17 00:00:00 2001 From: Conor Broderick Date: Mon, 3 Jul 2017 14:27:09 +0100 Subject: [PATCH] added checkOverflow to config (#179) --- config.go | 206 +++++++++++++++++++++++++++++++++++++ config_test.go | 27 +++++ main.go | 68 ------------ testdata/blackbox-bad.yml | 50 +++++++++ testdata/blackbox-good.yml | 49 +++++++++ 5 files changed, 332 insertions(+), 68 deletions(-) create mode 100644 config.go create mode 100644 config_test.go create mode 100644 testdata/blackbox-bad.yml create mode 100644 testdata/blackbox-good.yml diff --git a/config.go b/config.go new file mode 100644 index 0000000..1a5ebc0 --- /dev/null +++ b/config.go @@ -0,0 +1,206 @@ +package main + +import ( + "fmt" + "strings" + "sync" + "time" + + "github.com/prometheus/common/config" +) + +type Config struct { + Modules map[string]Module `yaml:"modules"` + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline"` +} + +type SafeConfig struct { + sync.RWMutex + C *Config +} + +type Module struct { + Prober string `yaml:"prober"` + Timeout time.Duration `yaml:"timeout"` + HTTP HTTPProbe `yaml:"http"` + TCP TCPProbe `yaml:"tcp"` + ICMP ICMPProbe `yaml:"icmp"` + DNS DNSProbe `yaml:"dns"` + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline"` +} + +type HTTPProbe struct { + // Defaults to 2xx. + ValidStatusCodes []int `yaml:"valid_status_codes"` + PreferredIPProtocol string `yaml:"preferred_ip_protocol"` + NoFollowRedirects bool `yaml:"no_follow_redirects"` + FailIfSSL bool `yaml:"fail_if_ssl"` + FailIfNotSSL bool `yaml:"fail_if_not_ssl"` + Method string `yaml:"method"` + Headers map[string]string `yaml:"headers"` + FailIfMatchesRegexp []string `yaml:"fail_if_matches_regexp"` + FailIfNotMatchesRegexp []string `yaml:"fail_if_not_matches_regexp"` + Body string `yaml:"body"` + HTTPClientConfig config.HTTPClientConfig `yaml:"http_client_config,inline"` + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline"` +} + +type QueryResponse struct { + Expect string `yaml:"expect"` + Send string `yaml:"send"` + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline"` +} + +type TCPProbe struct { + PreferredIPProtocol string `yaml:"preferred_ip_protocol"` + QueryResponse []QueryResponse `yaml:"query_response"` + TLS bool `yaml:"tls"` + TLSConfig config.TLSConfig `yaml:"tls_config"` + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline"` +} + +type ICMPProbe struct { + PreferredIPProtocol string `yaml:"preferred_ip_protocol"` // Defaults to "ip6". + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline"` +} + +type DNSProbe struct { + PreferredIPProtocol string `yaml:"preferred_ip_protocol"` + TransportProtocol string `yaml:"transport_protocol"` + QueryName string `yaml:"query_name"` + QueryType string `yaml:"query_type"` // Defaults to ANY. + ValidRcodes []string `yaml:"valid_rcodes"` // Defaults to NOERROR. + ValidateAnswer DNSRRValidator `yaml:"validate_answer_rrs"` + ValidateAuthority DNSRRValidator `yaml:"validate_authority_rrs"` + ValidateAdditional DNSRRValidator `yaml:"validate_additional_rrs"` + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline"` +} + +type DNSRRValidator struct { + FailIfMatchesRegexp []string `yaml:"fail_if_matches_regexp"` + FailIfNotMatchesRegexp []string `yaml:"fail_if_not_matches_regexp"` + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline"` +} + +func checkOverflow(m map[string]interface{}, ctx string) error { + if len(m) > 0 { + var keys []string + for k := range m { + keys = append(keys, k) + } + return fmt.Errorf("unknown fields in %s: %s", ctx, strings.Join(keys, ", ")) + } + return nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (s *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain Config + if err := unmarshal((*plain)(s)); err != nil { + return err + } + if err := checkOverflow(s.XXX, "config"); err != nil { + return err + } + return nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (s *Module) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain Module + if err := unmarshal((*plain)(s)); err != nil { + return err + } + if err := checkOverflow(s.XXX, "module"); err != nil { + return err + } + return nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (s *HTTPProbe) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain HTTPProbe + if err := unmarshal((*plain)(s)); err != nil { + return err + } + if err := checkOverflow(s.XXX, "http probe"); err != nil { + return err + } + return nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (s *DNSProbe) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain DNSProbe + if err := unmarshal((*plain)(s)); err != nil { + return err + } + if err := checkOverflow(s.XXX, "dns probe"); err != nil { + return err + } + return nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (s *TCPProbe) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain TCPProbe + if err := unmarshal((*plain)(s)); err != nil { + return err + } + if err := checkOverflow(s.XXX, "tcp probe"); err != nil { + return err + } + return nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (s *DNSRRValidator) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain DNSRRValidator + if err := unmarshal((*plain)(s)); err != nil { + return err + } + if err := checkOverflow(s.XXX, "dns rr validator"); err != nil { + return err + } + return nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (s *ICMPProbe) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain ICMPProbe + if err := unmarshal((*plain)(s)); err != nil { + return err + } + if err := checkOverflow(s.XXX, "icmp probe"); err != nil { + return err + } + return nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (s *QueryResponse) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain QueryResponse + if err := unmarshal((*plain)(s)); err != nil { + return err + } + if err := checkOverflow(s.XXX, "query response"); err != nil { + return err + } + return nil +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..bf42847 --- /dev/null +++ b/config_test.go @@ -0,0 +1,27 @@ +package main + +import "testing" + +func TestLoadConfig(t *testing.T) { + sc := &SafeConfig{ + C: &Config{}, + } + + err := sc.reloadConfig("testdata/blackbox-good.yml") + if err != nil { + t.Errorf("Error loading config %v: %v", "blackbox.yml", err) + } +} + +func TestLoadBadConfig(t *testing.T) { + sc := &SafeConfig{ + C: &Config{}, + } + + expected := "unknown fields in dns probe: invalid_extra_field" + + err := sc.reloadConfig("testdata/blackbox-bad.yml") + if err.Error() != expected { + t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) + } +} diff --git a/main.go b/main.go index b6dc8ff..fef6c15 100644 --- a/main.go +++ b/main.go @@ -23,82 +23,14 @@ import ( "syscall" "time" - "sync" - "gopkg.in/yaml.v2" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/prometheus/common/config" "github.com/prometheus/common/log" "github.com/prometheus/common/version" ) -type Config struct { - Modules map[string]Module `yaml:"modules"` -} - -type SafeConfig struct { - sync.RWMutex - C *Config -} - -type Module struct { - Prober string `yaml:"prober"` - Timeout time.Duration `yaml:"timeout"` - HTTP HTTPProbe `yaml:"http"` - TCP TCPProbe `yaml:"tcp"` - ICMP ICMPProbe `yaml:"icmp"` - DNS DNSProbe `yaml:"dns"` -} - -type HTTPProbe struct { - // Defaults to 2xx. - ValidStatusCodes []int `yaml:"valid_status_codes"` - PreferredIPProtocol string `yaml:"preferred_ip_protocol"` - NoFollowRedirects bool `yaml:"no_follow_redirects"` - FailIfSSL bool `yaml:"fail_if_ssl"` - FailIfNotSSL bool `yaml:"fail_if_not_ssl"` - Method string `yaml:"method"` - Headers map[string]string `yaml:"headers"` - FailIfMatchesRegexp []string `yaml:"fail_if_matches_regexp"` - FailIfNotMatchesRegexp []string `yaml:"fail_if_not_matches_regexp"` - Body string `yaml:"body"` - HTTPClientConfig config.HTTPClientConfig `yaml:"http_client_config,inline"` -} - -type QueryResponse struct { - Expect string `yaml:"expect"` - Send string `yaml:"send"` -} - -type TCPProbe struct { - PreferredIPProtocol string `yaml:"preferred_ip_protocol"` - QueryResponse []QueryResponse `yaml:"query_response"` - TLS bool `yaml:"tls"` - TLSConfig config.TLSConfig `yaml:"tls_config"` -} - -type ICMPProbe struct { - PreferredIPProtocol string `yaml:"preferred_ip_protocol"` // Defaults to "ip6". -} - -type DNSProbe struct { - PreferredIPProtocol string `yaml:"preferred_ip_protocol"` - TransportProtocol string `yaml:"transport_protocol"` - QueryName string `yaml:"query_name"` - QueryType string `yaml:"query_type"` // Defaults to ANY. - ValidRcodes []string `yaml:"valid_rcodes"` // Defaults to NOERROR. - ValidateAnswer DNSRRValidator `yaml:"validate_answer_rrs"` - ValidateAuthority DNSRRValidator `yaml:"validate_authority_rrs"` - ValidateAdditional DNSRRValidator `yaml:"validate_additional_rrs"` -} - -type DNSRRValidator struct { - FailIfMatchesRegexp []string `yaml:"fail_if_matches_regexp"` - FailIfNotMatchesRegexp []string `yaml:"fail_if_not_matches_regexp"` -} - var Probers = map[string]func(string, Module, *prometheus.Registry) bool{ "http": probeHTTP, "tcp": probeTCP, diff --git a/testdata/blackbox-bad.yml b/testdata/blackbox-bad.yml new file mode 100644 index 0000000..933ec54 --- /dev/null +++ b/testdata/blackbox-bad.yml @@ -0,0 +1,50 @@ +modules: + http_2xx: + prober: http + timeout: 5s + http: + http_post_2xx: + prober: http + timeout: 5s + http: + method: POST + tcp_connect: + prober: tcp + timeout: 5s + pop3s_banner: + prober: tcp + tcp: + query_response: + - expect: "^+OK" + tls: true + tls_config: + insecure_skip_verify: false + ssh_banner: + prober: tcp + timeout: 5s + tcp: + query_response: + - expect: "^SSH-2.0-" + irc_banner: + prober: tcp + timeout: 5s + tcp: + query_response: + - send: "NICK prober" + - send: "USER prober prober prober :prober" + - expect: "PING :([^ ]+)" + send: "PONG ${1}" + - expect: "^:[^ ]+ 001" + icmp_test: + prober: icmp + timeout: 5s + icmp: + preferred_ip_protocol: ip4 + dns_test: + prober: dns + timeout: 5s + dns: + preferred_ip_protocol: ip6 + validate_answer_rrs: + fail_if_matches_regexp: [test] + invalid_extra_field: value diff --git a/testdata/blackbox-good.yml b/testdata/blackbox-good.yml new file mode 100644 index 0000000..c5c8076 --- /dev/null +++ b/testdata/blackbox-good.yml @@ -0,0 +1,49 @@ +modules: + http_2xx: + prober: http + timeout: 5s + http: + http_post_2xx: + prober: http + timeout: 5s + http: + method: POST + tcp_connect: + prober: tcp + timeout: 5s + pop3s_banner: + prober: tcp + tcp: + query_response: + - expect: "^+OK" + tls: true + tls_config: + insecure_skip_verify: false + ssh_banner: + prober: tcp + timeout: 5s + tcp: + query_response: + - expect: "^SSH-2.0-" + irc_banner: + prober: tcp + timeout: 5s + tcp: + query_response: + - send: "NICK prober" + - send: "USER prober prober prober :prober" + - expect: "PING :([^ ]+)" + send: "PONG ${1}" + - expect: "^:[^ ]+ 001" + icmp_test: + prober: icmp + timeout: 5s + icmp: + preferred_ip_protocol: ip4 + dns_test: + prober: dns + timeout: 5s + dns: + preferred_ip_protocol: ip6 + validate_answer_rrs: + fail_if_matches_regexp: [test] -- 2.25.1