replacement: 127.0.0.1:9115 # The blackbox exporter's real hostname:port.
```
+HTTP probes can accept an additional `hostname` parameter that will set `Host` header and TLS SNI. This can be especially useful with `dns_sd_config`:
+```yaml
+scrape_configs:
+ - job_name: blackbox_all
+ metrics_path: /probe
+ params:
+ module: [ http_2xx ] # Look for a HTTP 200 response.
+ dns_sd_configs:
+ - names:
+ - example.com
+ - prometheus.io
+ type: A
+ port: 443
+ relabel_configs:
+ - source_labels: [__address__]
+ target_label: __param_target
+ replacement: https://$1/ # Make probe URL be like https://1.2.3.4:443/
+ - source_labels: [__param_target]
+ target_label: instance
+ - target_label: __address__
+ replacement: 127.0.0.1:9115 # The blackbox exporter's real hostname:port.
+ - source_labels: [__meta_dns_name]
+ target_label: __param_hostname # Make domain name become 'Host' header for probe requests
+ - source_labels: [__meta_dns_name]
+ target_label: vhost # and store it in 'vhost' label
+```
+
## Permissions
The ICMP probe requires elevated privileges to function:
return
}
+ hostname := params.Get("hostname")
+ if module.Prober == "http" && hostname != "" {
+ err = setHTTPHost(hostname, &module)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ }
+
sl := newScrapeLogger(logger, moduleName, target)
level.Info(sl).Log("msg", "Beginning probe", "probe", module.Prober, "timeout_seconds", timeoutSeconds)
h.ServeHTTP(w, r)
}
+func setHTTPHost(hostname string, module *config.Module) error {
+ // By creating a new hashmap and copying values there we
+ // ensure that the initial configuration remain intact.
+ headers := make(map[string]string)
+ if module.HTTP.Headers != nil {
+ for name, value := range module.HTTP.Headers {
+ if strings.Title(name) == "Host" && value != hostname {
+ return fmt.Errorf("host header defined both in module configuration (%s) and with URL-parameter 'hostname' (%s)", value, hostname)
+ }
+ headers[name] = value
+ }
+ }
+ headers["Host"] = hostname
+ module.HTTP.Headers = headers
+ return nil
+}
+
type scrapeLogger struct {
next log.Logger
buffer bytes.Buffer
import (
"bytes"
+ "fmt"
"net/http"
"net/http/httptest"
"strings"
}
}
}
+
+func TestHostnameParam(t *testing.T) {
+ headers := map[string]string{}
+ c := &config.Config{
+ Modules: map[string]config.Module{
+ "http_2xx": config.Module{
+ Prober: "http",
+ Timeout: 10 * time.Second,
+ HTTP: config.HTTPProbe{
+ Headers: headers,
+ IPProtocolFallback: true,
+ },
+ },
+ },
+ }
+
+ // check that 'hostname' parameter make its way to Host header
+ hostname := "foo.example.com"
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Host != hostname {
+ t.Errorf("Unexpected Host: expected %q, got %q.", hostname, r.Host)
+ }
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer ts.Close()
+
+ requrl := fmt.Sprintf("?debug=true&hostname=%s&target=%s", hostname, ts.URL)
+
+ req, err := http.NewRequest("GET", requrl, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rr := httptest.NewRecorder()
+
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ probeHandler(w, r, c, log.NewNopLogger(), &resultHistory{})
+ })
+
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Errorf("probe request handler returned wrong status code: %v, want %v", status, http.StatusOK)
+ }
+
+ // check that ts got the request to perform header check
+ if !strings.Contains(rr.Body.String(), "probe_success 1") {
+ t.Errorf("probe failed, response body: %v", rr.Body.String())
+ }
+
+ // check that host header both in config and in parameter will result in 400
+ c.Modules["http_2xx"].HTTP.Headers["Host"] = hostname + ".something"
+
+ handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ probeHandler(w, r, c, log.NewNopLogger(), &resultHistory{})
+ })
+
+ rr = httptest.NewRecorder()
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusBadRequest {
+ t.Errorf("probe request handler returned wrong status code: %v, want %v", status, http.StatusBadRequest)
+ }
+}
// If there is no `server_name` in tls_config, use
// the hostname of the target.
httpClientConfig.TLSConfig.ServerName = targetHost
+
+ // However, if there is a Host header it is better to use
+ // its value instead. This helps avoid TLS handshake error
+ // if targetHost is an IP address.
+ for name, value := range httpConfig.Headers {
+ if strings.Title(name) == "Host" {
+ httpClientConfig.TLSConfig.ServerName = value
+ }
+ }
}
client, err := pconfig.NewClientFromConfig(httpClientConfig, "http_probe", pconfig.WithKeepAlivesDisabled())
if err != nil {