diff --git a/cmd/registry/config-cache.yml b/cmd/registry/config-cache.yml index 421d964f0..a1745fdf5 100644 --- a/cmd/registry/config-cache.yml +++ b/cmd/registry/config-cache.yml @@ -51,6 +51,10 @@ proxy: remoteurl: https://registry-1.docker.io username: username password: password + remotehostquerykey: ns + remotehostconfigmap: + registry.k8s.io: + remoteurl: https://registry.k8s.io health: storagedriver: enabled: true diff --git a/configuration/configuration.go b/configuration/configuration.go index f6b88d84b..ded24d6dd 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -595,6 +595,9 @@ type Middleware struct { // Proxy configures the registry as a pull through cache type Proxy struct { + RemoteHostQueryKey string `yaml:"remotehostquerykey"` + RemoteHostConfigMap map[string]RemoteHostConfig `yaml:"remotehostconfigmap"` + // RemoteURL is the URL of the remote registry RemoteURL string `yaml:"remoteurl"` @@ -614,6 +617,19 @@ type Proxy struct { 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 { // Command is the command to execute. Command string `yaml:"command"` diff --git a/internal/dcontext/registry_host.go b/internal/dcontext/registry_host.go new file mode 100644 index 000000000..a642d45dd --- /dev/null +++ b/internal/dcontext/registry_host.go @@ -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{}) +} diff --git a/registry/handlers/app.go b/registry/handlers/app.go index 301fc9706..ab864c332 100644 --- a/registry/handlers/app.go +++ b/registry/handlers/app.go @@ -96,7 +96,7 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App { Config: config, Context: ctx, router: v2.RouterWithPrefix(config.HTTP.Prefix), - isCache: config.Proxy.RemoteURL != "", + isCache: config.Proxy.RemoteURL != "" || config.Proxy.RemoteHostQueryKey != "", } // Register the handler dispatchers. @@ -347,13 +347,12 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App { } // 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) if err != nil { panic(err.Error()) } app.isCache = true - dcontext.GetLogger(app).Info("Registry configured as a proxy cache to ", config.Proxy.RemoteURL) } var ok bool app.repoRemover, ok = app.registry.(distribution.RepositoryRemover) @@ -690,8 +689,13 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler { return } + // dcontext.WithValues(context) + // Add username to request logging 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. r = r.WithContext(context) diff --git a/registry/proxy/proxyregistry.go b/registry/proxy/proxyregistry.go index d353af3ff..bafdd49e9 100644 --- a/registry/proxy/proxyregistry.go +++ b/registry/proxy/proxyregistry.go @@ -26,20 +26,19 @@ var repositoryTTL = 24 * 7 * time.Hour // proxyingRegistry fetches content from a remote registry and caches it locally type proxyingRegistry struct { - embedded distribution.Namespace // provides local registry functionality - scheduler *scheduler.TTLExpirationScheduler - ttl *time.Duration - remoteURL url.URL - authChallenger authChallenger - basicAuth auth.CredentialStore + embedded distribution.Namespace // provides local registry functionality + scheduler *scheduler.TTLExpirationScheduler + ttl *time.Duration + defaultRemoteURL url.URL + defaultAuthChallenger authChallenger + 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 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) @@ -108,37 +107,70 @@ func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Name return nil }) - err = s.Start() + err := s.Start() if err != nil { 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 { - case config.Exec != nil: - cs, err := configureExecAuth(*config.Exec) + case exec != nil: + cs, err := configureExecAuth(*exec) return cs, cs, err 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, scheduler: s, 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, cm: challenge.NewSimpleManager(), cs: cs, - }, - basicAuth: b, - }, nil + } + reg.defaultBasicAuth = b + } + + 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 { @@ -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) { - 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{ Transport: http.DefaultTransport, - Credentials: c.credentialStore(), + Credentials: authChallenger.credentialStore(), Scopes: []auth.Scope{ auth.RepositoryScope{ Repository: name.Name(), @@ -165,11 +212,11 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named } tr := transport.NewTransport(http.DefaultTransport, - auth.NewAuthorizer(c.challengeManager(), + auth.NewAuthorizer(authChallenger.challengeManager(), 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 { return nil, err } @@ -178,7 +225,7 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named return nil, err } - remoteRepo, err := client.NewRepository(name, pr.remoteURL.String(), tr) + remoteRepo, err := client.NewRepository(name, registryURL.String(), tr) if err != nil { return nil, err } @@ -195,7 +242,7 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named scheduler: pr.scheduler, ttl: pr.ttl, repositoryName: name, - authChallenger: pr.authChallenger, + authChallenger: authChallenger, }, manifests: &proxyManifestStore{ repositoryName: name, @@ -204,13 +251,13 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named ctx: ctx, scheduler: pr.scheduler, ttl: pr.ttl, - authChallenger: pr.authChallenger, + authChallenger: authChallenger, }, name: name, tags: &proxyTagService{ localTags: localRepo.Tags(ctx), remoteTags: remoteRepo.Tags(ctx), - authChallenger: pr.authChallenger, + authChallenger: authChallenger, }, }, nil }