WIP: ✨ docker: public.ecr.aws support #66
@@ -133,6 +133,12 @@ func DefaultConfig() *AppConfig {
|
||||
AuthType: "anonymous",
|
||||
Enabled: true,
|
||||
},
|
||||
"public.ecr.aws": {
|
||||
Upstream: "public.ecr.aws",
|
||||
AuthHost: "public.ecr.aws/token",
|
||||
AuthType: "aws-ecr",
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
TokenCache: struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
|
||||
@@ -25,7 +25,7 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
@@ -37,6 +37,7 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||
|
||||
@@ -77,8 +77,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
|
||||
@@ -3,17 +3,17 @@ package handlers
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"hubproxy/config"
|
||||
"hubproxy/utils"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DockerProxy Docker代理配置
|
||||
@@ -546,7 +546,7 @@ func handleUpstreamBlobRequest(c *gin.Context, imageRef, digest string, mapping
|
||||
if err != nil {
|
||||
fmt.Printf("获取layer大小失败: %v\n", err)
|
||||
c.String(http.StatusInternalServerError, "Failed to get layer size")
|
||||
return
|
||||
// return
|
||||
}
|
||||
|
||||
reader, err := layer.Compressed()
|
||||
@@ -593,17 +593,62 @@ func handleUpstreamTagsRequest(c *gin.Context, imageRef string, mapping config.R
|
||||
// createUpstreamOptions 创建上游Registry选项
|
||||
func createUpstreamOptions(mapping config.RegistryMapping) []remote.Option {
|
||||
options := []remote.Option{
|
||||
remote.WithAuth(authn.Anonymous),
|
||||
remote.WithUserAgent("hubproxy/go-containerregistry"),
|
||||
remote.WithTransport(utils.GetGlobalHTTPClient().Transport),
|
||||
}
|
||||
|
||||
useAnonymous := true
|
||||
|
||||
// 预留将来不同Registry的差异化认证逻辑扩展点
|
||||
switch mapping.AuthType {
|
||||
case "github":
|
||||
case "google":
|
||||
case "quay":
|
||||
case "aws-ecr":
|
||||
|
||||
// Step 1: Get anonymous token from https://public.ecr.aws/token/
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: utils.GetGlobalHTTPClient().Transport,
|
||||
}
|
||||
authUrl := fmt.Sprint("https://", mapping.AuthHost)
|
||||
resp, err := client.Get(authUrl)
|
||||
if err != nil {
|
||||
fmt.Printf("%s: Failed to get token, fallback to anonymous: %v\n", mapping.AuthType, err)
|
||||
break
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
c, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("%s: Failed to read token, fallback to anonymous: %v\n", mapping.AuthType, err)
|
||||
break
|
||||
}
|
||||
var tokenRet map[string]string
|
||||
if err := jsoniter.Unmarshal(c, &tokenRet); err != nil {
|
||||
fmt.Printf("%s: Failed to read token, fallback to anonymous: %v\n", mapping.AuthType, err)
|
||||
break
|
||||
}
|
||||
|
||||
// Step 2: Successfully got token like:
|
||||
/*
|
||||
{"token":"[Token here]"}
|
||||
*/
|
||||
if token, ok := tokenRet["token"]; !ok {
|
||||
fmt.Printf("%s: token is missing in response, fallback to anonymous: %v\n", mapping.AuthType, err)
|
||||
break
|
||||
} else {
|
||||
// Step 3: Use the token fetched as Bearer auth
|
||||
auth := &authn.Bearer{
|
||||
Token: token,
|
||||
}
|
||||
options = append(options, remote.WithAuth(auth))
|
||||
useAnonymous = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if useAnonymous {
|
||||
options = append(options, remote.WithAuth(authn.Anonymous))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user