Fill out http/ssl features. Add tcp connection check
authorBrian Brazil <brian.brazil@robustperception.io>
Sat, 5 Sep 2015 17:07:19 +0000 (18:07 +0100)
committerBrian Brazil <brian.brazil@robustperception.io>
Sat, 5 Sep 2015 17:07:19 +0000 (18:07 +0100)
blackbox.yml [new file with mode: 0644]
http.go [new file with mode: 0644]
main.go
probers/http.go [deleted file]
probers/probers.go [deleted file]
tcp.go [new file with mode: 0644]

diff --git a/blackbox.yml b/blackbox.yml
new file mode 100644 (file)
index 0000000..dee0bf0
--- /dev/null
@@ -0,0 +1,8 @@
+modules:
+  http2xx:
+    prober: http
+    timeout: 5s
+    http:
+  tcpconnect:
+    prober: tcp
+    timeout: 5s
diff --git a/http.go b/http.go
new file mode 100644 (file)
index 0000000..ccf2394
--- /dev/null
+++ b/http.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+       "crypto/tls"
+       "errors"
+       "fmt"
+       "net/http"
+       "time"
+)
+
+func getEarliestCertExpiry(state *tls.ConnectionState) time.Time {
+       earliest := time.Time{}
+       for _, cert := range state.PeerCertificates {
+               if (earliest.IsZero() || cert.NotAfter.Before(earliest)) && !cert.NotAfter.IsZero() {
+                       earliest = cert.NotAfter
+               }
+       }
+       return earliest
+}
+
+func probeHTTP(target string, w http.ResponseWriter, module Module) (success bool) {
+       var isSSL, redirects int
+       config := module.HTTP
+
+       client := &http.Client{
+               Timeout: module.Timeout,
+       }
+
+       client.CheckRedirect = func(_ *http.Request, via []*http.Request) error {
+               redirects = len(via)
+               if redirects > 10 || config.NoFollowRedirects {
+                       return errors.New("Don't follow redirects")
+               } else {
+                       return nil
+               }
+       }
+
+       resp, err := client.Get(target)
+       if err == nil {
+               defer resp.Body.Close()
+               if len(config.ValidStatusCodes) != 0 {
+                       for _, code := range config.ValidStatusCodes {
+                               if resp.StatusCode == code {
+                                       success = true
+                                       break
+                               }
+                       }
+               } else if 200 >= resp.StatusCode && resp.StatusCode < 300 {
+                       success = true
+               }
+       }
+
+       if resp == nil {
+               resp = &http.Response{}
+       }
+
+       if resp.TLS != nil {
+               isSSL = 1
+               fmt.Fprintf(w, "probe_ssl_earliest_cert_expiry %f\n",
+                       float64(getEarliestCertExpiry(resp.TLS).UnixNano())/1e9)
+               if config.FailIfSSL {
+                       success = false
+               }
+       } else if config.FailIfNotSSL {
+               success = false
+       }
+       fmt.Fprintf(w, "probe_http_status_code %d\n", resp.StatusCode)
+       fmt.Fprintf(w, "probe_http_content_length %d\n", resp.ContentLength)
+       fmt.Fprintf(w, "probe_http_redirects %d\n", redirects)
+       fmt.Fprintf(w, "probe_http_ssl %d\n", isSSL)
+       return
+}
diff --git a/main.go b/main.go
index bd744ef876c6f6ede159c2c4e0d6ec16711a851f..9ae8edf592d961f73cee4e4b70796ee25f206212 100644 (file)
--- a/main.go
+++ b/main.go
@@ -3,39 +3,63 @@ package main
 import (
        "flag"
        "fmt"
+       "gopkg.in/yaml.v2"
+       "io/ioutil"
        "net/http"
        "time"
 
        "github.com/prometheus/client_golang/prometheus"
-
-       "github.com/brian-brazil/blackbox_exporter/probers"
+       "github.com/prometheus/log"
 )
 
 var addr = flag.String("web.listen-address", ":8080", "The address to listen on for HTTP requests.")
+var configFile = flag.String("config.file", "blackbox.yml", "Blackbox exporter configuration file.")
 
 type Config struct {
-  Modules map[string]Module `yaml:"modules"`
+       Modules map[string]Module `yaml:"modules"`
 }
 
 type Module struct {
-  Prober string  `yaml:"prober"`
-  Timeout time.Duration `yaml:"timeout"`
-  Config interface{} `yaml:"config"`
+       Prober  string        `yaml:"prober"`
+       Timeout time.Duration `yaml:"timeout"`
+       HTTP    HTTPProbe     `yaml:"http"`
+       TCP     TCPProbe      `yaml:"tcp"`
+}
+
+type HTTPProbe struct {
+       // Defaults to 2xx.
+       ValidStatusCodes  []int `yaml:"valid_status_codes"`
+       NoFollowRedirects bool  `yaml:"no_follow_redirects"`
+       FailIfSSL         bool  `yaml:"fail_if_ssl"`
+       FailIfNotSSL      bool  `yaml:"fail_if_not_ssl"`
+}
+
+type TCPProbe struct {
+}
+
+var Probers = map[string]func(string, http.ResponseWriter, Module) bool{
+       "http": probeHTTP,
+       "tcp":  probeTCP,
 }
 
-func probeHandler(w http.ResponseWriter, r *http.Request) {
+func probeHandler(w http.ResponseWriter, r *http.Request, config *Config) {
        params := r.URL.Query()
        target := params.Get("target")
-       module := params.Get("module")
+       moduleName := params.Get("module")
        if target == "" {
                http.Error(w, "Target parameter is missing", 400)
                return
        }
-       if module == "" {
-               module = "http2xx"
+       if moduleName == "" {
+               moduleName = "http2xx"
+       }
+       module, ok := config.Modules[moduleName]
+       if !ok {
+               http.Error(w, fmt.Sprintf("Unkown module %s", moduleName), 400)
+               return
        }
        start := time.Now()
-       success := probers.Probers["http"](target, w)
+       success := Probers[module.Prober](target, w, module)
        fmt.Fprintf(w, "probe_duration_seconds %f\n", float64(time.Now().Sub(start))/1e9)
        if success {
                fmt.Fprintf(w, "probe_success %d\n", 1)
@@ -46,7 +70,24 @@ func probeHandler(w http.ResponseWriter, r *http.Request) {
 
 func main() {
        flag.Parse()
+
+       yamlFile, err := ioutil.ReadFile(*configFile)
+
+       if err != nil {
+               log.Fatalf("Error reading config file: %s", err)
+       }
+
+       config := Config{}
+
+       err = yaml.Unmarshal(yamlFile, &config)
+       if err != nil {
+               log.Fatalf("Error parsing config file: %s", err)
+       }
+
        http.Handle("/metrics", prometheus.Handler())
-       http.HandleFunc("/probe", probeHandler)
+       http.HandleFunc("/probe",
+               func(w http.ResponseWriter, r *http.Request) {
+                       probeHandler(w, r, &config)
+               })
        http.ListenAndServe(*addr, nil)
 }
diff --git a/probers/http.go b/probers/http.go
deleted file mode 100644 (file)
index aaa00b8..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package probers
-
-import (
-  "net/http"
-  "fmt"
-)
-
-func init() {
-  Probers["http"] = probeHTTP
-}
-
-type HTTPProbe struct {
-  // Defaults to 2xx.
-  ValidStatusCodes []int `yaml:"valid_status_codes"`
-  FollowRedirects bool `yaml:"follow_redirects"`
-  FailIfSSL bool `yaml:"fail_if_ssl"`
-  FailIfNotSSL bool `yaml:"fail_if_not_ssl"`
-}
-
-func probeHTTP(target string, w http.ResponseWriter) (success bool) {
-       var isSSL int
-       resp, err := http.Get(target)
-       if err == nil {
-               if 200 >= resp.StatusCode && resp.StatusCode < 300 {
-                       success = true
-               }
-               defer resp.Body.Close()
-       }
-       if resp.TLS != nil {
-               isSSL = 1
-       }
-       fmt.Fprintf(w, "probe_http_status_code %d\n", resp.StatusCode)
-       fmt.Fprintf(w, "probe_http_content_length %d\n", resp.ContentLength)
-       fmt.Fprintf(w, "probe_http_ssl %d\n", isSSL)
-       return
-}
diff --git a/probers/probers.go b/probers/probers.go
deleted file mode 100644 (file)
index a4f710f..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package probers
-
-import (
-  "net/http"
-)
-
-var Probers = make(map[string]func(string, http.ResponseWriter)(bool))
diff --git a/tcp.go b/tcp.go
new file mode 100644 (file)
index 0000000..685de9e
--- /dev/null
+++ b/tcp.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+       "net"
+       "net/http"
+)
+
+func probeTCP(target string, w http.ResponseWriter, module Module) (success bool) {
+       conn, err := net.DialTimeout("tcp", target, module.Timeout)
+       if err == nil {
+               success = true
+               conn.Close()
+       }
+       return
+}