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]}