-
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
Add regexp matching of HTTP response headers to the http probe #419
Conversation
Signed-off-by: Gleb Smirnov <[email protected]>
Signed-off-by: Gleb Smirnov <[email protected]>
Signed-off-by: Gleb Smirnov <[email protected]>
Oh dear, I just checked the list of open pull requests and found not one, but TWO others with the same changes: #62 and #332 😕 Apparently they were abandoned by their authors before all the comments were addressed. Looking at the comments from @brian-brazil, it seems like all of them are addressed in this PR now. The "check that header is missing" can be achieved by
|
config/config.go
Outdated
Method string `yaml:"method,omitempty"` | ||
Headers map[string]string `yaml:"headers,omitempty"` | ||
FailIfMatchesRegexp []string `yaml:"fail_if_matches_regexp,omitempty"` | ||
FailIfNotMatchesRegexp []string `yaml:"fail_if_not_matches_regexp,omitempty"` |
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.
Should these two above be renamed to FailIfBodyMatchesRegexp
and FailIfBodyNotMatchesRegexp
?
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.
Yes, I would rename them.
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.
Done
Thanks for picking up where those other PRs went stale. Per your above comment, I think you want the regexp to match fail_if_header_matches_regexp:
- header: Not-Supposed-To-Be-Here
allow_missing: true
regexp: '.+' EDIT: Are headers with no value after the header name valid? |
Signed-off-by: Gleb Smirnov <[email protected]>
If I read RFC 7230 section 3.2 correctly, header value is not optional, but an empty value is valid. That seems to be the generally accepted interpretation, judging by the discussions on the Internet that I found. However, This leads to another consideration: as per section section 3.2.2, there may be multiple header fields with the same field name. Judging by the doc on I would propose the following behaviour:
I have doubts about the behaviour in (2) for the "fail if does not match" for multi-value headers. "fail if none match" may be more desirable than "fail if any does not match". What do you think? |
Signed-off-by: Gleb Smirnov <[email protected]>
After some consideration, the most common use of multi-value headers is probably
fail_if_header_not_matches_regexp:
header: Set-Cookie
regexp: '.*Domain=\.example\.com.*'
fail_if_header_matches_regexp:
header: Set-Cookie
allow_missing: true
regexp: '.*' Another common multi-value header is I pushed the changes implementing the behaviour described above. Please let me know if you think it is reasonable. |
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.
for consistency, but that would be a breaking change and thus does not seem like a good idea.
It's probably worth it.
We can always reconsider handling of multiple headers if there's use cases.
prober/http.go
Outdated
@@ -173,6 +236,11 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr | |||
Help: "Indicates if probe failed due to regex", | |||
}) | |||
|
|||
probeFailedDueToHeaders = prometheus.NewGauge(prometheus.GaugeOpts{ | |||
Name: "probe_failed_due_to_headers", |
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.
probe_http_
falied_due_regex should already cover this
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.
probe_http
Thee are several other labels that do not have the http
: probe_ssl_earliest_cert_expiry
and probe_failed_due_to_regex
. Should they be changed, too? Or can I just ditch the newly introduced probe_failed_due_to_headers
and keep the rest as is?
EDIT: formatting
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.
They should be changed, but at least the ssl one is commonly used for alerting so we need to be a little careful so not now.
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.
OK, I will not touch the existing labels.
But I am not sure if representing the failed header matches with probe_failed_due_to_regex
is the right way. It would be helpful if the user could see immediately from the metrics where the failure originated from. I do not see any drawbacks to this either (the extra disk usage would be negligible).
If you do insist, I can reuse probe_failed_due_to_regex
. Do you insist?
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.
I'd suggest reusing, either that or have two more clearly named metrics.
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.
OK, reused existing metric. Renaming existing ones would be a more dangerous breaking change indeed.
prober/http.go
Outdated
} | ||
} | ||
for _, headerMatchSpec := range httpConfig.FailIfHeaderNotMatchesRegexp { | ||
values := textproto.MIMEHeader(header)[headerMatchSpec.Header] |
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.
This won't canonicalise the key
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.
Fixed and added tests
Signed-off-by: Gleb Smirnov <[email protected]>
Signed-off-by: Gleb Smirnov <[email protected]>
Alright, done. The breaking change would be caught at the configuration parsing stage, so it's a fail-fast scenario. |
config/config.go
Outdated
Headers map[string]string `yaml:"headers,omitempty"` | ||
FailIfBodyMatchesRegexp []string `yaml:"fail_if_body_matches_regexp,omitempty"` | ||
FailIfBodyNotMatchesRegexp []string `yaml:"fail_if_body_not_matches_regexp,omitempty"` | ||
FailIfHeaderMatchesRegexp []HeaderMatch `yaml:"fail_if_header_matches_regexp,omitempty"` |
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.
This isn't a string, so I'd remove the _regexp from the name to avoid confusion.
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.
Where would the confusion arise from? Is there a convention that properties ending with _regexp
are always strings that represent regular expression?
I could rename the properties so it would be like this:
# ...
fail_if_body_matches_regexp:
- 'Thou shall not pass'
fail_if_header_matches:
- header: Host
regexp: example.com
It would lose the consistency between property names for header and body, though. Is it worth it?
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.
Yes, the data structures are different
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.
Done
Signed-off-by: Gleb Smirnov <[email protected]>
Signed-off-by: Gleb Smirnov <[email protected]>
@brian-brazil @SuperQ is there anything else I can do to get this merged? I would love to use the upstream version instead of own fork. |
CONFIGURATION.md
Outdated
[ - <regex>, ... ] | ||
|
||
# Probe fails if response header matches regex. For headers with multiple values, fails if *at least one* matches |
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
CONFIGURATION.md
Outdated
# 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 comment
The 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 comment
The 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 regex
should be named regexp
and be made non-optional, so it would look like this:
# 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Another example right in this repo is query_response
in tcp_probe
, it does not break it out as a separate type. That's why I did not do it here. Can extract both or keep inlined like above
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.
For query_response they're all optional though.
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.
OK, extracted only the http header match spec.
prober/http.go
Outdated
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed here and in the other place.
Signed-off-by: Gleb Smirnov <[email protected]>
Huh, travis build failed when fetching dependencies. Is it possible to rerun it? |
@brian-brazil @SuperQ sorry to pester, but I ask again: anything else I can do to get this merged? |
|
||
```yml | ||
header: <string>, | ||
regexp: <regex>, |
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.
This is optional
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.
Why is it optional? It is not possible to match a header without a regex to match against.
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.
Unless you allow it to be missing
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.
But if you allow it to be missing, then what is the point of having a match rule at all?
fail_if_header_matches:
- header: Transfer-Encoding
allow_missing: true
fail_if_header_not_matches:
- header: Transfer-Encoding
allow_missing: true
Neither of these rules make sense to me. But looking at the checks made in config.go
, apparently they used to. So I think I should change the config check, so it always required regexp
to be set, regardless of allow_missing
.
Would that be OK?
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.
In a stroke of speculative execution, did just that.
Signed-off-by: Gleb Smirnov <[email protected]>
Thanks! |
Thanks for a thorough code review! I appreciate it. Is there any estimate of when the next version will be released? I’m fine building from upstream source, asking for the sake of completeness. |
It's not going to be today, and there's a breaking change in there so Friday is a bad idea. Probably early next week. |
So now example.yml should be fixed, because for "http" prober option "fail_if_body_matches_regexp" fails and this is very confusing:
|
@piotrkochan how should it be fixed? Where do you get this error? I would guess that you are running the old version of @brian-brazil any way I can help with making a release? This is indeed confusing now. |
#382 (comment) is what's holding a release up. |
I needed to add the verification of header values for some of the targets probed by the blackbox exporter, but apparently matching is currently only supported for body content.
This pull request adds two new config options:
fail_if_header_matches_regexp
andfail_if_header_not_matches_regexp
, mirroring the existingfail_if_matches_regexp
andfail_if_not_matches_regexp
for the response body. I considered renaming these ones tofail_if_body_matches_regexp
andfail_if_body_not_matches_regexp
for consistency, but that would be a breaking change and thus does not seem like a good idea.I am fairly new to golang, so I just tried to follow the style and apparent conventions in the rest of this project. Please let me know if something should be changed.
I also tested this extensively manually, it worked in all the corner cases I tried.