Skip to content
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

Adds explicit SavedObjectsRespository error type for 404 that do not originate from Elasticsearch responses #107104

Conversation

TinaHeiligers
Copy link
Contributor

@TinaHeiligers TinaHeiligers commented Jul 28, 2021

Summary

Resolves #102353
There may be cases where a 404 can be returned during calls to the Saved Objects Repository for reasons other than the saved object not being found.

The Saved Objects service should distinguish between a Saved Object is not found and other unknown reasons that do not originate from responses to an Elasticsearch call.

This PR adds an explicit 503 NotFoundEsUnavailable Error that will wrap 404 errors from get, update and delete requests when the response does not contain the x-elastic-product: Elasticsearch header.

Checklist

Delete any items that are not applicable to this PR.

Risk Matrix

Risk Probability Severity Mitigation/Notes
False positives— An intermediate proxy doesn't forward x-elastic-product header . Low Low As of 7.15.0 Kibana won't start unless the required headers are present. For 7.14 patches, the type of error returned could be a 404 or a 503 but we will still get an error

For maintainers

Copy link
Contributor Author

@TinaHeiligers TinaHeiligers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial comments to reviewers.

const notFoundError = this.createGenericNotFoundError(type, id);
return this.decorateEsUnavailableError(
new Error(`${notFoundError.message}`),
`x-elastic-product not present or not recognized`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is the best text string to use as a descriptor for the "missing" header. I took inspiration from the client's team but made it a lot more specific. We need to get Product's input on wording if we choose to follow this implementation.

public static isNotFoundEsUnavailableError(error: Error | DecoratedError) {
return (
isSavedObjectsClientError(error) &&
error[code] === CODE_ES_UNAVAILABLE &&
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically set a status code of 503 to indicate an ES availability error. This overrides the 404 we would otherwise have thrown.

@TinaHeiligers TinaHeiligers force-pushed the so-errors/improve-response-to-not-found branch from df0c75c to e1e93eb Compare July 29, 2021 01:56
Copy link
Contributor

@joshdover joshdover left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great. I really only have feedback on which error we'd like to raise in this case and subsequently, the condition we should use to throw this error.

An integration test for this would be really nice to have but it's not going to be simple. We'd need to setup a proxy server in front of Elasticsearch that allows us to 'hi-jack' the response to test this condition. I imagine this would be easier to accomplish in a jest integration test than with the FTR. As a starting place, I'd experiment with using a custom Hapi server with the h2o2 plugin (same one we use for the bath path proxy). We can also do this as a follow up if needed.

@@ -202,4 +202,23 @@ export class SavedObjectsErrorHelpers {
public static isGeneralError(error: Error | DecoratedError) {
return isSavedObjectsClientError(error) && error[code] === CODE_GENERAL_ERROR;
}

public static createGenericNotFoundEsUnavailableError(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to differentiate this error from isEsUnavailableError? I'm inclined to say that we want applications to handle this condition in the same way they would handle any other issue reaching Elasticsearch. I'm not sure if there's anything different a consumer would or can do in this case vs. any other isEsUnavailableError. Another benefit of using the existing error is that any existing code that is handling it will also handle this new case.

Curious what @mshustov thinks as well.

Copy link
Contributor

@mshustov mshustov Jul 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, it's ES server is unavailable rather than a Kibana entity is not found.
A user reacts to them in different ways: in the case of a Kibana entity is not found, the user needs to adjust the request params and try again.
While in the case of ES server is unavailable, the error is not actionable: the user needs to contact an admin to address the availability problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially, I did try throwing isEsUnavailableError but that returns a boolean, not an actual Error or DecoratedError. If you mean that we should rather throw a decorated EsUnavailableError that will return true on checking that the error is isEsUnavailable, then we can do that.
If I'm mis-reading the types here, please let me know!.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f you mean that we should rather throw a decorated EsUnavailableError that will return true on checking that the error is isEsUnavailable, then we can do that.

Yeah, I was thinking about something like this. @joshdover Did you mean the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially replied with this but then deleted it again. I'm adding it back for more context on the error that I'm using:

createGenericNotFoundEsUnavailableError will return a 503 error that adds the message from a NotFoundError to the EsUnavailableError. The decorated error that's thrown will assert true to a check for isEsUnavailableError, since the latter returns a boolean for the statusCode being 503.

const indexNotFound = statusCode === 404;

// check if we have the elasticsearch header when doc.found is not true (false, undefined or null) and if we do, ensure it is Elasticsearch
if (!isFoundGetResponse(body) && !isEsHeaderValid(headers)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we decide to use the same isEsUnavailable error (see comment above), then I think we should simplify this condition to only if (!isEsHeaderValid(headers)) to catch this issue more broadly. Same for the other repository functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isEsUnavailable does not return an error. It returns a boolean.

Copy link
Contributor Author

@TinaHeiligers TinaHeiligers Jul 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, what we're hoping to accomplish with this work is to differentiate between a saved object not actually being found (404) vs the response returning a 404 because ES is unavailable.

For the initial work (this PR), we're only scoping the work to checking if the response is from ES when we get a 404, rather than a larger check on all or any responses.

We will need to implement something for ensuring all responses received are valid ES responses in the long run but IMO, is too risky to try and get in for a patch release.

* @returns boolean
*/
const isEsHeaderValid = (headers: Record<string, unknown> | null) => {
return headers && headers['x-elastic-product']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fairly certain this is the case, but have we validated that the casing of the header names are always normalized to lowercase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! I did do a cursory check but we could handle the case by casting it to lowercase before checking the condition to be safe.

return (
isSavedObjectsClientError(error) &&
error[code] === CODE_ES_UNAVAILABLE &&
error.message.startsWith('x-elastic-product not present or not recognized:')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need : at the end?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. Good catch!

// check if we have the elasticsearch header when doc.found is not true (false, undefined or null) and if we do, ensure it is Elasticsearch
const esHeaderFound = isEsHeaderValid(headers);

if (!isFoundGetResponse(body) && !esHeaderFound) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Might it lead to false positives if an intermediate proxy doesn't forward x-elastic-product header? In v7.14, we do allow Kibana starts even if x-elastic-product header is missing. Kibana won't start unless the header presents starting from v7.15 #105557
Does it mean that we cannot backport this change to v7.14.1?

Copy link
Contributor Author

@TinaHeiligers TinaHeiligers Jul 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might it lead to false positives if an intermediate proxy doesn't forward x-elastic-product header?

If the header is missing from the response then yes, we would get 503 errors rather than 404 (which is what we're getting now). We would, however, still be getting an error because ATM, the header check is only done if we are already getting a 404 response.

If we did backport the work to 7.14.1, then there is a chance we'd get false positives but I don't know how likely that would be. We rely on headers for other Kibana functionality (e.g. accessing internal indices). In what cases wouldn't a proxy forward response headers on?

@TinaHeiligers
Copy link
Contributor Author

TinaHeiligers commented Jul 29, 2021

An integration test for this would be really nice to have but it's not going to be simple. We'd need to setup a proxy server in front of Elasticsearch that allows us to 'hi-jack' the response to test this condition. I imagine this would be easier to accomplish in a jest integration test than with the FTR. As a starting place, I'd experiment with using a custom Hapi server with the h2o2 plugin (same one we use for the bath path proxy). We can also do this as a follow up if needed.

++ to adding a jest integration test. I also agree adding one as a follow up would be better because, as you say, it's not going to be simple.

@TinaHeiligers TinaHeiligers marked this pull request as ready for review July 30, 2021 01:24
@TinaHeiligers TinaHeiligers requested a review from a team as a code owner July 30, 2021 01:24
@YulNaumenko YulNaumenko self-requested a review July 30, 2021 08:26
@TinaHeiligers
Copy link
Contributor Author

TinaHeiligers commented Jul 30, 2021

@mshustov @joshdover I've either addressed or answered your initial comments. Could you please take another look at the PR? I've kept the scope small for this one in the hope that we can get it in for the 7.14.1 patch.
@YulNaumenko Would this work for your case? The changes have been applied to get, update and delete.

I'm not sure what's up with all those doc changes. I'll investigate and potentially open a new PR if needed. Converting back to draft to figure this out. Sorry folks!

@TinaHeiligers TinaHeiligers marked this pull request as draft July 30, 2021 14:23
@TinaHeiligers
Copy link
Contributor Author

@elasticmachine merge upstream

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Unknown metric groups

API count

id before after diff
core 2363 2368 +5

API count missing comments

id before after diff
core 1081 1086 +5

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@TinaHeiligers
Copy link
Contributor Author

@joshdover @mshustov @YulNaumenko I've opened a new PR without all those weird doc changes. Sorry about that!

@TinaHeiligers TinaHeiligers deleted the so-errors/improve-response-to-not-found branch October 14, 2021 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add explicit error type for SavedObjectsRepository for 404 errors that do not originate from Elasticsearch
4 participants