This commit is contained in:
sky22333
2026-05-16 03:56:29 +08:00
parent 53cc1761ce
commit 3e8ceb2b32
3 changed files with 47 additions and 14 deletions

View File

@@ -49,8 +49,7 @@ var forwardedRequestHeaders = []string{
"If-Unmodified-Since",
}
// InitDockerProxy is kept as the Docker proxy initialization hook. The online
// registry proxy is intentionally stateless and uses the shared HTTP client.
// 保留初始化入口,在线代理无状态。
func InitDockerProxy() {}
func defaultRegistryTarget() registryTarget {
@@ -136,12 +135,13 @@ func resolveTokenTarget(c *gin.Context) (registryTarget, bool) {
return registryTarget{}, false
}
// ProxyDockerRegistryGin proxies Docker Registry API v2 requests transparently.
// 透明代理 Docker Registry API v2 请求。
func ProxyDockerRegistryGin(c *gin.Context) {
path := c.Request.URL.Path
if path == "/v2/" {
c.JSON(http.StatusOK, gin.H{})
target, _ := resolveRegistryTarget(c, "")
proxyRegistryHTTP(c, target, "/v2/")
return
}
@@ -182,7 +182,7 @@ func handleRegistryRequest(c *gin.Context, path string) {
proxyRegistryHTTP(c, target, "/v2/"+targetPath)
}
// parseRegistryPath parses a Docker Registry v2 path without the leading /v2/.
// 解析去掉 /v2/ 前缀后的 Registry 路径。
func parseRegistryPath(path string) (imageName, apiType, reference string) {
if idx := strings.Index(path, "/manifests/"); idx != -1 {
return path[:idx], "manifests", path[idx+len("/manifests/"):]
@@ -196,8 +196,7 @@ func parseRegistryPath(path string) (imageName, apiType, reference string) {
return "", "", ""
}
// ProxyDockerAuthGin forwards Docker token requests, including client Basic
// credentials, to the selected upstream auth service.
// 代理 Docker token 请求,并透传客户端认证头。
func ProxyDockerAuthGin(c *gin.Context) {
target, ok := resolveTokenTarget(c)
if !ok {

View File

@@ -245,6 +245,45 @@ enabled = true
}
}
func TestDockerV2BaseProxiesUpstreamChallenge(t *testing.T) {
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v2/" {
t.Fatalf("upstream path = %q", r.URL.Path)
}
w.Header().Set("WWW-Authenticate", `Bearer realm="https://registry.example/token",service="registry.example"`)
w.WriteHeader(http.StatusUnauthorized)
}))
defer upstream.Close()
initDockerProxyTest(t, `
[registries."docker.io"]
upstream = "`+upstream.URL+`"
authHost = "https://auth.example/token"
authType = "docker"
enabled = true
`)
gin.SetMode(gin.TestMode)
router := gin.New()
router.Any("/v2/", ProxyDockerRegistryGin)
req := httptest.NewRequest(http.MethodGet, "/v2/", nil)
req.Host = "hub.example.com"
req.Header.Set("X-Forwarded-Proto", "https")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Fatalf("status = %d, want 401; body=%s", w.Code, w.Body.String())
}
wantChallenge := `Bearer realm="https://hub.example.com/token/docker.io",service="registry.docker.io"`
if got := w.Header().Get("WWW-Authenticate"); got != wantChallenge {
t.Fatalf("WWW-Authenticate = %q, want %q", got, wantChallenge)
}
}
func TestProxyDockerAuthForwardsBasicCredentials(t *testing.T) {
authServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.Header.Get("Authorization"); got != "Basic dXNlcjpwYXNz" {

View File

@@ -140,15 +140,10 @@ func TestGitHubNoRouteRejectsUnsupportedHost(t *testing.T) {
}
}
func TestDockerV2PingAndInvalidPath(t *testing.T) {
func TestDockerV2InvalidPath(t *testing.T) {
router := newTestRouter(t, "")
w := performRequest(router, http.MethodGet, "/v2/", "")
if w.Code != http.StatusOK {
t.Fatalf("/v2/ status = %d, want 200; body=%s", w.Code, w.Body.String())
}
w = performRequest(router, http.MethodGet, "/v2/library/nginx/unknown/latest", "")
w := performRequest(router, http.MethodGet, "/v2/library/nginx/unknown/latest", "")
if w.Code != http.StatusBadRequest {
t.Fatalf("invalid v2 status = %d, want 400; body=%s", w.Code, w.Body.String())
}