Skip to content

Commit

Permalink
Add regular expression matching on headers
Browse files Browse the repository at this point in the history
Signed-off-by: Richard Mitchell <[email protected]>
  • Loading branch information
mitchellrj committed Jun 11, 2018
1 parent 40dd02e commit 80a451d
Show file tree
Hide file tree
Showing 5 changed files with 394 additions and 12 deletions.
14 changes: 14 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ The other placeholders are specified separately.
fail_if_not_matches_regexp:
[ - <regex>, ... ]

# Probe fails if response header matches regex.
fail_if_header_matches_regexp:
[ - header: <header-name>
pattern: <regex>
required: <bool>
... ]

# Probe fails if response does not header matches regex.
fail_if_header_not_matches_regexp:
[ - header: <header-name>
pattern: <regex>
required: <bool>
... ]

# Configuration for TLS protocol of HTTP probe.
tls_config:
[ <tls_config> ]
Expand Down
32 changes: 20 additions & 12 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,28 @@ type Module struct {
DNS DNSProbe `yaml:"dns,omitempty"`
}

type HTTPHeaderMatch struct {
Header string `yaml:"header,omitempty"`
Pattern string `yaml:"pattern,omitempty"`
Required bool `yaml:"pattern,omitempty"`
}

type HTTPProbe struct {
// Defaults to 2xx.
ValidStatusCodes []int `yaml:"valid_status_codes,omitempty"`
ValidHTTPVersions []string `yaml:"valid_http_versions,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"`
FailIfBodyMatchesRegexp []string `yaml:"fail_if_matches_regexp,omitempty"`
FailIfBodyNotMatchesRegexp []string `yaml:"fail_if_not_matches_regexp,omitempty"`
Body string `yaml:"body,omitempty"`
HTTPClientConfig config.HTTPClientConfig `yaml:"http_client_config,inline"`
ValidStatusCodes []int `yaml:"valid_status_codes,omitempty"`
ValidHTTPVersions []string `yaml:"valid_http_versions,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"`
FailIfBodyMatchesRegexp []string `yaml:"fail_if_matches_regexp,omitempty"`
FailIfBodyNotMatchesRegexp []string `yaml:"fail_if_not_matches_regexp,omitempty"`
FailIfHeaderMatchesRegexp []HTTPHeaderMatch `yaml:"fail_if_header_matches_regexp,omitempty"`
FailIfHeaderNotMatchesRegexp []HTTPHeaderMatch `yaml:"fail_if_header_not_matches_regexp,omitempty"`
Body string `yaml:"body,omitempty"`
HTTPClientConfig config.HTTPClientConfig `yaml:"http_client_config,inline"`
}

type QueryResponse struct {
Expand Down
9 changes: 9 additions & 0 deletions example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ modules:
- "Could not connect to database"
fail_if_not_matches_regexp:
- "Download the latest version here"
fail_if_not_matches_regexp:
- "Download the latest version here"
fail_if_header_matches_regexp:
- header: server
pattern: "Apache"
fail_if_header_not_matches_regexp:
- header: server
pattern: "nginx"
required: true
tls_config:
insecure_skip_verify: false
preferred_ip_protocol: "ip4" # defaults to "ip6"
Expand Down
55 changes: 55 additions & 0 deletions prober/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,46 @@ import (
"github.com/prometheus/blackbox_exporter/config"
)

func matchHeaderRegularExpressions(headers http.Header, httpConfig config.HTTPProbe, logger log.Logger) bool {
for _, matcher := range httpConfig.FailIfHeaderMatchesRegexp {
key := http.CanonicalHeaderKey(matcher.Header)
if headerValues, ok := headers[key]; ok {
re, err := regexp.Compile(matcher.Pattern)
if err != nil {
level.Error(logger).Log("msg", "Could not compile regular expression", "regexp", key, matcher.Pattern, "err", err)
return false
}
for _, value := range headerValues {
if re.MatchString(value) {
level.Error(logger).Log("msg", "Header matched regular expression", "regexp", key, value, matcher.Pattern)
return false
}
}
} else if matcher.Required {
return false
}
}
for _, matcher := range httpConfig.FailIfHeaderNotMatchesRegexp {
key := http.CanonicalHeaderKey(matcher.Header)
re, err := regexp.Compile(matcher.Pattern)
if headerValues, ok := headers[key]; ok {
if err != nil {
level.Error(logger).Log("msg", "Could not compile regular expression", "regexp", key, matcher.Pattern, "err", err)
return false
}
for _, value := range headerValues {
if !re.MatchString(value) {
level.Error(logger).Log("msg", "Header did not match regular expression", "regexp", key, value, matcher.Pattern)
return false
}
}
} else if matcher.Required {
return false
}
}
return true
}

func matchBodyRegularExpressions(reader io.Reader, httpConfig config.HTTPProbe, logger log.Logger) bool {
body, err := ioutil.ReadAll(reader)
if err != nil {
Expand Down Expand Up @@ -169,6 +209,11 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
Help: "Returns the version of HTTP of the probe response",
})

probeFailedDueToHeaderRegex = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "probe_failed_due_to_header_regex",
Help: "Indicates if probe failed due to header regex",
})

probeFailedDueToBodyRegex = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "probe_failed_due_to_regex",
Help: "Indicates if probe failed due to body regex",
Expand All @@ -185,6 +230,7 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
registry.MustRegister(isSSLGauge)
registry.MustRegister(statusCodeGauge)
registry.MustRegister(probeHTTPVersionGauge)
registry.MustRegister(probeFailedDueToHeaderRegex)
registry.MustRegister(probeFailedDueToBodyRegex)

httpConfig := module.HTTP
Expand Down Expand Up @@ -315,6 +361,15 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
level.Info(logger).Log("msg", "Invalid HTTP response status code, wanted 2xx", "status_code", resp.StatusCode)
}

if success && (len(httpConfig.FailIfHeaderMatchesRegexp) > 0 || len(httpConfig.FailIfHeaderNotMatchesRegexp) > 0) {
success = matchHeaderRegularExpressions(resp.Header, httpConfig, logger)
if success {
probeFailedDueToHeaderRegex.Set(0)
} else {
probeFailedDueToHeaderRegex.Set(1)
}
}

if success && (len(httpConfig.FailIfBodyMatchesRegexp) > 0 || len(httpConfig.FailIfBodyNotMatchesRegexp) > 0) {
success = matchBodyRegularExpressions(resp.Body, httpConfig, logger)
if success {
Expand Down
Loading

0 comments on commit 80a451d

Please sign in to comment.