-
Notifications
You must be signed in to change notification settings - Fork 402
docker: mimic docker upstream registry authentication #211
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
Conversation
43aa484
to
faebe39
Compare
@mtrmac tested this out for |
ping := url.URL{ | ||
Host: req.URL.Host, | ||
Scheme: req.URL.Scheme, | ||
Path: "/v2/", |
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.
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.
we don't make the extra call to retrieve the header - which is likely fixing https://bugzilla.redhat.com/show_bug.cgi?id=1413987 also (I'm contacting the op to test this also)
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.
@mtrmac bottom line is, this is taken directly from docker (well, the way not the actual code). I believe if it works there, it'll work here as well. And again, this is removing a problematic request that we were making when setting up authentication to the registries.
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.
funny thing is that #191 actually fixed https://bugzilla.redhat.com/show_bug.cgi?id=1413987 but then #195 broke it again :)
From a quick look I can't see how this is fixing the "we don’t know what authentication will be necessary for a specific URL in advance” issue of #191 / #195 ; AFAICT all this PR ultimately does, compared to the pre-#191 state, is that for getting It seems that I am overlooking something critical about this PR; what is it? |
this is not doing an extra request to check for authentication but just mimic what docker does (and gcr works with docker also) |
@@ -446,8 +416,10 @@ func (c *dockerClient) ping() (*pingResponse, error) { | |||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized { | |||
return nil, errors.Errorf("error pinging repository, response code %d", resp.StatusCode) | |||
} | |||
chs := parseAuthHeader(resp.Header) | |||
c.saveChallenges(chs, resp.Request.URL) |
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 the only saveChallenges
call everywhere ever; if that’s all that is needed, we can go back to a single dockerClient.wwwAuthenticate
and have the code noticeably simpler (no map, no URL normalization)
/me watches too |
faebe39
to
4e410de
Compare
To add more context and debug, @mtrmac I have a skopeo branch with c/image pre-#191 + debug #201:
@mtrmac it seems clear that the call to There's no doubt |
@mtrmac as for #195, here's the request stack (c/image w/o #195 + #201 for debugging):
I believe this is just another case of "gcr.io behaves really wrong...". Full stop. |
Note that both issues go away with this PR (#211). I'm leaning towards behaving like docker/docker which didn't have this sort of issues with gcr.io (since gcr.io apparently respects /v2/ with
|
4e410de
to
f1ea4b4
Compare
Rebased, @mtrmac would you take a look at this? |
AHEM.....what's that CI error?! Tests are running just fine on my laptop |
distribution/distribution@6170ac5 (+ distribution/distribution@dbc336e ). Looking into that now. On the positive note, we may be able to get rid of |
f1ea4b4
to
4e59d5f
Compare
rebased again, should be good to review 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.
ACK on the general approach. Feel free to merge if this is urgent; if not I will do a detailed review later today or on Monday.
} | ||
return addr | ||
} | ||
|
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.
None of the additions in this file are used.
type requestOptions struct { | ||
remoteName string | ||
actions string | ||
} |
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.
requestOptions
feels rather generic. Call this authScope
, or, perhaps preferably, just use 2 string parameters?
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.
authScope
sounds fine, will change 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.
on a second though, makeRequest
and makeRequestToResolvedURL
already take too many param IMO. I would rather compact those options (and others) in a generic requestOptions
struct. Mandatory arguments continue to be passed as function arguments though.
eca4631
to
1e168f0
Compare
@mtrmac I've reworked a bit |
type authScope struct { | ||
remoteName string | ||
actions string | ||
} |
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.
Actually, looking at this more, the authScope
is fixed for the life of a dockerClient
, and remoteName
can be computed from a dockerReference
; so can we set the scope in newDockerClient
and drop it from makeRequest
* entirely? Sorry for not noticing this earlier.
Would then having the requestOptions
still be worthwhile?
scope authScope | ||
headers map[string][]string | ||
stream io.Reader | ||
} |
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 don’t really feel strongly about introducing this… having named optional parameters is nice, I suppose. Though if we end up having 2 or 3, it is not a big deal.
c.scheme = pr.scheme | ||
} | ||
|
||
url = fmt.Sprintf(baseURL, c.scheme, c.registry) + url | ||
return c.makeRequestToResolvedURL(method, url, headers, stream, -1, true) | ||
return c.makeRequestToResolvedURL(method, url, opts, -1, true) | ||
} | ||
|
||
// makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client. | ||
// streamLen, if not -1, specifies the length of the data expected on stream. |
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.
If we do requestOptions
, streamLen
should be probably in there as well (but then every caller with a non-nil stream
will have to take care to set streamLen
to -1
).
@@ -446,8 +399,9 @@ func (c *dockerClient) ping() (*pingResponse, error) { | |||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized { | |||
return nil, errors.Errorf("error pinging repository, response code %d", resp.StatusCode) | |||
} | |||
c.challenges = parseAuthHeader(resp.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 feels unclean; other values are only set via the pr
structure, and c.challenges
differs for no obvious reason.
(We could probably get rid of pingResponse
altogether, have the nested function return a (*http.Response, scheme string, error)
, and move the resp.Header.Get()
calls and the data transfers which now happen at the top of makeRequest
to the successful termination path of ping
. Not sure whether that would be the cleanest way, it’s just one of many options. Just adding challenges
to pingResponse
would also work. Or dropping pingReponse
and making all the c.
assignments in here.)
@@ -150,7 +164,8 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI | |||
// TODO: check inputInfo.Digest == computedDigest https://github.com/containers/image/pull/70#discussion_r77646717 | |||
locationQuery.Set("digest", computedDigest.String()) | |||
uploadLocation.RawQuery = locationQuery.Encode() | |||
res, err = d.c.makeRequestToResolvedURL("PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, true) | |||
opts.stream = nil |
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 reuse of opts
and having to correct it works but it’s also a bit ugly; it means having to think about the state of opts
, instead of just using it as a single-shot named parameters.)
}, | ||
headers: headers, | ||
} | ||
res, err := s.c.makeRequest("GET", url, opts) |
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 wouldn’t be opposed to (but won’t insist on) inlining this:
res, err := s.c.makeRequeset("GET", url, requestOptions{
…
})
(and the same throughout, if scope
is left anywhere.)
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.
sorry, I meant “… (and the same throughout, if opts
is left after we possibly remove scope
from it.)"
// code is not 401. | ||
// We just skip this case as it's not standard on docker/distribution | ||
// registries (https://github.com/docker/distribution/blob/master/docs/spec/api.md#api-version-check) | ||
if res.StatusCode != http.StatusUnauthorized { |
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.
Some similarly explicit comments should remain in this method, explaining at least that we intentionally use the WWW-Authenticate
values from /v2/
and not from the destination URL, which we completely ignore, because 1) that’s what docker does and 2) because gcr.io is sending 401-without-WWW-Authenticate in the real requests, and pointing to dumps in this PR.).
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
a5852be
to
dbf8fec
Compare
Layer compression
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.
Thanks!
Two minor nits, but feel free to merge as is.
LGTM pending tests.
scope authScope | ||
} | ||
|
||
type authScope struct { |
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.
Consider inlining the fields into dockerClient
directly, but this is perfectly fine as well.
if c.wwwAuthenticate != "" && sendAuth { | ||
if err := c.setupRequestAuth(req); err != nil { | ||
if sendAuth { | ||
if err := c.setupRequestAuth(req, c.scope.remoteName, c.scope.actions); err != nil { |
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.
c.scope
* does not have to be a parameter, the method has direct access to c
.
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, overlooked this
dbf8fec
to
3716a78
Compare
this should be good to go as well, giving I'm not sure if we prefer getting #223 in before |
👍, but I’d prefer waiting on #223 if that can be done reasonably quickly. |
Signed-off-by: Antonio Murdaca <[email protected]>
3716a78
to
35b00eb
Compare
@mtrmac should be OK to pull this in now. |
A registry is expected to provide a scope value when proposing a challenge. If it fails to do so, it is possible to infer it from the user request. Currently, Podman and Docker do this because they use the challenge provided by a "ping" request to fetch a Bearer token, when possible. This was done in Podman just to allow compatibility with gcr.io. See [1] for reference. [1] containers/image#211 Signed-off-by: Athos Ribeiro <[email protected]>
A registry is expected to provide a scope value when proposing a challenge. If it fails to do so, it is possible to infer it from the user request. Currently, Podman and Docker do this because they use the challenge provided by a "ping" request to fetch a Bearer token, when possible. This was done in Podman just to allow compatibility with gcr.io. See [1] for reference. [1] containers/image#211 Signed-off-by: Athos Ribeiro <[email protected]>
Signed-off-by: Antonio Murdaca [email protected]