diff --git a/README.md b/README.md index 2281412..872a3f9 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,11 @@ configuration file with `--config.file`. The file is written in yaml format, defined by the schema below. ``` +# The default module to use. If omitted, then the module must be provided by the +# 'module' query parameter +default_module: + +# Module configuration modules: [] ``` @@ -257,6 +262,10 @@ modules: [] # The type of probe (https, tcp, file, kubernetes, kubeconfig) prober: +# The probe target. If set, then the 'target' query parameter is ignored. +# If omitted, then the 'target' query parameter is required. +target: + # How long the probe will wait before giving up. [ timeout: ] diff --git a/config/config.go b/config/config.go index c6c63ad..f7c4c53 100644 --- a/config/config.go +++ b/config/config.go @@ -15,7 +15,8 @@ var ( // DefaultConfig is the default configuration that is used when no // configuration file is provided DefaultConfig = &Config{ - map[string]Module{ + DefaultModule: "tcp", + Modules: map[string]Module{ "tcp": Module{ Prober: "tcp", }, @@ -59,12 +60,14 @@ func LoadConfig(confFile string) (*Config, error) { // Config configures the exporter type Config struct { - Modules map[string]Module `yaml:"modules"` + DefaultModule string `yaml:"default_module"` + Modules map[string]Module `yaml:"modules"` } // Module configures a prober type Module struct { Prober string `yaml:"prober,omitempty"` + Target string `yaml:"target,omitempty"` Timeout time.Duration `yaml:"timeout,omitempty"` TLSConfig TLSConfig `yaml:"tls_config,omitempty"` HTTPS HTTPSProbe `yaml:"https,omitempty"` diff --git a/examples/ssl_exporter.yaml b/examples/ssl_exporter.yaml index dd9d7fc..b5f491e 100644 --- a/examples/ssl_exporter.yaml +++ b/examples/ssl_exporter.yaml @@ -1,3 +1,4 @@ +default_module: https modules: https: prober: https @@ -34,6 +35,9 @@ modules: starttls: smtp file: prober: file + file_ca_certificates: + prober: file + target: /etc/ssl/certs/ca-certificates.crt kubernetes: prober: kubernetes kubernetes_kubeconfig: diff --git a/ssl_exporter.go b/ssl_exporter.go index 2051c53..4c53fe7 100644 --- a/ssl_exporter.go +++ b/ssl_exporter.go @@ -27,7 +27,11 @@ const ( func probeHandler(logger log.Logger, w http.ResponseWriter, r *http.Request, conf *config.Config) { moduleName := r.URL.Query().Get("module") if moduleName == "" { - moduleName = "tcp" + moduleName = conf.DefaultModule + if moduleName == "" { + http.Error(w, "Module parameter must be set", http.StatusBadRequest) + return + } } module, ok := conf.Modules[moduleName] if !ok { @@ -60,10 +64,13 @@ func probeHandler(logger log.Logger, w http.ResponseWriter, r *http.Request, con ctx, cancel := context.WithTimeout(r.Context(), timeout) defer cancel() - target := r.URL.Query().Get("target") + target := module.Target if target == "" { - http.Error(w, "Target parameter is missing", http.StatusBadRequest) - return + target = r.URL.Query().Get("target") + if target == "" { + http.Error(w, "Target parameter is missing", http.StatusBadRequest) + return + } } probeFunc, ok := prober.Probers[module.Prober] diff --git a/ssl_exporter_test.go b/ssl_exporter_test.go index e0073df..95387b3 100644 --- a/ssl_exporter_test.go +++ b/ssl_exporter_test.go @@ -51,7 +51,7 @@ func TestProbeHandler(t *testing.T) { } } -// TestProbeHandler tests that the probe handler sets the ssl_probe_success and +// TestProbeHandlerFail tests that the probe handler sets the ssl_probe_success and // ssl_prober metrics correctly when the probe fails func TestProbeHandlerFail(t *testing.T) { rr, err := probe("localhost:6666", "", config.DefaultConfig) @@ -70,6 +70,133 @@ func TestProbeHandlerFail(t *testing.T) { } } +// TestProbeHandlerDefaultModule tests the default module is used correctly +func TestProbeHandlerDefaultModule(t *testing.T) { + server, _, _, caFile, teardown, err := test.SetupHTTPSServer() + if err != nil { + t.Fatalf(err.Error()) + } + defer teardown() + + server.StartTLS() + defer server.Close() + + conf := &config.Config{ + DefaultModule: "https", + Modules: map[string]config.Module{ + "tcp": config.Module{ + Prober: "tcp", + TLSConfig: config.TLSConfig{ + CAFile: caFile, + }, + }, + "https": config.Module{ + Prober: "https", + TLSConfig: config.TLSConfig{ + CAFile: caFile, + }, + }, + }, + } + + rr, err := probe(server.URL, "", conf) + if err != nil { + t.Fatalf(err.Error()) + } + + // Should have used the https prober + if ok := strings.Contains(rr.Body.String(), "ssl_prober{prober=\"https\"} 1"); !ok { + t.Errorf("expected `ssl_prober{prober=\"https\"} 1`") + } + + conf.DefaultModule = "" + + rr, err = probe(server.URL, "", conf) + if err != nil { + t.Fatalf(err.Error()) + } + + // It should fail when there's no default module + if rr.Code != http.StatusBadRequest { + t.Errorf("expected code: %d, got: %d", http.StatusBadRequest, rr.Code) + } + +} + +// TestProbeHandlerTarget tests the target module parameter is used correctly +func TestProbeHandlerDefaultTarget(t *testing.T) { + server, _, _, caFile, teardown, err := test.SetupHTTPSServer() + if err != nil { + t.Fatalf(err.Error()) + } + defer teardown() + + server.StartTLS() + defer server.Close() + + conf := &config.Config{ + Modules: map[string]config.Module{ + "https": config.Module{ + Prober: "https", + Target: server.URL, + TLSConfig: config.TLSConfig{ + CAFile: caFile, + }, + }, + }, + } + + // Should use the target in the module configuration + rr, err := probe("", "https", conf) + if err != nil { + t.Fatalf(err.Error()) + } + + // Check probe success + if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 1"); !ok { + t.Errorf("expected `ssl_probe_success 1`") + } + + // Check prober metric + if ok := strings.Contains(rr.Body.String(), "ssl_prober{prober=\"https\"} 1"); !ok { + t.Errorf("expected `ssl_prober{prober=\"https\"} 1`") + } + + // Should ignore a different target in the target parameter + rr, err = probe("localhost:6666", "https", conf) + if err != nil { + t.Fatalf(err.Error()) + } + + // Check probe success + if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 1"); !ok { + t.Errorf("expected `ssl_probe_success 1`") + } + + // Check prober metric + if ok := strings.Contains(rr.Body.String(), "ssl_prober{prober=\"https\"} 1"); !ok { + t.Errorf("expected `ssl_prober{prober=\"https\"} 1`") + } + + conf.Modules["tcp"] = config.Module{ + Prober: "tcp", + TLSConfig: config.TLSConfig{ + CAFile: caFile, + }, + } + + rr, err = probe("", "tcp", conf) + if err != nil { + t.Fatalf(err.Error()) + } + + // It should fail when there's no target in the module configuration or + // the query parameters + if rr.Code != http.StatusBadRequest { + t.Errorf("expected code: %d, got: %d", http.StatusBadRequest, rr.Code) + } +} + func probe(target, module string, conf *config.Config) (*httptest.ResponseRecorder, error) { uri := "/probe?target=" + target if module != "" {