Joxit/docker-registry-ui

UI showing same sha256 content digest for all tags + Delete is not working

cr1st1p opened this issue ยท 10 comments

Using UI version v1.4.0 - inside a kubernetes cluster.
After fixing Cors headers problem, I still face 3 issues when looking at an image.

Bug description

  1. looking at an image, I see the same content digest all over. I will attach a screenshot.
  2. when I try to delete an image, the wrong digest is used for the http DELETE call, mainly that duplicated ID. I get a 404 error back, "Manifest unknown".
    3- the UI does NOT show any kind of information when the DELETE call fails with that 404 error

Docker registry: version 2.7.1

Checking on the net, it looks like what needs to be given for deletion, is the value of the http header
'docker-content-digest', from when you retrieved each individual tag's manifest.
I succeeded when I did that with a curl command but with the digest from the http response headers - that's why there's no tag '1' in the screenshot, for example.

Also, that sha256 digest is probably the digest to show in the screen with the tags available for the image (i.e. in the page where I take the screenshot from).
Probably fixing 1) will automatically fix 2).

How to Reproduce

To reproduce, try deleting a docker image tag, after using the software versions I described.

Expected behavior

I would expect, for each case:

  1. correct content digest for each tag to be displayed
  2. DELETE to work
  3. in case it receives some 404 error, to show an error.

Screenshots

same-digest

System information

  • OS: Manjaro (Arch based Linux distro)
  • Browser:
    • Name: Chromium
    • Version: 79.0.*
  • Docker registry UI:
    • Version: 1.4.0
    • Interface variant: ?? [standard or static]
    • Server: docker (inside kubernetes)
    • Docker version: 18.0*
    • OS/Arch: linux/amd64
    • Tools: kubernetes
Joxit commented

Hi,
Thanks for your issue ๐Ÿ™‚

Can I see a screenshot of your network console ? I want to see the Response Headers of the request 3 (http:///v2/cristi/backup/manifests/3). Like this one :

issue-116

As you can see in my screenshot, the digest header value is already used by the UI.

Can I see your registry configuration ? (at least the http part)

Can you reproduce your issue without kubernetes ? I tried the delete feature (without kubernetes) and it works like a charm.

in case it receives some 404 error, to show an error.

I will fix the 404 toast asap.

Attached screenshot of the headers for that manifest. As you see, you get the good manifest in the header, but it is not used in the display (and later, in the delete API call)
console

the config.yml file, given as ConfigMap in kubernetes:

  config.yml: "health:\n  storagedriver:\n    enabled: true\n    interval: 10s\n    threshold:
    3\nhttp:\n  addr: :5000\n  headers:\n    Access-Control-Expose-Headers:\n    -
    Docker-Content-Digest\n    X-Content-Type-Options:\n    - nosniff\nlog:\n  fields:\n
    \   service: registry\nstorage:\n  cache:\n    blobdescriptor: inmemory\nversion:
    0.1"

Other than that, http auth is done by kubernetes (nginx ingress controller), also CORS headers as well - because Docker Registry is buggy for that. And SSL termination as well - by nginx.

Trying to reproduce without kubernetes - too much of an effort for now - sorry.

Joxit commented

Hum... Ok... That's odd...
Try to pull the newest image of the UI and see if it works (version 1.4.2).

If the issue is still present, can I see the result of these two queries :

curl  -v 'https://registry-url/v2/cristi/backup/manifests/2' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json'
curl  -v 'https://registry-url/v2/cristi/backup/manifests/3' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json'

Do you see where the value sha256:73... come from ? It has to come from somewhere... Maybe another image in your registry ?

Are you using a server-side cache ?

There are some examples using docker-compose here : https://github.com/Joxit/docker-registry-ui/tree/master/examples

You were not very clear about where to find 1.4.2, so I just pulled tag '1.4-static' again, from dockerhub. It seems to be 1.4.3
Behavior is the same.

Attached, the curl output for those 2 requests.
curl1.txt
curl2.txt
As you can see, there's nowhere that displayed ID.

I also did a find . -iname 'THATID' inside the docker repo - nothing.
I also did a fgrep -ri , still nothing.
Used also Chrome with no extension loaded, no caching proxy.

A quick look at the source, I guess that only 'Http.prototype.getContentDigest' could possibly be the source of the problem. I wonder if it isn't computing the checksum on something else than it should.
Like on a string reference that is long time changed, since it is callback based.

Let me know how I can run it from sources.
Thank you!

Joxit commented

Sorry, I did the 1.4.3 release after my message (for the delete message) ๐Ÿ™‚

There are two ways to get the sha256:

  1. With the header Docker-Content-Digest
  2. With the crypto function in our browsers

Http.prototype.getContentDigest = function(cb) {
if (this.oReq.hasHeader('Docker-Content-Digest')) {
// Same origin or advanced CORS headers set:
// 'Access-Control-Expose-Headers: Docker-Content-Digest'
cb(this.oReq.getResponseHeader('Docker-Content-Digest'))
} else if (window.crypto && window.TextEncoder) {
crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(this.oReq.responseText)
).then(function (buffer) {
cb(
'sha256:' + Array.from(
new Uint8Array(buffer)
).map(function(byte) {
return byte.toString(16).padStart(2, '0');
}).join('')
);
})
} else {
// IE and old Edge
// simply do not call the callback and skip the setup downstream
}
};

Your headers seem correct... And I tried the crypto function with your first curl response and I have the same value as your header, try it:

crypto.subtle.digest(
      'SHA-256',
      new TextEncoder().encode(`{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
   "config": {
      "mediaType": "application/vnd.docker.container.image.v1+json",
      "size": 5442,
      "digest": "sha256:875048dc040156399acd0b2d961f19d9b0c379d3166ea2edb4340638ef2d6986"
   },
   "layers": [
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 32174695,
         "digest": "sha256:84ed7d2f608f8a65d944b40132a0333069302d24e9e51a6d6b338888e8fd0a6b"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 843,
         "digest": "sha256:be2bf1c4a48dae92d5a1b8aa319c8767fa491316fb80da52de80378264599a16"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 509,
         "digest": "sha256:a5bdc630309340a3154f37e17c00a61c28c476107656e8d6744d2ba9af980058"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 163,
         "digest": "sha256:e9055237d68d011bb90d49096b637b3b6c5c7251f52e0f2a2a44148aec1181dc"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 1660,
         "digest": "sha256:a3812c4d536da945bafb5f2252c3f29d12fe67d9c6e717845ba1f200f99e9d3e"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 314,
         "digest": "sha256:a2990df452746ce7e684a0b107e4aceb4241dcd313898d3c75590543b4c245d7"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 9472990,
         "digest": "sha256:40ab844b9d6cd89f65f6e67edde551d5b415780314506c0a9740e04462180fef"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 663,
         "digest": "sha256:55241b6aa4e55ae85a67e2aaec19ba41659675cd1fe33fa33da3ef0961f1a831"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 526,
         "digest": "sha256:8359133dfbfe63098696e2b7287462d31c418638237f311fe8cda7b5988229b2"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 76624538,
         "digest": "sha256:3e63b71d0b30cc890f93ebb35ee4f3f752bedfab7e5744eabae01823179475b8"
      }
   ]
}`)
    ).then(function (buffer) {
console.log('sha256:' + Array.from(
          new Uint8Array(buffer)
        ).map(function(byte) {
          return byte.toString(16).padStart(2, '0');
        }).join('')
)})

If you want to debug with sources, here is what you need to do : https://github.com/Joxit/docker-registry-ui#basic
Then use the DevTool of chrome and add breakpoints in the getContentDigest function from http.js file and here :

opts.image.one('content-digest', function(digest) {
self.digest = digest;
opts.image.on('content-digest-chars', self.onResize);
opts.image.trigger('get-content-digest-chars');
});

Is the wrong sha256 always the same in the UI ?

The problem comes from the fact that if you get on your original request a 401 response - that is, a docker registry asking for the authentication, you build a NEW request, this time, with the authorization header. But you do NOT overwrite, as I think you should, the .oReq' variable.
And in the 'loadend' event, of the new request, which is copied from the original, you have
' oReq.getContentDigest(function (digest) { '

Further demonstration, with the output of the first call, that returns 401:

cat | sha256sum
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"cristi/backup","Action":"pull"}]}]}
73d0f75c356dbc9091a80764dffa14d674a5dde41919bfdf6025a8caa889663c  -

One suggestion - pay attention when fixing this - if you also want to do #117
To me it suggests that you should refactor code to have a single factory method for creating the docker registry http request, probably with auth values as parameters.

Joxit commented

๐Ÿ˜ฑ Well done ! Thank you a lot for your deep investigation ! I will try to fix this ASAP !

Joxit commented

Hi there! I think the issue will be fixed with 1.4.4, pull again the image ;)

I added your name in the contributor list https://joxit.dev/docker-registry-ui/CONTRIBUTORS ๐ŸŽ‰

Thank you again for your issue report ๐Ÿ˜„

Issue is fixed, thank you! ๐Ÿ‘