go-containerregistry: `crane delete` requests token with insufficient scope
I’m the lead developer on a cloud-hosted registry implementation called Keppel. I received a complaint from a customer that they cannot crane delete images stored in Keppel. I reproduced the issue; it boils down to Crane guessing the wrong token scope. When invoked as crane delete keppel.example.com/foo/bar:qux, it just assumes the required token scope is repository:foo/bar:push,pull, even though repository:foo/bar:delete would be required.
For reference, here’s a trace of what crane delete keppel.example.com/foo/bar:qux does, as recorded by mitmdump. (The actual hostnames, repository names, and tokens have been redacted.) Note that the final request, the actual DELETE, indicates the correct token scope, but Crane ignores this information.
$ mitmdump -p 8888 --ssl-insecure --flow-detail 3
Proxy server listening at http://*:8888
[::1]:48300: clientconnect
[::1]:48300: GET https://keppel.example.com/v2/ HTTP/2.0
:authority: keppel.example.com
accept-encoding: gzip
user-agent: Go-http-client/2.0
<< 401 104b
server: nginx/1.15.6
date: Wed, 27 May 2020 14:04:27 GMT
content-type: application/json
content-length: 104
docker-distribution-api-version: registry/2.0
vary: Origin
www-authenticate: Bearer realm="https://keppel.example.com/keppel/v1/auth",service="keppel.example.com"
strict-transport-security: max-age=15724800; includeSubDomains
{
"errors": [
{
"code": "UNAUTHORIZED",
"detail": null,
"message": "no bearer token found in request headers"
}
]
}
[::1]:48300: GET https://keppel.example.com/keppel/v1/auth?scope=repository%3Afoo%2Fbar%3Apush%2Cpull&service=keppel.example.com HTTP/2.0
:authority: keppel.example.com
authorization: Basic password-redacted
user-agent: go-containerregistry
accept-encoding: gzip
scope: repository:foo/bar:push,pull
service: keppel.example.com
<< 200 1.65k
server: nginx/1.15.6
date: Wed, 27 May 2020 14:04:27 GMT
content-type: application/json
vary: Accept-Encoding
vary: Origin
strict-transport-security: max-age=15724800; includeSubDomains
content-encoding: gzip
{
"expires_in": 3600,
"issued_at": "2020-05-27T14:04:27Z",
"token": "token-was-redacted"
}
[::1]:48300: DELETE https://keppel.example.com/v2/foo/bar/manifests/qux HTTP/2.0
:authority: keppel.example.com
authorization: Bearer token-was-redacted
user-agent: go-containerregistry
accept-encoding: gzip
<< 403 117b
server: nginx/1.15.6
date: Wed, 27 May 2020 14:04:27 GMT
content-type: application/json
content-length: 117
docker-distribution-api-version: registry/2.0
vary: Origin
www-authenticate: Bearer realm="https://keppel.example.com/keppel/v1/auth",service="keppel.example.com",scope="repository:foo/bar:delete",error="insufficient_scope"
strict-transport-security: max-age=15724800; includeSubDomains
{
"errors": [
{
"code": "DENIED",
"detail": null,
"message": "token does not cover scope repository:foo/bar:delete"
}
]
}
[::1]:48300: clientdisconnect
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 3
- Comments: 17
Use of the
deletescope is widespread enough that it might be worth just adopting it… this was changed a while back: https://github.com/docker/distribution/commit/ccb839e0e30c3b6992fb4084dfd6550d0ddd4d1aAnnoyingly, this would break
crane deletefor GCR. We should adopt the approach of parsing the challenge and requesting the correct scope.Done. See commit linked above.