-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add regexp matching of HTTP response headers to the http probe #419
Changes from 9 commits
f6cdd9c
2c2a8ee
aa18657
b5c2f94
4bf57e1
6abb43e
4e794b3
7c95fb3
0ed9994
3527651
e7e7afb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,14 +58,30 @@ The other placeholders are specified separately. | |
# Probe fails if SSL is not present. | ||
[ fail_if_not_ssl: <boolean> | default = false ] | ||
|
||
# Probe fails if response matches regex. | ||
fail_if_matches_regexp: | ||
# Probe fails if response body matches regex. | ||
fail_if_body_matches_regexp: | ||
[ - <regex>, ... ] | ||
|
||
# Probe fails if response does not match regex. | ||
fail_if_not_matches_regexp: | ||
# Probe fails if response body does not match regex. | ||
fail_if_body_not_matches_regexp: | ||
[ - <regex>, ... ] | ||
|
||
# Probe fails if response header matches regex. For headers with multiple values, fails if *at least one* matches | ||
fail_if_header_matches: | ||
[ - [ header: <string>, | ||
[ regex: <regex>, ] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indentation seems off here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, does not look quite good. I got it from the prometheus config documentation, e.g. here when the same level contains both optional and non-optional parameters, then it looks like this (comments stripped): job_name: <job_name>
[ scrape_interval: <duration> | default = <global_config.scrape_interval> ] In here, though, the # Probe fails if response header matches regex. For headers with multiple values, fails if *at least one* matches.
fail_if_header_matches:
[ - [ header: <string>,
regexp: <regex>,
[ allow_missing: <boolean> | default = false ]
], ...
] Would that be OK? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking around, the only similar one we have is some of pagerduty for the AM. There we break it out as a different type. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another example right in this repo is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For query_response they're all optional though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, extracted only the http header match spec. |
||
[ allow_missing: <boolean> | default = false ] | ||
], ... | ||
] | ||
|
||
# Probe fails if response header does not match regex. For headers with multiple values, fails if *none* match | ||
fail_if_header_not_matches: | ||
[ - [ header: <string>, | ||
[ regex: <regex>, ] | ||
[ allow_missing: <boolean> | default = false ] | ||
], ... | ||
] | ||
|
||
# Configuration for TLS protocol of HTTP probe. | ||
tls_config: | ||
[ <tls_config> ] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
modules: | ||
http_headers: | ||
prober: http | ||
timeout: 5s | ||
http: | ||
fail_if_header_not_matches: | ||
- header: Access-Control-Allow-Origin | ||
allow_missing: false |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ import ( | |
"net/http" | ||
"net/http/cookiejar" | ||
"net/http/httptrace" | ||
"net/textproto" | ||
"net/url" | ||
"regexp" | ||
"strconv" | ||
|
@@ -44,7 +45,7 @@ func matchRegularExpressions(reader io.Reader, httpConfig config.HTTPProbe, logg | |
level.Error(logger).Log("msg", "Error reading HTTP body", "err", err) | ||
return false | ||
} | ||
for _, expression := range httpConfig.FailIfMatchesRegexp { | ||
for _, expression := range httpConfig.FailIfBodyMatchesRegexp { | ||
re, err := regexp.Compile(expression) | ||
if err != nil { | ||
level.Error(logger).Log("msg", "Could not compile regular expression", "regexp", expression, "err", err) | ||
|
@@ -55,7 +56,7 @@ func matchRegularExpressions(reader io.Reader, httpConfig config.HTTPProbe, logg | |
return false | ||
} | ||
} | ||
for _, expression := range httpConfig.FailIfNotMatchesRegexp { | ||
for _, expression := range httpConfig.FailIfBodyNotMatchesRegexp { | ||
re, err := regexp.Compile(expression) | ||
if err != nil { | ||
level.Error(logger).Log("msg", "Could not compile regular expression", "regexp", expression, "err", err) | ||
|
@@ -69,6 +70,68 @@ func matchRegularExpressions(reader io.Reader, httpConfig config.HTTPProbe, logg | |
return true | ||
} | ||
|
||
func matchRegularExpressionsOnHeaders(header http.Header, httpConfig config.HTTPProbe, logger log.Logger) bool { | ||
for _, headerMatchSpec := range httpConfig.FailIfHeaderMatchesRegexp { | ||
values := header[textproto.CanonicalMIMEHeaderKey(headerMatchSpec.Header)] | ||
if len(values) == 0 { | ||
if !headerMatchSpec.AllowMissing { | ||
level.Error(logger).Log("msg", "Missing required header", "header", headerMatchSpec.Header) | ||
return false | ||
} else { | ||
continue // no need to match any regex on missing headers | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "No", and full stop There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed here and in the other place. |
||
} | ||
} | ||
|
||
re, err := regexp.Compile(headerMatchSpec.Regexp) | ||
if err != nil { | ||
level.Error(logger).Log("msg", "Could not compile regular expression", "regexp", headerMatchSpec.Regexp, "err", err) | ||
return false | ||
} | ||
|
||
for _, val := range values { | ||
if re.MatchString(val) { | ||
level.Error(logger).Log("msg", "Header matched regular expression", "header", headerMatchSpec.Header, | ||
"regexp", headerMatchSpec.Regexp, "value_count", len(values)) | ||
return false | ||
} | ||
} | ||
} | ||
for _, headerMatchSpec := range httpConfig.FailIfHeaderNotMatchesRegexp { | ||
values := header[textproto.CanonicalMIMEHeaderKey(headerMatchSpec.Header)] | ||
if len(values) == 0 { | ||
if !headerMatchSpec.AllowMissing { | ||
level.Error(logger).Log("msg", "Missing required header", "header", headerMatchSpec.Header) | ||
return false | ||
} else { | ||
continue // no need to match any regex on missing headers | ||
} | ||
} | ||
|
||
re, err := regexp.Compile(headerMatchSpec.Regexp) | ||
if err != nil { | ||
level.Error(logger).Log("msg", "Could not compile regular expression", "regexp", headerMatchSpec.Regexp, "err", err) | ||
return false | ||
} | ||
|
||
anyHeaderValueMatched := false | ||
|
||
for _, val := range values { | ||
if re.MatchString(val) { | ||
anyHeaderValueMatched = true | ||
break | ||
} | ||
} | ||
|
||
if !anyHeaderValueMatched { | ||
level.Error(logger).Log("msg", "Header did not match regular expression", "header", headerMatchSpec.Header, | ||
"regexp", headerMatchSpec.Regexp, "value_count", len(values)) | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
// roundTripTrace holds timings for a single HTTP roundtrip. | ||
type roundTripTrace struct { | ||
tls bool | ||
|
@@ -320,7 +383,16 @@ 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.FailIfMatchesRegexp) > 0 || len(httpConfig.FailIfNotMatchesRegexp) > 0) { | ||
if success && (len(httpConfig.FailIfHeaderMatchesRegexp) > 0 || len(httpConfig.FailIfHeaderNotMatchesRegexp) > 0) { | ||
success = matchRegularExpressionsOnHeaders(resp.Header, httpConfig, logger) | ||
if success { | ||
probeFailedDueToRegex.Set(0) | ||
} else { | ||
probeFailedDueToRegex.Set(1) | ||
} | ||
} | ||
|
||
if success && (len(httpConfig.FailIfBodyMatchesRegexp) > 0 || len(httpConfig.FailIfBodyNotMatchesRegexp) > 0) { | ||
success = matchRegularExpressions(resp.Body, httpConfig, logger) | ||
if success { | ||
probeFailedDueToRegex.Set(0) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Full stop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added here and in other places