Docker拉取镜像认证报文简析

来自三线的随记

有时候会有一些奇怪的镜像操作需要用到curl等工具去实现,遂有了本篇记录

这里主要记录集中与registry API交互过程中,auth -> get manifest info部分

针对 hub.docker.com 例子

" 一个 docker pull 指令会拉两部分,一部分是 manifest,一部分是 layer,前者指定了一个 image 相关的信息和 layer 的信息(一个 JSON 文件),后者就是一些大文件(layer)"

会用到的关联域名:

Location of Docker index: https://index.docker.io/

Location of the remote repository: https://registry-1.docker.io

实现:

假设需要拉取的镜像为alpine:3.17.0_rc1 即:

➤ docker pull alpine:3.17.0_rc1

那么在curl中的实现即为(是的,他有个library!):

➤ curl https://registry-1.docker.io/v2/library/alpine/manifests/3.17.0_rc1

这时候直接请求是会401的,要根据返回的www-authenticate header去进行认证

* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< content-type: application/json
< docker-distribution-api-version: registry/2.0
< www-authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/alpine:pull"
< date: Sun, 20 Nov 2022 09:08:33 GMT
< content-length: 157
< strict-transport-security: max-age=31536000
< docker-ratelimit-source: 8.210.192.22
<
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"library/alpine","Action":"pull"}]}]}
* Connection #1 to host registry-1.docker.io left intact

根据回显进行认证(匿名认证):

注意这里的service也是需要一一对应的,scope value可以不进行URL编码

➤ curl "https://auth.docker.io/token?scope=repository%3Alibrary%2Falpine%3Apull&service=registry.docker.io"
{"token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlDK1RDQ0FwK2dBd0lCQWdJQkFEQUtCZ2dxaGtqT1BRUURBakJHTVVRd1FnWURWUVFERXp0U1RVbEdPbEZNUmpRNlEwZFFNenBSTWtWYU9sRklSRUk2VkVkRlZUcFZTRlZNT2taTVZqUTZSMGRXV2pwQk5WUkhPbFJMTkZNNlVVeElTVEFlRncweU1qQXhNVEF5TWpJeE5EbGFGdzB5TXpBeE1qVXlNakl4TkRsYU1FWXhSREJDQmdOVkJBTVRPMUJaUTFJNlNWQmFRanBJUWxGWE9qZE1SVms2UWtGV1FqcEhTRGRhT2xWSVZ6TTZVa3RMVWpwRE4wNHpPbGxOTkZJNlNWaE5TRHBLVkZCQ01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBbE5JOGFtMlBERnZzNndOeVl2d0dkZWZXVXJXQWdMZ3N2MzA2MnZycG5VNkN0WUpEZDZ2K2NEVG1FN1FlTllEaFMyaU1wU3djZkRCL2RFclEwdnhIZE4ycDIvODZmZy9TeWlIMnhmMGFVTjlDV1dud0JPaTIvS3hLditpbFNlQ01HYXRwRlg3SmYxcWI4N0Q5NUxOVDBvOU9OTmYxT3RidjY5ck9tL1RIVFh3clUvV3dTZlUyWktUbEw4SVRXRkRXN09ZK3hXdUJ0WUpteVhqcVpsaWRBbUNTdTdHY0Y0MVB5em9KTFFTMnJCdXJwOXc0cWgxMFk1bUNIcWdsaEI1Rk9aOUs0T2pUaVhUUHJFUk5WcnArUFVIR3JBYVRPRTBwQzgyUHBuWVZhNzNDUkdsMEdDdC9RckJwVjRpdmswdzF0eEtkV1NiSDNnRmtqZ2g1N0tOcDhRSURBUUFCbzRHeU1JR3ZNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVBCZ05WSFNVRUNEQUdCZ1JWSFNVQU1FUUdBMVVkRGdROUJEdFFXVU5TT2tsUVdrSTZTRUpSVnpvM1RFVlpPa0pCVmtJNlIwZzNXanBWU0Zjek9sSkxTMUk2UXpkT016cFpUVFJTT2tsWVRVZzZTbFJRUWpCR0JnTlZIU01FUHpBOWdEdFNUVWxHT2xGTVJqUTZRMGRRTXpwUk1rVmFPbEZJUkVJNlZFZEZWVHBWU0ZWTU9rWk1WalE2UjBkV1dqcEJOVlJIT2xSTE5GTTZVVXhJU1RBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQTdIY1VyVm1namo1cE01MXhZVHd2eGE1VnRqd2hub0dRZjFxTU52UGVHeVlDSUFwYm4vWFkvS1F5WWFWRnRjMWtsb0lmZzd4L3hlbkZhbkp4L0F2cURGdFgiXX0.eyJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6ImxpYnJhcnkvYWxwaW5lIiwiYWN0aW9ucyI6WyJwdWxsIl0sInBhcmFtZXRlcnMiOnsicHVsbF9saW1pdCI6IjEwMCIsInB1bGxfbGltaXRfaW50ZXJ2YWwiOiIyMTYwMCJ9fV0sImF1ZCI6InJlZ2lzdHJ5LmRvY2tlci5pbyIsImV4cCI6MTY2ODkzNTY5MiwiaWF0IjoxNjY4OTM1MzkyLCJpc3MiOiJhdXRoLmRvY2tlci5pbyIsImp0aSI6ImRja3JfanRpX19FanZUOWtRS041YVJSZXUybE51WWROQU84VT0iLCJuYmYiOjE2Njg5MzUwOTIsInN1YiI6IiJ9.WcwQ-5wGEUUXsET0cqfWGwk-zAxx5HCSdRPNKi47oM04qeybNolZQejeFYsH-yiEEnQU8X0qoYSrJoy77xMaOAfTw_k2V4bYdKNct16g1WKpkQadd5hubIWShL59JblIhC21gfO30KcVcbsoZiepRKfCwCJ5vVm_Zt8BLsqIySTLnV-Vfyd_jCvSuNNpnOpC2r7ChTGTeUslRfMl2ZK4UGzCIy6rYwgcp4at5d_GLVyjZtS50WAmPlb9xBcawCZE3ulzGc7H9-OcmAtP04GV5exkQ9G34cfGdPvjKmwA1PkD6dQf2nsDE31LxeGqJcWdw7cYyGo31yPFHGJmQ905xQ","access_token":"xxxxxxxxxxxxxxx","expires_in":300,"issued_at":"2022-11-20T09:09:52.106027636Z"}

拿到需要的token,在http header中带上token,重新请求manifests信息

注意这里的实例是一种application/vnd.docker.distribution.manifest.v1+prettyjws类型的manifests信息(具体返回的是什么类型的镜像格式同样可以在http response header: content-type 看出来)

➤ curl https://registry-1.docker.io/v2/library/alpine/manifests/3.17.0_rc1 -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlDK1RDQ0FwK2dBd0lCQWdJQkFEQUtCZ2dxaGtqT1BRUURBakJHTVVRd1FnWURWUVFERXp0U1RVbEdPbEZNUmpRNlEwZFFNenBSTWtWYU9sRklSRUk2VkVkRlZUcFZTRlZNT2taTVZqUTZSMGRXV2pwQk5WUkhPbFJMTkZNNlVVeElTVEFlRncweU1qQXhNVEF5TWpJeE5EbGFGdzB5TXpBeE1qVXlNakl4TkRsYU1FWXhSREJDQmdOVkJBTVRPMUJaUTFJNlNWQmFRanBJUWxGWE9qZE1SVms2UWtGV1FqcEhTRGRhT2xWSVZ6TTZVa3RMVWpwRE4wNHpPbGxOTkZJNlNWaE5TRHBLVkZCQ01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBbE5JOGFtMlBERnZzNndOeVl2d0dkZWZXVXJXQWdMZ3N2MzA2MnZycG5VNkN0WUpEZDZ2K2NEVG1FN1FlTllEaFMyaU1wU3djZkRCL2RFclEwdnhIZE4ycDIvODZmZy9TeWlIMnhmMGFVTjlDV1dud0JPaTIvS3hLditpbFNlQ01HYXRwRlg3SmYxcWI4N0Q5NUxOVDBvOU9OTmYxT3RidjY5ck9tL1RIVFh3clUvV3dTZlUyWktUbEw4SVRXRkRXN09ZK3hXdUJ0WUpteVhqcVpsaWRBbUNTdTdHY0Y0MVB5em9KTFFTMnJCdXJwOXc0cWgxMFk1bUNIcWdsaEI1Rk9aOUs0T2pUaVhUUHJFUk5WcnArUFVIR3JBYVRPRTBwQzgyUHBuWVZhNzNDUkdsMEdDdC9RckJwVjRpdmswdzF0eEtkV1NiSDNnRmtqZ2g1N0tOcDhRSURBUUFCbzRHeU1JR3ZNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVBCZ05WSFNVRUNEQUdCZ1JWSFNVQU1FUUdBMVVkRGdROUJEdFFXVU5TT2tsUVdrSTZTRUpSVnpvM1RFVlpPa0pCVmtJNlIwZzNXanBWU0Zjek9sSkxTMUk2UXpkT016cFpUVFJTT2tsWVRVZzZTbFJRUWpCR0JnTlZIU01FUHpBOWdEdFNUVWxHT2xGTVJqUTZRMGRRTXpwUk1rVmFPbEZJUkVJNlZFZEZWVHBWU0ZWTU9rWk1WalE2UjBkV1dqcEJOVlJIT2xSTE5GTTZVVXhJU1RBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQTdIY1VyVm1namo1cE01MXhZVHd2eGE1VnRqd2hub0dRZjFxTU52UGVHeVlDSUFwYm4vWFkvS1F5WWFWRnRjMWtsb0lmZzd4L3hlbkZhbkp4L0F2cURGdFgiXX0.eyJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6ImxpYnJhcnkvYWxwaW5lIiwiYWN0aW9ucyI6WyJwdWxsIl0sInBhcmFtZXRlcnMiOnsicHVsbF9saW1pdCI6IjEwMCIsInB1bGxfbGltaXRfaW50ZXJ2YWwiOiIyMTYwMCJ9fV0sImF1ZCI6InJlZ2lzdHJ5LmRvY2tlci5pbyIsImV4cCI6MTY2ODkzNTY5MiwiaWF0IjoxNjY4OTM1MzkyLCJpc3MiOiJhdXRoLmRvY2tlci5pbyIsImp0aSI6ImRja3JfanRpX19FanZUOWtRS041YVJSZXUybE51WWROQU84VT0iLCJuYmYiOjE2Njg5MzUwOTIsInN1YiI6IiJ9.WcwQ-5wGEUUXsET0cqfWGwk-zAxx5HCSdRPNKi47oM04qeybNolZQejeFYsH-yiEEnQU8X0qoYSrJoy77xMaOAfTw_k2V4bYdKNct16g1WKpkQadd5hubIWShL59JblIhC21gfO30KcVcbsoZiepRKfCwCJ5vVm_Zt8BLsqIySTLnV-Vfyd_jCvSuNNpnOpC2r7ChTGTeUslRfMl2ZK4UGzCIy6rYwgcp4at5d_GLVyjZtS50WAmPlb9xBcawCZE3ulzGc7H9-OcmAtP04GV5exkQ9G34cfGdPvjKmwA1PkD6dQf2nsDE31LxeGqJcWdw7cYyGo31yPFHGJmQ905xQ"
{
   "schemaVersion": 1,
   "name": "library/alpine",
   "tag": "3.17.0_rc1",
   "architecture": "amd64",
   "fsLayers": [
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:7b26c2f269ea232d8dd57ee7696652248b61fda9653d7c7f06a9fea2bd2f009e"
      }
   ],
   "history": [
      {
         "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\"],\"Image\":\"sha256:118ada46def154efd2774e3211fef3356ad1f268e143dba1136af919d7670b0b\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"container\":\"bcb0af1ba8750c9284eb4ae69a2d444d288ccc718e2f9d329f34bbbd06deaad5\",\"container_config\":{\"Hostname\":\"bcb0af1ba875\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) \",\"CMD [\\\"/bin/sh\\\"]\"],\"Image\":\"sha256:118ada46def154efd2774e3211fef3356ad1f268e143dba1136af919d7670b0b\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"created\":\"2022-11-16T22:19:19.655513526Z\",\"docker_version\":\"20.10.12\",\"id\":\"fecb21f6263ecee0ce8a3d6c50c432b455c23f8afb7ebe929d54f6ca520b032b\",\"os\":\"linux\",\"parent\":\"f163dc23345785e0ac6ebc8d6c6262f19f2565ca420f0cd194c09e1029317881\",\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"f163dc23345785e0ac6ebc8d6c6262f19f2565ca420f0cd194c09e1029317881\",\"created\":\"2022-11-16T22:19:19.533331062Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:a7bed337c392105c21fe1c7067e51f5d0dedbc488b2be5daf17a18783fa29d04 in / \"]}}"
      }
   ],
   "signatures": [
      {
         "header": {
            "jwk": {
               "crv": "P-256",
               "kid": "GLVQ:OCEF:4TWE:RS63:BQJ5:VXYZ:EK7Y:DZQG:QNIT:4ZN3:PLT5:CBSK",
               "kty": "EC",
               "x": "Z4i5ZWH8GizJjOrkPpUAPT1lZH8frbh6XVlZZ6rwXtA",
               "y": "EuvtBs7t1sKcHzX2t5WOsXAyT8jnuetPK_IsojxGb0E"
            },
            "alg": "ES256"
         },
         "signature": "gJk_3VSshExIjeBeaLhprRpriQMANg5C-5inX-mBexjGHiAKTRjeg2c-JOcHnFpku_rdQFAEJdyLfCBkTFDKeA",
         "protected": "eyJmb3JtYXRMZW5ndGgiOjIxMDEsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMi0xMS0yMFQwOToxMzoxNVoifQ"
      }
   ]
}

ps: 对于不存在的tag, hub.docker.com返回的是(http code: 404)

{"errors":[{"code":"MANIFEST_UNKNOWN","message":"manifest unknown","detail":"unknown tag=3.17.0_rc2"}]}

题外话: 手动认证请求registry的 /_catalog接口的例子

先匿名请求接口,遇到401

# curl 10.10.217.241/v2/_catalog -v

* About to connect() to 10.10.217.241 port 80 (#0)
*   Trying 10.10.217.241...
* Connected to 10.10.217.241 (10.10.217.241) port 80 (#0)
> GET /v2/_catalog HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 10.10.217.241
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Server: nginx
< Date: Wed, 24 May 2023 10:32:57 GMT
< Content-Type: application/json; charset=utf-8
< Content-Length: 145
< Connection: keep-alive
< Docker-Distribution-Api-Version: registry/2.0
< Www-Authenticate: Bearer realm="http://10.10.217.241/registry/token",service="BUILDIN-REGISTRY",scope="registry:catalog:*"
< X-Content-Type-Options: nosniff
< Docker-Distribution-Api-Version: registry/2.0
<
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
* Connection #0 to host 10.10.217.241 left intact

获取token:

注意这里的service也是需要一一对应的,scope value可以不进行URL编码

# curl "http://10.10.217.241/registry/token?service=BUILDIN-REGISTRY&scope=registry:catalog:*" -u admin:password-password

{
    "token":"xxxx-token-x"
}

带上token,成功请求:

# curl 10.10.217.241/v2/_catalog -H "Authorization: Bearer xxxx-token-x"

{"repositories":[xxxxxxxxxxxxxx]}

Related articles