This commit is contained in:
JeffYang 2025-03-05 13:22:44 +00:00 committed by GitHub
commit bddfc81175
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 122 additions and 36 deletions

View File

@ -51,6 +51,10 @@ proxy:
remoteurl: https://registry-1.docker.io remoteurl: https://registry-1.docker.io
username: username username: username
password: password password: password
remotehostquerykey: ns
remotehostconfigmap:
registry.k8s.io:
remoteurl: https://registry.k8s.io
health: health:
storagedriver: storagedriver:
enabled: true enabled: true

View File

@ -595,6 +595,9 @@ type Middleware struct {
// Proxy configures the registry as a pull through cache // Proxy configures the registry as a pull through cache
type Proxy struct { type Proxy struct {
RemoteHostQueryKey string `yaml:"remotehostquerykey"`
RemoteHostConfigMap map[string]RemoteHostConfig `yaml:"remotehostconfigmap"`
// RemoteURL is the URL of the remote registry // RemoteURL is the URL of the remote registry
RemoteURL string `yaml:"remoteurl"` RemoteURL string `yaml:"remoteurl"`
@ -614,6 +617,19 @@ type Proxy struct {
TTL *time.Duration `yaml:"ttl,omitempty"` TTL *time.Duration `yaml:"ttl,omitempty"`
} }
type RemoteHostConfig struct {
RemoteURL string `yaml:"remoteurl"`
// Username of the hub user
Username string `yaml:"username"`
// Password of the hub user
Password string `yaml:"password"`
// Exec specifies a custom exec-based command to retrieve credentials.
// If set, Username and Password are ignored.
Exec *ExecConfig `yaml:"exec,omitempty"`
}
type ExecConfig struct { type ExecConfig struct {
// Command is the command to execute. // Command is the command to execute.
Command string `yaml:"command"` Command string `yaml:"command"`

View File

@ -0,0 +1,15 @@
package dcontext
import "context"
type registryHostKey struct{}
func (registryHostKey) String() string { return "registryHost" }
func WithRegistryHost(ctx context.Context, host string) context.Context {
return context.WithValue(ctx, registryHostKey{}, host)
}
func GetRegistryHost(ctx context.Context) string {
return GetStringValue(ctx, registryHostKey{})
}

View File

@ -96,7 +96,7 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
Config: config, Config: config,
Context: ctx, Context: ctx,
router: v2.RouterWithPrefix(config.HTTP.Prefix), router: v2.RouterWithPrefix(config.HTTP.Prefix),
isCache: config.Proxy.RemoteURL != "", isCache: config.Proxy.RemoteURL != "" || config.Proxy.RemoteHostQueryKey != "",
} }
// Register the handler dispatchers. // Register the handler dispatchers.
@ -347,13 +347,12 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
} }
// configure as a pull through cache // configure as a pull through cache
if config.Proxy.RemoteURL != "" { if config.Proxy.RemoteURL != "" || config.Proxy.RemoteHostQueryKey != "" {
app.registry, err = proxy.NewRegistryPullThroughCache(ctx, app.registry, app.driver, config.Proxy) app.registry, err = proxy.NewRegistryPullThroughCache(ctx, app.registry, app.driver, config.Proxy)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }
app.isCache = true app.isCache = true
dcontext.GetLogger(app).Info("Registry configured as a proxy cache to ", config.Proxy.RemoteURL)
} }
var ok bool var ok bool
app.repoRemover, ok = app.registry.(distribution.RepositoryRemover) app.repoRemover, ok = app.registry.(distribution.RepositoryRemover)
@ -690,8 +689,13 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
return return
} }
// dcontext.WithValues(context)
// Add username to request logging // Add username to request logging
context.Context = dcontext.WithLogger(context.Context, dcontext.GetLogger(context.Context, userNameKey)) context.Context = dcontext.WithLogger(context.Context, dcontext.GetLogger(context.Context, userNameKey))
if registryHost, ok := r.URL.Query()["ns"]; ok {
context.Context = dcontext.WithRegistryHost(context.Context, registryHost[0])
}
// sync up context on the request. // sync up context on the request.
r = r.WithContext(context) r = r.WithContext(context)

View File

@ -26,20 +26,19 @@ var repositoryTTL = 24 * 7 * time.Hour
// proxyingRegistry fetches content from a remote registry and caches it locally // proxyingRegistry fetches content from a remote registry and caches it locally
type proxyingRegistry struct { type proxyingRegistry struct {
embedded distribution.Namespace // provides local registry functionality embedded distribution.Namespace // provides local registry functionality
scheduler *scheduler.TTLExpirationScheduler scheduler *scheduler.TTLExpirationScheduler
ttl *time.Duration ttl *time.Duration
remoteURL url.URL defaultRemoteURL url.URL
authChallenger authChallenger defaultAuthChallenger authChallenger
basicAuth auth.CredentialStore defaultBasicAuth auth.CredentialStore
remoteURLMap map[string]url.URL
authChallengerMap map[string]authChallenger
basicAuthMap map[string]auth.CredentialStore
} }
// NewRegistryPullThroughCache creates a registry acting as a pull through cache // NewRegistryPullThroughCache creates a registry acting as a pull through cache
func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Namespace, driver driver.StorageDriver, config configuration.Proxy) (distribution.Namespace, error) { func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Namespace, driver driver.StorageDriver, config configuration.Proxy) (distribution.Namespace, error) {
remoteURL, err := url.Parse(config.RemoteURL)
if err != nil {
return nil, err
}
v := storage.NewVacuum(ctx, driver) v := storage.NewVacuum(ctx, driver)
@ -108,37 +107,70 @@ func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Name
return nil return nil
}) })
err = s.Start() err := s.Start()
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
cs, b, err := func() (auth.CredentialStore, auth.CredentialStore, error) { getAuth := func(username, password, remoteurl string, exec *configuration.ExecConfig) (auth.CredentialStore, auth.CredentialStore, error) {
switch { switch {
case config.Exec != nil: case exec != nil:
cs, err := configureExecAuth(*config.Exec) cs, err := configureExecAuth(*exec)
return cs, cs, err return cs, cs, err
default: default:
return configureAuth(config.Username, config.Password, config.RemoteURL) return configureAuth(username, password, remoteurl)
} }
}()
if err != nil {
return nil, err
} }
return &proxyingRegistry{ reg := &proxyingRegistry{
embedded: registry, embedded: registry,
scheduler: s, scheduler: s,
ttl: ttl, ttl: ttl,
remoteURL: *remoteURL, }
authChallenger: &remoteAuthChallenger{ if config.RemoteURL != "" {
remoteURL, err := url.Parse(config.RemoteURL)
if err != nil {
return nil, err
}
cs, b, err := getAuth(config.Username, config.Password, config.RemoteURL, config.Exec)
if err != nil {
return nil, err
}
reg.defaultRemoteURL = *remoteURL
reg.defaultAuthChallenger = &remoteAuthChallenger{
remoteURL: *remoteURL, remoteURL: *remoteURL,
cm: challenge.NewSimpleManager(), cm: challenge.NewSimpleManager(),
cs: cs, cs: cs,
}, }
basicAuth: b, reg.defaultBasicAuth = b
}, nil }
if config.RemoteHostConfigMap != nil {
reg.remoteURLMap = make(map[string]url.URL)
reg.authChallengerMap = make(map[string]authChallenger)
reg.basicAuthMap = make(map[string]auth.CredentialStore)
for key, value := range config.RemoteHostConfigMap {
remoteURL, err := url.Parse(value.RemoteURL)
if err != nil {
return nil, err
}
cs, b, err := getAuth(value.Username, value.Password, value.RemoteURL, value.Exec)
if err != nil {
return nil, err
}
reg.remoteURLMap[key] = *remoteURL
reg.authChallengerMap[key] = &remoteAuthChallenger{
remoteURL: *remoteURL,
cm: challenge.NewSimpleManager(),
cs: cs,
}
reg.basicAuthMap[key] = b
}
}
return reg, nil
} }
func (pr *proxyingRegistry) Scope() distribution.Scope { func (pr *proxyingRegistry) Scope() distribution.Scope {
@ -150,11 +182,26 @@ func (pr *proxyingRegistry) Repositories(ctx context.Context, repos []string, la
} }
func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named) (distribution.Repository, error) { func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named) (distribution.Repository, error) {
c := pr.authChallenger var err error
localRepoName := name
authChallenger := pr.defaultAuthChallenger
registryURL := pr.defaultRemoteURL
basicAuth := pr.defaultBasicAuth
if registryHost := dcontext.GetRegistryHost(ctx); registryHost != "" {
if _, ok := pr.remoteURLMap[registryHost]; ok {
localRepoName, err = reference.WithName(fmt.Sprintf("%s/%s", registryHost, name.Name()))
if err != nil {
return nil, err
}
registryURL = pr.remoteURLMap[registryHost]
authChallenger = pr.authChallengerMap[registryHost]
basicAuth = pr.basicAuthMap[registryHost]
}
}
tkopts := auth.TokenHandlerOptions{ tkopts := auth.TokenHandlerOptions{
Transport: http.DefaultTransport, Transport: http.DefaultTransport,
Credentials: c.credentialStore(), Credentials: authChallenger.credentialStore(),
Scopes: []auth.Scope{ Scopes: []auth.Scope{
auth.RepositoryScope{ auth.RepositoryScope{
Repository: name.Name(), Repository: name.Name(),
@ -165,11 +212,11 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named
} }
tr := transport.NewTransport(http.DefaultTransport, tr := transport.NewTransport(http.DefaultTransport,
auth.NewAuthorizer(c.challengeManager(), auth.NewAuthorizer(authChallenger.challengeManager(),
auth.NewTokenHandlerWithOptions(tkopts), auth.NewTokenHandlerWithOptions(tkopts),
auth.NewBasicHandler(pr.basicAuth))) auth.NewBasicHandler(basicAuth)))
localRepo, err := pr.embedded.Repository(ctx, name) localRepo, err := pr.embedded.Repository(ctx, localRepoName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -178,7 +225,7 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named
return nil, err return nil, err
} }
remoteRepo, err := client.NewRepository(name, pr.remoteURL.String(), tr) remoteRepo, err := client.NewRepository(name, registryURL.String(), tr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -195,7 +242,7 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named
scheduler: pr.scheduler, scheduler: pr.scheduler,
ttl: pr.ttl, ttl: pr.ttl,
repositoryName: name, repositoryName: name,
authChallenger: pr.authChallenger, authChallenger: authChallenger,
}, },
manifests: &proxyManifestStore{ manifests: &proxyManifestStore{
repositoryName: name, repositoryName: name,
@ -204,13 +251,13 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named
ctx: ctx, ctx: ctx,
scheduler: pr.scheduler, scheduler: pr.scheduler,
ttl: pr.ttl, ttl: pr.ttl,
authChallenger: pr.authChallenger, authChallenger: authChallenger,
}, },
name: name, name: name,
tags: &proxyTagService{ tags: &proxyTagService{
localTags: localRepo.Tags(ctx), localTags: localRepo.Tags(ctx),
remoteTags: remoteRepo.Tags(ctx), remoteTags: remoteRepo.Tags(ctx),
authChallenger: pr.authChallenger, authChallenger: authChallenger,
}, },
}, nil }, nil
} }