Docker Registry 重定向的問題
不知道有多少人遇到我這個問題。當Docker的Registry伺服器返回301、302 Redirect時,特定條件下,Docker客戶端跟隨返回的新地址時,認證信息會失效。導致後續請求都被視作匿名的未授權請求處理。
這是什麼原因呢?這個特定條件又是指什麼呢?
原來Docker客戶端在做HTTP請求時,使用的是golang的默認http客戶端。在golang的http客戶端文檔中,明確指出,golang在跟隨重定向響應的新地址時,針對像Authorization、WWW-Authenticate和Cookie這類HTTP Header,會有一個域安全性校驗。如果新地址的域名和老地址不一樣,並且不是老地址的子域名時,跟隨請求會丟掉這些HTTP頭,以保障請求安全性。
When following redirects, the Client will forward all headers set on the initial Request except:
? when forwarding sensitive headers like "Authorization", "WWW-Authenticate", and "Cookie" to untrusted targets. These headers will be ignored when following a redirect to a domain that is not a subdomain match or exact match of the initial domain. For example, a redirect from "http://foo.com" to either "http://foo.com" or "http://sub.foo.com" will forward the sensitive headers, but a redirect to "http://bar.com" will not.
這裡提到了域和子域。那麼針對域名滿足條件,但是埠不滿足條件的情況(比如從http://foo.com重定向到http://foo.com:8080),Go怎麼處理的呢?我們跟進Go的源代碼看看。相關代碼位於https://golang.org/src/net/http/client.go這個文件中,從Client.Do()開始,依次調用copyHeaders() => makeHeadersCopier() => shouldCopyHeaderOnRedirect(),我們來看看最後這個函數的源代碼:
func shouldCopyHeaderOnRedirect(headerKey string, initial, dest *url.URL) bool { switch CanonicalHeaderKey(headerKey) { case "Authorization", "Www-Authenticate", "Cookie", "Cookie2": // Permit sending auth/cookie headers from "foo.com" // to "sub.foo.com". // Note that we dont send all cookies to subdomains // automatically. This function is only used for // Cookies set explicitly on the initial outgoing // client request. Cookies automatically added via the // CookieJar mechanism continue to follow each // cookies scope as set by Set-Cookie. But for // outgoing requests with the Cookie header set // directly, we dont know their scope, so we assume // its for *.domain.com. // TODO(bradfitz): once issue 16142 is fixed, make // this code use those URL accessors, and consider // "http://foo.com" and "http://foo.com:80" as // equivalent? // TODO(bradfitz): better hostname canonicalization, // at least once we figure out IDNA/Punycode (issue // 13835). ihost := strings.ToLower(initial.Host) dhost := strings.ToLower(dest.Host) return isDomainOrSubdomain(dhost, ihost) } // All other headers are copied: return true } // isDomainOrSubdomain reports whether sub is a subdomain (or exact // match) of the parent domain. // // Both domains must already be in canonical form. func isDomainOrSubdomain(sub, parent string) bool { if sub == parent { return true } // If sub is "foo.example.com" and parent is "example.com", // that means sub must end in "."+parent. // Do it without allocating. if !strings.HasSuffix(sub, parent) { return false } return sub[len(sub)-len(parent)-1] == . }
從代碼里可以看出,判斷重定向時特殊的Header是否複製上,直接是通過Host欄位字元串匹配進行的。而且代碼中的TODO說的很清楚,針對帶埠的請求,這裡暫時沒作處理。等#16142這個issue修復後再做處理。但是這裡的處理,應該僅僅是針對默認的80埠,將帶80埠和不帶80埠的域視作同一個域。而其他埠則不作處理。
也就是說,如果你原始請求是http://test.com,新請求是http://test.com:8080,那麼這個時候是不會作為同一個域處理,Header會丟棄。
如果原始請求是http://test.com,新請求是http://test.com:80。現階段是會作為不同域處理,後續等16142的issue修復後,這裡會修復,修復後作為同域處理。
這裡回到最開始我們說的Docker Registry的問題。如果你Registry地址返回的是301或者302重定向,而且重定向的新地址和Registry的地址不是同域名、同埠的話,那麼Docker客戶端在跟隨新地址時,就會丟棄認證用的HTTP頭,導致最終請求被視作匿名用戶。
針對這個問題,我已經在Docker上提了Issue #865,不知Docker何時以何種方式處理。
推薦閱讀:
※誰用光了磁碟?Docker System命令詳解
※Docker Compose + GPU + TensorFlow 所產生的奇妙火花
※DaoCloud和雲雀到底誰家的技術比較強一些?VMware和微軟系的比較?
※Docker運行nginx
※探討一下,docker/compose 中關於 link 的設計怎麼樣?