mirror of
https://github.com/sky22333/hubproxy.git
synced 2026-08-05 03:24:57 +08:00
fix
This commit is contained in:
@@ -119,12 +119,8 @@ func resolveRegistryTarget(c *gin.Context, pathWithoutV2 string) (registryTarget
|
|||||||
|
|
||||||
func resolveTokenTarget(c *gin.Context) (registryTarget, bool) {
|
func resolveTokenTarget(c *gin.Context) (registryTarget, bool) {
|
||||||
name := strings.Trim(strings.TrimSpace(c.Param("path")), "/")
|
name := strings.Trim(strings.TrimSpace(c.Param("path")), "/")
|
||||||
if name == "" {
|
if name == "" || isDockerHubAlias(name) {
|
||||||
return defaultRegistryTarget(), true
|
return inferTokenTargetFromScope(c.Query("scope"))
|
||||||
}
|
|
||||||
|
|
||||||
if name == dockerHubName || name == "dockerhub" || name == "registry-1.docker.io" {
|
|
||||||
return defaultRegistryTarget(), true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := config.GetConfig()
|
cfg := config.GetConfig()
|
||||||
@@ -135,6 +131,44 @@ func resolveTokenTarget(c *gin.Context) (registryTarget, bool) {
|
|||||||
return registryTarget{}, false
|
return registryTarget{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func inferTokenTargetFromScope(scope string) (registryTarget, bool) {
|
||||||
|
if registryName, ok := registryNameFromScope(scope); ok {
|
||||||
|
if isDockerHubAlias(registryName) {
|
||||||
|
return defaultRegistryTarget(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := config.GetConfig()
|
||||||
|
if mapping, exists := cfg.Registries[registryName]; exists && mapping.Enabled {
|
||||||
|
return registryTargetFromMapping(registryName, mapping), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultRegistryTarget(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func registryNameFromScope(scope string) (string, bool) {
|
||||||
|
parts := strings.Split(scope, ":")
|
||||||
|
if len(parts) != 3 || parts[0] != "repository" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := parts[1]
|
||||||
|
slash := strings.Index(repo, "/")
|
||||||
|
if slash == -1 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
registryName := repo[:slash]
|
||||||
|
if strings.Contains(registryName, ".") || strings.Contains(registryName, ":") || registryName == "localhost" {
|
||||||
|
return registryName, true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDockerHubAlias(name string) bool {
|
||||||
|
return name == dockerHubName || name == "dockerhub" || name == "registry-1.docker.io"
|
||||||
|
}
|
||||||
|
|
||||||
// 透明代理 Docker Registry API v2 请求。
|
// 透明代理 Docker Registry API v2 请求。
|
||||||
func ProxyDockerRegistryGin(c *gin.Context) {
|
func ProxyDockerRegistryGin(c *gin.Context) {
|
||||||
path := c.Request.URL.Path
|
path := c.Request.URL.Path
|
||||||
@@ -266,8 +300,8 @@ func buildAuthURL(target registryTarget, rawQuery string) (string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
if strings.EqualFold(key, "scope") && target.AutoLibraryPrefix {
|
if strings.EqualFold(key, "scope") {
|
||||||
value = addLibraryPrefixToScope(value)
|
value = normalizeScopeForTarget(value, target)
|
||||||
}
|
}
|
||||||
query.Add(key, value)
|
query.Add(key, value)
|
||||||
}
|
}
|
||||||
@@ -278,6 +312,34 @@ func buildAuthURL(target registryTarget, rawQuery string) (string, error) {
|
|||||||
return authURL.String(), nil
|
return authURL.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeScopeForTarget(scope string, target registryTarget) string {
|
||||||
|
scope = stripTargetRegistryFromScope(scope, target)
|
||||||
|
if target.AutoLibraryPrefix {
|
||||||
|
return addLibraryPrefixToScope(scope)
|
||||||
|
}
|
||||||
|
return scope
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripTargetRegistryFromScope(scope string, target registryTarget) string {
|
||||||
|
parts := strings.Split(scope, ":")
|
||||||
|
if len(parts) != 3 || parts[0] != "repository" {
|
||||||
|
return scope
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixes := []string{target.Name + "/"}
|
||||||
|
if target.Name == dockerHubName {
|
||||||
|
prefixes = append(prefixes, "dockerhub/", "registry-1.docker.io/")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range prefixes {
|
||||||
|
if strings.HasPrefix(parts[1], prefix) {
|
||||||
|
parts[1] = strings.TrimPrefix(parts[1], prefix)
|
||||||
|
return strings.Join(parts, ":")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scope
|
||||||
|
}
|
||||||
|
|
||||||
func addLibraryPrefixToScope(scope string) string {
|
func addLibraryPrefixToScope(scope string) string {
|
||||||
parts := strings.Split(scope, ":")
|
parts := strings.Split(scope, ":")
|
||||||
if len(parts) != 3 || parts[0] != "repository" || strings.Contains(parts[1], "/") {
|
if len(parts) != 3 || parts[0] != "repository" || strings.Contains(parts[1], "/") {
|
||||||
|
|||||||
@@ -139,6 +139,60 @@ func TestBuildAuthURLForDockerHubAddsLibraryScopeAndService(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTokenTargetIsInferredFromPathBasedRegistryScope(t *testing.T) {
|
||||||
|
initDockerProxyTest(t, `
|
||||||
|
[registries."ghcr.io"]
|
||||||
|
upstream = "ghcr.io"
|
||||||
|
authHost = "ghcr.io/token"
|
||||||
|
authType = "github"
|
||||||
|
enabled = true
|
||||||
|
`)
|
||||||
|
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/token/docker.io?scope=repository:ghcr.io/jeessy2/ddns-go:pull&service=registry.docker.io", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(w)
|
||||||
|
c.Request = req
|
||||||
|
c.Params = gin.Params{{Key: "path", Value: "/docker.io"}}
|
||||||
|
|
||||||
|
target, ok := resolveTokenTarget(c)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("resolveTokenTarget returned false")
|
||||||
|
}
|
||||||
|
if target.Name != "ghcr.io" {
|
||||||
|
t.Fatalf("target.Name = %q, want ghcr.io", target.Name)
|
||||||
|
}
|
||||||
|
if target.AuthService != "ghcr.io" {
|
||||||
|
t.Fatalf("AuthService = %q, want ghcr.io", target.AuthService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildAuthURLStripsPathBasedRegistryPrefixForGHCR(t *testing.T) {
|
||||||
|
target := registryTarget{
|
||||||
|
Name: "ghcr.io",
|
||||||
|
AuthRealm: "https://ghcr.io/token",
|
||||||
|
AuthService: "ghcr.io",
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := buildAuthURL(
|
||||||
|
target,
|
||||||
|
"scope=repository%3Aghcr.io%2Fjeessy2%2Fddns-go%3Apull&service=registry.docker.io",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(got, "service=ghcr.io") {
|
||||||
|
t.Fatalf("auth URL missing ghcr service: %q", got)
|
||||||
|
}
|
||||||
|
if !strings.Contains(got, "scope=repository%3Ajeessy2%2Fddns-go%3Apull") {
|
||||||
|
t.Fatalf("auth URL missing stripped scope: %q", got)
|
||||||
|
}
|
||||||
|
if strings.Contains(got, "registry.docker.io") {
|
||||||
|
t.Fatalf("auth URL leaked Docker Hub service: %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDockerIODefaultTargetUsesBuiltInWhenUnconfigured(t *testing.T) {
|
func TestDockerIODefaultTargetUsesBuiltInWhenUnconfigured(t *testing.T) {
|
||||||
initDockerProxyTest(t, "")
|
initDockerProxyTest(t, "")
|
||||||
|
|
||||||
@@ -327,6 +381,46 @@ enabled = true
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProxyDockerAuthRoutesPathBasedGHCRScopeToGHCRAuth(t *testing.T) {
|
||||||
|
authServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if got := r.URL.Query().Get("service"); got != "ghcr.io" {
|
||||||
|
t.Fatalf("service = %q, want ghcr.io", got)
|
||||||
|
}
|
||||||
|
if got := r.URL.Query().Get("scope"); got != "repository:jeessy2/ddns-go:pull" {
|
||||||
|
t.Fatalf("scope = %q, want repository:jeessy2/ddns-go:pull", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"token":"ghcr-token","expires_in":3600}`))
|
||||||
|
}))
|
||||||
|
defer authServer.Close()
|
||||||
|
|
||||||
|
initDockerProxyTest(t, `
|
||||||
|
[registries."ghcr.io"]
|
||||||
|
upstream = "ghcr.io"
|
||||||
|
authHost = "`+authServer.URL+`"
|
||||||
|
authType = "github"
|
||||||
|
enabled = true
|
||||||
|
`)
|
||||||
|
utils.GlobalCache = &utils.UniversalCache{}
|
||||||
|
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
router := gin.New()
|
||||||
|
router.Any("/token/*path", ProxyDockerAuthGin)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/token/docker.io?scope=repository%3Aghcr.io%2Fjeessy2%2Fddns-go%3Apull&service=registry.docker.io", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want 200; body=%s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
if got := w.Body.String(); !strings.Contains(got, `"token":"ghcr-token"`) {
|
||||||
|
t.Fatalf("body = %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDockerHubShortNameIsProxiedWithLibraryPrefix(t *testing.T) {
|
func TestDockerHubShortNameIsProxiedWithLibraryPrefix(t *testing.T) {
|
||||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/v2/library/nginx/manifests/latest" {
|
if r.URL.Path != "/v2/library/nginx/manifests/latest" {
|
||||||
|
|||||||
Reference in New Issue
Block a user