Skip to content

Check if response has unformatted error details#4674

Closed
cpuguy83 wants to merge 3 commits intocontainerd:mainfrom
cpuguy83:docker_errors_details
Closed

Check if response has unformatted error details#4674
cpuguy83 wants to merge 3 commits intocontainerd:mainfrom
cpuguy83:docker_errors_details

Conversation

@cpuguy83
Copy link
Copy Markdown
Member

In some cases a registry may not return a formaatted message, in this
case try to get error details from the response body as a simple string.

Closes #4672

@cpuguy83
Copy link
Copy Markdown
Member Author

Ping @thaJeztah @tonistiigi WDYT?

@cpuguy83 cpuguy83 force-pushed the docker_errors_details branch from e71df13 to 3880fa9 Compare October 29, 2020 17:04
@theopenlab-ci
Copy link
Copy Markdown

theopenlab-ci bot commented Oct 29, 2020

Build succeeded.

@estesp
Copy link
Copy Markdown
Member

estesp commented Oct 29, 2020

Looks like there is some string matching in TestDockerFetcherOpen that is broken by this change

@cpuguy83
Copy link
Copy Markdown
Member Author

It like the error handling is just incorrect anyway.
I've actually noticed containerd is always missing error details.

The specific case of 429 returns an object rather than an array:

{
  "details": "Too Many Requests. Please see https://docs.docker.com/docker-hub/download-rate-limit/",
  "error": {
    "details": "Too Many Requests. Please see https://docs.docker.com/docker-hub/download-rate-limit/"
  }
}

@cpuguy83
Copy link
Copy Markdown
Member Author

But handling the case of this possibly being just an unencoded message is probably OK as well, e.g. maybe the registry is in a degraded state.

@estesp
Copy link
Copy Markdown
Member

estesp commented Oct 29, 2020

What I'm confused about is that it was "fixed" in #3109 (I'm pretty sure @bainsy88 confirmed at the time that the full error message was getting passed through after that PR), and shouldn't have been broken by #3173, at least on its face, since it is still trying to decode into the same value, but with the more canonical approach of letting the decoder read the stream rather than the "ReadAll(..)".

@thaJeztah
Copy link
Copy Markdown
Member

Is it because the registry in this case returns application/json, but a format that doesn't match?

@estesp
Copy link
Copy Markdown
Member

estesp commented Oct 29, 2020

Oh, I see Brian's point--the response is not an array, so it's not going to decode into Errors; could that have changed at some point with DockerHub? Or maybe the original PR from the IBM registry team still works properly with ICCR/icr.io responses, which may properly be encoded as a JSON array

@cpuguy83
Copy link
Copy Markdown
Member Author

We could update UnmarshalJSON for Errors to check for this case.

`Errors` here should used lowercase `errors` in the json.

This should fix issues where error details are not present when the
registry returns a non-OK response.

Signed-off-by: Brian Goff <[email protected]>
Makes suere we do the same thing in either case and do our best to get
an error.

Also adds an additional fallback for plain text error responses.

Signed-off-by: Brian Goff <[email protected]>
@cpuguy83 cpuguy83 force-pushed the docker_errors_details branch from 3880fa9 to a8dd01d Compare October 29, 2020 23:10
@cpuguy83
Copy link
Copy Markdown
Member Author

Ok, updated this with some commits split up.

  1. The error unmarshalling was incorrect to begin with. It was looking for Errors in json instead of errors, which explains why I've seen missing error details when using containerd libs.
  2. Add fallback to handle:
    i. If error is a single error instead of a { Errors: []Error }
    ii. Additional fallback for legacy behavior like https://github.com/docker/distribution/blob/749f6afb4572201e3c37325d0ffedb6f32be8950/registry/client/errors.go#L48-L52
  3. Coalesce error handling for resolve and fetch, with an additional fallback if the error is plain text.

@cpuguy83
Copy link
Copy Markdown
Member Author

$ bin/ctr image pull docker.io/ratelimitalways/test:latest
docker.io/ratelimitalways/test:latest:                                            resolved       |++++++++++++++++++++++++++++++++++++++| 
manifest-sha256:c2d41d2ba6d8b7b4a3ffec621578eb4d9a0909df29dfa2f6fd8a2e5fd0836aed: downloading    |--------------------------------------|    0.0 B/527.0 B 
elapsed: 2.6 s                                                                    total:   0.0 B (0.0 B/s)                                         
ctr: failed to copy: httpReaderSeeker: failed open: unexpected status code https://registry-1.docker.io/v2/ratelimitalways/test/manifests/sha256:c2d41d2ba6d8b7b4a3ffec621578eb4d9a0909df29dfa2f6fd8a2e5fd0836aed: 429 Too Many Requests - Server message: unknown: Too Many Requests. Please see https://docs.docker.com/docker-hub/download-rate-limit/

@theopenlab-ci
Copy link
Copy Markdown

theopenlab-ci bot commented Oct 29, 2020

Build succeeded.

@thaJeztah
Copy link
Copy Markdown
Member

curious; where is the unknown coming from in Server message: unknown ?

@cpuguy83
Copy link
Copy Markdown
Member Author

It's from the errcode being 0, since the server is not giving us one in the json.

Copy link
Copy Markdown
Member

@estesp estesp left a comment

Choose a reason for hiding this comment

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

LGTM

@crosbymichael
Copy link
Copy Markdown
Member

LGTM but I'll let @dmcgowan do the final check since he knows this better than I do.

continue
}
return "", ocispec.Descriptor{}, errors.Errorf("unexpected status code %v: %v", u, resp.Status)
return "", ocispec.Descriptor{}, getResponseErr(ctx, req, resp)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is a resp.Body.Close() a few lines above this

@thaJeztah
Copy link
Copy Markdown
Member

Should we decide wether or not we must support the invalid JSON response?

@thaJeztah
Copy link
Copy Markdown
Member

i.e.; I agree that plain text responses should be returned; mostly wondering where the "backward compat" part in moby / distribution came from, and if it's still relevant (not taking the current Docker Hub response into account) #4672 (comment)

if err2 != nil {
log.G(ctx).WithError(err2).Debug("Error copying response body details")
}
return errors.Errorf("unexpected status code %v: %v, details: %s", req.String(), resp.Status, sb)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would rather us type this error, shoveling the raw http body into an error string still doesn't seem right. The cases this occurs is if the registry is not implementing the same error handling format or the response is from middleware. The output is more likely to be mangled html than a valid error message.

We could wrap the registry error type in a response error which holds the request, status code, registry error type. In this case, we can use the unknown registry error type and let the caller decide if it wants to present details.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The output is more likely to be mangled html than a valid error message.

Distribution spec is a bit sketchy on that ("If the response body is in JSON format"); i.e. it mentions the body to be JSON format, but doesn't mention wether or not that requires the content-type to be application/json (i.e., should a text/plain response be attempted to parse as JSON?)

We could wrap the registry error type in a response error which holds the request, status code, registry error type. In this case, we can use the unknown registry error type and let the caller decide if it wants to present details.

You mean; preserve the body, but convert it to a "valid" format? I think that would work; at least the information would not be lost, and clients should be able to handle the standard format (how to handle other formats is somewhat "unspecified": "A 4XX response code from the registry MAY return a body in any format" )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@tonistiigi additional thoughts on the above? ^^

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@dmcgowan That's a fair point. It also kinda sucks to expect all callers to be aware of this to make that determination.
I'd be fine dropping that, or possibly checking explicitly text/plain as the content type.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you just dump the body into the log and mention the status code is unexpected and the error is not formatted.

@thaJeztah
Copy link
Copy Markdown
Member

quick search in the docker/distribution repository; distribution/distribution@ec636bb distribution/distribution#1379

distribution/distribution#1249 changed token fetching to parse HTTP error response bodies as serialized errcodes. However, Docker Hub's authentication endpoint does not return error bodies in this format. To work around this, convert its format into ErrCodeUnauthorized or ErrCodeUnknown.

fixes / related to distribution/distribution#1373
related to moby/moby#19476

@dmcgowan dmcgowan added the status/needs-update Awaiting contributor update label May 27, 2021
@estesp
Copy link
Copy Markdown
Member

estesp commented Jan 18, 2022

this PR now has conflicts with main; given the last few comments, is there a path forward with @dmcgowan's suggestion of a typed error and/or logging the body content?

@dmcgowan
Copy link
Copy Markdown
Member

There is still a tracking issue, going to close this for now. We do need to address better remote error response handling still.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status/needs-update Awaiting contributor update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DockerFetcher swallows non-JSON errors returned by registries

5 participants