Expose loaded blackbox configuration on /config (#185)
authorConor Broderick <conor.broderick@robustperception.io>
Tue, 11 Jul 2017 13:58:36 +0000 (14:58 +0100)
committerBrian Brazil <brian.brazil@robustperception.io>
Tue, 11 Jul 2017 13:58:36 +0000 (14:58 +0100)
config.go
config_test.go
main.go
testdata/blackbox-good.yml

index 1a5ebc00bc7c48b7a9875b02f2abaf90c106832e..5e2978880ea29c04ffe9c82b0d722fb6f23dc04f 100644 (file)
--- a/config.go
+++ b/config.go
@@ -22,12 +22,12 @@ type SafeConfig struct {
 }
 
 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"`
+       Prober  string        `yaml:"prober,omitempty"`
+       Timeout time.Duration `yaml:"timeout,omitempty"`
+       HTTP    HTTPProbe     `yaml:"http,omitempty"`
+       TCP     TCPProbe      `yaml:"tcp,omitempty"`
+       ICMP    ICMPProbe     `yaml:"icmp,omitempty"`
+       DNS     DNSProbe      `yaml:"dns,omitempty"`
 
        // Catches all undefined fields and must be empty after parsing.
        XXX map[string]interface{} `yaml:",inline"`
@@ -35,16 +35,16 @@ type Module struct {
 
 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"`
+       ValidStatusCodes       []int                   `yaml:"valid_status_codes,omitempty"`
+       PreferredIPProtocol    string                  `yaml:"preferred_ip_protocol,omitempty"`
+       NoFollowRedirects      bool                    `yaml:"no_follow_redirects,omitempty"`
+       FailIfSSL              bool                    `yaml:"fail_if_ssl,omitempty"`
+       FailIfNotSSL           bool                    `yaml:"fail_if_not_ssl,omitempty"`
+       Method                 string                  `yaml:"method,omitempty"`
+       Headers                map[string]string       `yaml:"headers,omitempty"`
+       FailIfMatchesRegexp    []string                `yaml:"fail_if_matches_regexp,omitempty"`
+       FailIfNotMatchesRegexp []string                `yaml:"fail_if_not_matches_regexp,omitempty"`
+       Body                   string                  `yaml:"body,omitempty"`
        HTTPClientConfig       config.HTTPClientConfig `yaml:"http_client_config,inline"`
 
        // Catches all undefined fields and must be empty after parsing.
@@ -52,47 +52,47 @@ type HTTPProbe struct {
 }
 
 type QueryResponse struct {
-       Expect string `yaml:"expect"`
-       Send   string `yaml:"send"`
+       Expect string `yaml:"expect,omitempty"`
+       Send   string `yaml:"send,omitempty"`
 
        // 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"`
+       PreferredIPProtocol string           `yaml:"preferred_ip_protocol,omitempty"`
+       QueryResponse       []QueryResponse  `yaml:"query_response,omitempty"`
+       TLS                 bool             `yaml:"tls,omitempty"`
+       TLSConfig           config.TLSConfig `yaml:"tls_config,omitempty"`
 
        // 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".
+       PreferredIPProtocol string `yaml:"preferred_ip_protocol,omitempty"` // 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"`
+       PreferredIPProtocol string         `yaml:"preferred_ip_protocol,omitempty"`
+       TransportProtocol   string         `yaml:"transport_protocol,omitempty"`
+       QueryName           string         `yaml:"query_name,omitempty"`
+       QueryType           string         `yaml:"query_type,omitempty"`   // Defaults to ANY.
+       ValidRcodes         []string       `yaml:"valid_rcodes,omitempty"` // Defaults to NOERROR.
+       ValidateAnswer      DNSRRValidator `yaml:"validate_answer_rrs,omitempty"`
+       ValidateAuthority   DNSRRValidator `yaml:"validate_authority_rrs,omitempty"`
+       ValidateAdditional  DNSRRValidator `yaml:"validate_additional_rrs,omitempty"`
 
        // 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"`
+       FailIfMatchesRegexp    []string `yaml:"fail_if_matches_regexp,omitempty"`
+       FailIfNotMatchesRegexp []string `yaml:"fail_if_not_matches_regexp,omitempty"`
 
        // Catches all undefined fields and must be empty after parsing.
        XXX map[string]interface{} `yaml:",inline"`
index bf42847cdf54b5fa4a6dd44fc21742edf79b42b3..cea82c46e85c5f1e80d283e76c063e1a1e60aac0 100644 (file)
@@ -1,6 +1,11 @@
 package main
 
-import "testing"
+import (
+       "strings"
+       "testing"
+
+       yaml "gopkg.in/yaml.v2"
+)
 
 func TestLoadConfig(t *testing.T) {
        sc := &SafeConfig{
@@ -25,3 +30,26 @@ func TestLoadBadConfig(t *testing.T) {
                t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
        }
 }
+
+func TestHideConfigSecrets(t *testing.T) {
+
+       sc := &SafeConfig{
+               C: &Config{},
+       }
+
+       err := sc.reloadConfig("testdata/blackbox-good.yml")
+       if err != nil {
+               t.Errorf("Error loading config %v: %v", "testdata/blackbox-good.yml", err)
+       }
+
+       // String method must not reveal authentication credentials.
+       sc.RLock()
+       c, err := yaml.Marshal(sc.C)
+       sc.RUnlock()
+       if err != nil {
+               t.Errorf("Error marshalling config: %v", err)
+       }
+       if strings.Contains(string(c), "mysecret") {
+               t.Fatal("config's String method reveals authentication credentials.")
+       }
+}
diff --git a/main.go b/main.go
index fef6c159b46f202cd48b508c3b87703384110184..5eff6bcfc92e27b679494928e38a552870a220fb 100644 (file)
--- a/main.go
+++ b/main.go
@@ -176,15 +176,29 @@ func main() {
                                http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError)
                        }
                })
+
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte(`<html>
-            <head><title>Blackbox Exporter</title></head>
-            <body>
-            <h1>Blackbox Exporter</h1>
-            <p><a href="/probe?target=prometheus.io&module=http_2xx">Probe prometheus.io for http_2xx</a></p>
-            <p><a href="/metrics">Metrics</a></p>
-            </body>
-            </html>`))
+                       <head><title>Blackbox Exporter</title></head>
+                       <body>
+                       <h1>Blackbox Exporter</h1>
+                       <p><a href="/probe?target=prometheus.io&module=http_2xx">Probe prometheus.io for http_2xx</a></p>
+                       <p><a href="/metrics">Metrics</a></p>
+                       <p><a href="/config">Configuration</a></p>
+                       </body>
+                       </html>`))
+       })
+
+       http.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
+               sc.RLock()
+               c, err := yaml.Marshal(sc.C)
+               sc.RUnlock()
+               if err != nil {
+                       log.Warnf("Error marshalling configuration: %v", err)
+                       http.Error(w, err.Error(), 500)
+                       return
+               }
+               w.Write(c)
        })
 
        log.Infoln("Listening on", *listenAddress)
index c5c8076ec684356231db2585642b43cad3e2069a..9025394f3d8145cd6bfed10338a9b7a1d7c3d349 100644 (file)
@@ -8,6 +8,9 @@ modules:
     timeout: 5s
     http:
       method: POST
+      basic_auth:
+        username: "username"
+        password: "mysecret"
   tcp_connect:
     prober: tcp
     timeout: 5s