From 98838d529a4ad913cb87ca64fa3660b129a25373 Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Sat, 5 Sep 2015 18:07:19 +0100 Subject: [PATCH] Fill out http/ssl features. Add tcp connection check --- blackbox.yml | 8 ++++++ http.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++ main.go | 65 +++++++++++++++++++++++++++++++++-------- probers/http.go | 36 ----------------------- probers/probers.go | 7 ----- tcp.go | 15 ++++++++++ 6 files changed, 148 insertions(+), 55 deletions(-) create mode 100644 blackbox.yml create mode 100644 http.go delete mode 100644 probers/http.go delete mode 100644 probers/probers.go create mode 100644 tcp.go diff --git a/blackbox.yml b/blackbox.yml new file mode 100644 index 0000000..dee0bf0 --- /dev/null +++ b/blackbox.yml @@ -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 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 bd744ef..9ae8edf 100644 --- 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 index aaa00b8..0000000 --- a/probers/http.go +++ /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 index a4f710f..0000000 --- a/probers/probers.go +++ /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 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 +} -- 2.25.1