github.com/npaton/distribution@v2.3.1-rc.0+incompatible/registry/handlers/app.go (about)

     1  package handlers
     2  
     3  import (
     4  	cryptorand "crypto/rand"
     5  	"expvar"
     6  	"fmt"
     7  	"math/rand"
     8  	"net"
     9  	"net/http"
    10  	"net/url"
    11  	"os"
    12  	"time"
    13  
    14  	log "github.com/Sirupsen/logrus"
    15  	"github.com/docker/distribution"
    16  	"github.com/docker/distribution/configuration"
    17  	ctxu "github.com/docker/distribution/context"
    18  	"github.com/docker/distribution/health"
    19  	"github.com/docker/distribution/health/checks"
    20  	"github.com/docker/distribution/notifications"
    21  	"github.com/docker/distribution/reference"
    22  	"github.com/docker/distribution/registry/api/errcode"
    23  	"github.com/docker/distribution/registry/api/v2"
    24  	"github.com/docker/distribution/registry/auth"
    25  	registrymiddleware "github.com/docker/distribution/registry/middleware/registry"
    26  	repositorymiddleware "github.com/docker/distribution/registry/middleware/repository"
    27  	"github.com/docker/distribution/registry/proxy"
    28  	"github.com/docker/distribution/registry/storage"
    29  	memorycache "github.com/docker/distribution/registry/storage/cache/memory"
    30  	rediscache "github.com/docker/distribution/registry/storage/cache/redis"
    31  	storagedriver "github.com/docker/distribution/registry/storage/driver"
    32  	"github.com/docker/distribution/registry/storage/driver/factory"
    33  	storagemiddleware "github.com/docker/distribution/registry/storage/driver/middleware"
    34  	"github.com/docker/libtrust"
    35  	"github.com/garyburd/redigo/redis"
    36  	"github.com/gorilla/mux"
    37  	"golang.org/x/net/context"
    38  )
    39  
    40  // randomSecretSize is the number of random bytes to generate if no secret
    41  // was specified.
    42  const randomSecretSize = 32
    43  
    44  // defaultCheckInterval is the default time in between health checks
    45  const defaultCheckInterval = 10 * time.Second
    46  
    47  // App is a global registry application object. Shared resources can be placed
    48  // on this object that will be accessible from all requests. Any writable
    49  // fields should be protected.
    50  type App struct {
    51  	context.Context
    52  
    53  	Config *configuration.Configuration
    54  
    55  	router           *mux.Router                 // main application router, configured with dispatchers
    56  	driver           storagedriver.StorageDriver // driver maintains the app global storage driver instance.
    57  	registry         distribution.Namespace      // registry is the primary registry backend for the app instance.
    58  	accessController auth.AccessController       // main access controller for application
    59  
    60  	// httpHost is a parsed representation of the http.host parameter from
    61  	// the configuration. Only the Scheme and Host fields are used.
    62  	httpHost url.URL
    63  
    64  	// events contains notification related configuration.
    65  	events struct {
    66  		sink   notifications.Sink
    67  		source notifications.SourceRecord
    68  	}
    69  
    70  	redis *redis.Pool
    71  
    72  	// trustKey is a deprecated key used to sign manifests converted to
    73  	// schema1 for backward compatibility. It should not be used for any
    74  	// other purposes.
    75  	trustKey libtrust.PrivateKey
    76  
    77  	// isCache is true if this registry is configured as a pull through cache
    78  	isCache bool
    79  
    80  	// readOnly is true if the registry is in a read-only maintenance mode
    81  	readOnly bool
    82  }
    83  
    84  // NewApp takes a configuration and returns a configured app, ready to serve
    85  // requests. The app only implements ServeHTTP and can be wrapped in other
    86  // handlers accordingly.
    87  func NewApp(ctx context.Context, configuration *configuration.Configuration) *App {
    88  	app := &App{
    89  		Config:  configuration,
    90  		Context: ctx,
    91  		router:  v2.RouterWithPrefix(configuration.HTTP.Prefix),
    92  		isCache: configuration.Proxy.RemoteURL != "",
    93  	}
    94  
    95  	// Register the handler dispatchers.
    96  	app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler {
    97  		return http.HandlerFunc(apiBase)
    98  	})
    99  	app.register(v2.RouteNameManifest, imageManifestDispatcher)
   100  	app.register(v2.RouteNameCatalog, catalogDispatcher)
   101  	app.register(v2.RouteNameTags, tagsDispatcher)
   102  	app.register(v2.RouteNameBlob, blobDispatcher)
   103  	app.register(v2.RouteNameBlobUpload, blobUploadDispatcher)
   104  	app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher)
   105  
   106  	var err error
   107  	app.driver, err = factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters())
   108  	if err != nil {
   109  		// TODO(stevvooe): Move the creation of a service into a protected
   110  		// method, where this is created lazily. Its status can be queried via
   111  		// a health check.
   112  		panic(err)
   113  	}
   114  
   115  	purgeConfig := uploadPurgeDefaultConfig()
   116  	if mc, ok := configuration.Storage["maintenance"]; ok {
   117  		if v, ok := mc["uploadpurging"]; ok {
   118  			purgeConfig, ok = v.(map[interface{}]interface{})
   119  			if !ok {
   120  				panic("uploadpurging config key must contain additional keys")
   121  			}
   122  		}
   123  		if v, ok := mc["readonly"]; ok {
   124  			readOnly, ok := v.(map[interface{}]interface{})
   125  			if !ok {
   126  				panic("readonly config key must contain additional keys")
   127  			}
   128  			if readOnlyEnabled, ok := readOnly["enabled"]; ok {
   129  				app.readOnly, ok = readOnlyEnabled.(bool)
   130  				if !ok {
   131  					panic("readonly's enabled config key must have a boolean value")
   132  				}
   133  			}
   134  		}
   135  	}
   136  
   137  	startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig)
   138  
   139  	app.driver, err = applyStorageMiddleware(app.driver, configuration.Middleware["storage"])
   140  	if err != nil {
   141  		panic(err)
   142  	}
   143  
   144  	app.configureSecret(configuration)
   145  	app.configureEvents(configuration)
   146  	app.configureRedis(configuration)
   147  	app.configureLogHook(configuration)
   148  
   149  	if configuration.Compatibility.Schema1.TrustKey != "" {
   150  		app.trustKey, err = libtrust.LoadKeyFile(configuration.Compatibility.Schema1.TrustKey)
   151  		if err != nil {
   152  			panic(fmt.Sprintf(`could not load schema1 "signingkey" parameter: %v`, err))
   153  		}
   154  	} else {
   155  		// Generate an ephemeral key to be used for signing converted manifests
   156  		// for clients that don't support schema2.
   157  		app.trustKey, err = libtrust.GenerateECP256PrivateKey()
   158  		if err != nil {
   159  			panic(err)
   160  		}
   161  	}
   162  
   163  	if configuration.HTTP.Host != "" {
   164  		u, err := url.Parse(configuration.HTTP.Host)
   165  		if err != nil {
   166  			panic(fmt.Sprintf(`could not parse http "host" parameter: %v`, err))
   167  		}
   168  		app.httpHost = *u
   169  	}
   170  
   171  	options := []storage.RegistryOption{}
   172  
   173  	if app.isCache {
   174  		options = append(options, storage.DisableDigestResumption)
   175  	}
   176  
   177  	if configuration.Compatibility.Schema1.DisableSignatureStore {
   178  		options = append(options, storage.DisableSchema1Signatures)
   179  		options = append(options, storage.Schema1SigningKey(app.trustKey))
   180  	}
   181  
   182  	// configure deletion
   183  	if d, ok := configuration.Storage["delete"]; ok {
   184  		e, ok := d["enabled"]
   185  		if ok {
   186  			if deleteEnabled, ok := e.(bool); ok && deleteEnabled {
   187  				options = append(options, storage.EnableDelete)
   188  			}
   189  		}
   190  	}
   191  
   192  	// configure redirects
   193  	var redirectDisabled bool
   194  	if redirectConfig, ok := configuration.Storage["redirect"]; ok {
   195  		v := redirectConfig["disable"]
   196  		switch v := v.(type) {
   197  		case bool:
   198  			redirectDisabled = v
   199  		default:
   200  			panic(fmt.Sprintf("invalid type for redirect config: %#v", redirectConfig))
   201  		}
   202  	}
   203  	if redirectDisabled {
   204  		ctxu.GetLogger(app).Infof("backend redirection disabled")
   205  	} else {
   206  		options = append(options, storage.EnableRedirect)
   207  	}
   208  
   209  	// configure storage caches
   210  	if cc, ok := configuration.Storage["cache"]; ok {
   211  		v, ok := cc["blobdescriptor"]
   212  		if !ok {
   213  			// Backwards compatible: "layerinfo" == "blobdescriptor"
   214  			v = cc["layerinfo"]
   215  		}
   216  
   217  		switch v {
   218  		case "redis":
   219  			if app.redis == nil {
   220  				panic("redis configuration required to use for layerinfo cache")
   221  			}
   222  			cacheProvider := rediscache.NewRedisBlobDescriptorCacheProvider(app.redis)
   223  			localOptions := append(options, storage.BlobDescriptorCacheProvider(cacheProvider))
   224  			app.registry, err = storage.NewRegistry(app, app.driver, localOptions...)
   225  			if err != nil {
   226  				panic("could not create registry: " + err.Error())
   227  			}
   228  			ctxu.GetLogger(app).Infof("using redis blob descriptor cache")
   229  		case "inmemory":
   230  			cacheProvider := memorycache.NewInMemoryBlobDescriptorCacheProvider()
   231  			localOptions := append(options, storage.BlobDescriptorCacheProvider(cacheProvider))
   232  			app.registry, err = storage.NewRegistry(app, app.driver, localOptions...)
   233  			if err != nil {
   234  				panic("could not create registry: " + err.Error())
   235  			}
   236  			ctxu.GetLogger(app).Infof("using inmemory blob descriptor cache")
   237  		default:
   238  			if v != "" {
   239  				ctxu.GetLogger(app).Warnf("unknown cache type %q, caching disabled", configuration.Storage["cache"])
   240  			}
   241  		}
   242  	}
   243  
   244  	if app.registry == nil {
   245  		// configure the registry if no cache section is available.
   246  		app.registry, err = storage.NewRegistry(app.Context, app.driver, options...)
   247  		if err != nil {
   248  			panic("could not create registry: " + err.Error())
   249  		}
   250  	}
   251  
   252  	app.registry, err = applyRegistryMiddleware(app.Context, app.registry, configuration.Middleware["registry"])
   253  	if err != nil {
   254  		panic(err)
   255  	}
   256  
   257  	authType := configuration.Auth.Type()
   258  
   259  	if authType != "" {
   260  		accessController, err := auth.GetAccessController(configuration.Auth.Type(), configuration.Auth.Parameters())
   261  		if err != nil {
   262  			panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err))
   263  		}
   264  		app.accessController = accessController
   265  		ctxu.GetLogger(app).Debugf("configured %q access controller", authType)
   266  	}
   267  
   268  	// configure as a pull through cache
   269  	if configuration.Proxy.RemoteURL != "" {
   270  		app.registry, err = proxy.NewRegistryPullThroughCache(ctx, app.registry, app.driver, configuration.Proxy)
   271  		if err != nil {
   272  			panic(err.Error())
   273  		}
   274  		app.isCache = true
   275  		ctxu.GetLogger(app).Info("Registry configured as a proxy cache to ", configuration.Proxy.RemoteURL)
   276  	}
   277  
   278  	return app
   279  }
   280  
   281  // RegisterHealthChecks is an awful hack to defer health check registration
   282  // control to callers. This should only ever be called once per registry
   283  // process, typically in a main function. The correct way would be register
   284  // health checks outside of app, since multiple apps may exist in the same
   285  // process. Because the configuration and app are tightly coupled,
   286  // implementing this properly will require a refactor. This method may panic
   287  // if called twice in the same process.
   288  func (app *App) RegisterHealthChecks(healthRegistries ...*health.Registry) {
   289  	if len(healthRegistries) > 1 {
   290  		panic("RegisterHealthChecks called with more than one registry")
   291  	}
   292  	healthRegistry := health.DefaultRegistry
   293  	if len(healthRegistries) == 1 {
   294  		healthRegistry = healthRegistries[0]
   295  	}
   296  
   297  	if app.Config.Health.StorageDriver.Enabled {
   298  		interval := app.Config.Health.StorageDriver.Interval
   299  		if interval == 0 {
   300  			interval = defaultCheckInterval
   301  		}
   302  
   303  		storageDriverCheck := func() error {
   304  			_, err := app.driver.List(app, "/") // "/" should always exist
   305  			return err                          // any error will be treated as failure
   306  		}
   307  
   308  		if app.Config.Health.StorageDriver.Threshold != 0 {
   309  			healthRegistry.RegisterPeriodicThresholdFunc("storagedriver_"+app.Config.Storage.Type(), interval, app.Config.Health.StorageDriver.Threshold, storageDriverCheck)
   310  		} else {
   311  			healthRegistry.RegisterPeriodicFunc("storagedriver_"+app.Config.Storage.Type(), interval, storageDriverCheck)
   312  		}
   313  	}
   314  
   315  	for _, fileChecker := range app.Config.Health.FileCheckers {
   316  		interval := fileChecker.Interval
   317  		if interval == 0 {
   318  			interval = defaultCheckInterval
   319  		}
   320  		ctxu.GetLogger(app).Infof("configuring file health check path=%s, interval=%d", fileChecker.File, interval/time.Second)
   321  		healthRegistry.Register(fileChecker.File, health.PeriodicChecker(checks.FileChecker(fileChecker.File), interval))
   322  	}
   323  
   324  	for _, httpChecker := range app.Config.Health.HTTPCheckers {
   325  		interval := httpChecker.Interval
   326  		if interval == 0 {
   327  			interval = defaultCheckInterval
   328  		}
   329  
   330  		statusCode := httpChecker.StatusCode
   331  		if statusCode == 0 {
   332  			statusCode = 200
   333  		}
   334  
   335  		checker := checks.HTTPChecker(httpChecker.URI, statusCode, httpChecker.Timeout, httpChecker.Headers)
   336  
   337  		if httpChecker.Threshold != 0 {
   338  			ctxu.GetLogger(app).Infof("configuring HTTP health check uri=%s, interval=%d, threshold=%d", httpChecker.URI, interval/time.Second, httpChecker.Threshold)
   339  			healthRegistry.Register(httpChecker.URI, health.PeriodicThresholdChecker(checker, interval, httpChecker.Threshold))
   340  		} else {
   341  			ctxu.GetLogger(app).Infof("configuring HTTP health check uri=%s, interval=%d", httpChecker.URI, interval/time.Second)
   342  			healthRegistry.Register(httpChecker.URI, health.PeriodicChecker(checker, interval))
   343  		}
   344  	}
   345  
   346  	for _, tcpChecker := range app.Config.Health.TCPCheckers {
   347  		interval := tcpChecker.Interval
   348  		if interval == 0 {
   349  			interval = defaultCheckInterval
   350  		}
   351  
   352  		checker := checks.TCPChecker(tcpChecker.Addr, tcpChecker.Timeout)
   353  
   354  		if tcpChecker.Threshold != 0 {
   355  			ctxu.GetLogger(app).Infof("configuring TCP health check addr=%s, interval=%d, threshold=%d", tcpChecker.Addr, interval/time.Second, tcpChecker.Threshold)
   356  			healthRegistry.Register(tcpChecker.Addr, health.PeriodicThresholdChecker(checker, interval, tcpChecker.Threshold))
   357  		} else {
   358  			ctxu.GetLogger(app).Infof("configuring TCP health check addr=%s, interval=%d", tcpChecker.Addr, interval/time.Second)
   359  			healthRegistry.Register(tcpChecker.Addr, health.PeriodicChecker(checker, interval))
   360  		}
   361  	}
   362  }
   363  
   364  // register a handler with the application, by route name. The handler will be
   365  // passed through the application filters and context will be constructed at
   366  // request time.
   367  func (app *App) register(routeName string, dispatch dispatchFunc) {
   368  
   369  	// TODO(stevvooe): This odd dispatcher/route registration is by-product of
   370  	// some limitations in the gorilla/mux router. We are using it to keep
   371  	// routing consistent between the client and server, but we may want to
   372  	// replace it with manual routing and structure-based dispatch for better
   373  	// control over the request execution.
   374  
   375  	app.router.GetRoute(routeName).Handler(app.dispatcher(dispatch))
   376  }
   377  
   378  // configureEvents prepares the event sink for action.
   379  func (app *App) configureEvents(configuration *configuration.Configuration) {
   380  	// Configure all of the endpoint sinks.
   381  	var sinks []notifications.Sink
   382  	for _, endpoint := range configuration.Notifications.Endpoints {
   383  		if endpoint.Disabled {
   384  			ctxu.GetLogger(app).Infof("endpoint %s disabled, skipping", endpoint.Name)
   385  			continue
   386  		}
   387  
   388  		ctxu.GetLogger(app).Infof("configuring endpoint %v (%v), timeout=%s, headers=%v", endpoint.Name, endpoint.URL, endpoint.Timeout, endpoint.Headers)
   389  		endpoint := notifications.NewEndpoint(endpoint.Name, endpoint.URL, notifications.EndpointConfig{
   390  			Timeout:   endpoint.Timeout,
   391  			Threshold: endpoint.Threshold,
   392  			Backoff:   endpoint.Backoff,
   393  			Headers:   endpoint.Headers,
   394  		})
   395  
   396  		sinks = append(sinks, endpoint)
   397  	}
   398  
   399  	// NOTE(stevvooe): Moving to a new queueing implementation is as easy as
   400  	// replacing broadcaster with a rabbitmq implementation. It's recommended
   401  	// that the registry instances also act as the workers to keep deployment
   402  	// simple.
   403  	app.events.sink = notifications.NewBroadcaster(sinks...)
   404  
   405  	// Populate registry event source
   406  	hostname, err := os.Hostname()
   407  	if err != nil {
   408  		hostname = configuration.HTTP.Addr
   409  	} else {
   410  		// try to pick the port off the config
   411  		_, port, err := net.SplitHostPort(configuration.HTTP.Addr)
   412  		if err == nil {
   413  			hostname = net.JoinHostPort(hostname, port)
   414  		}
   415  	}
   416  
   417  	app.events.source = notifications.SourceRecord{
   418  		Addr:       hostname,
   419  		InstanceID: ctxu.GetStringValue(app, "instance.id"),
   420  	}
   421  }
   422  
   423  func (app *App) configureRedis(configuration *configuration.Configuration) {
   424  	if configuration.Redis.Addr == "" {
   425  		ctxu.GetLogger(app).Infof("redis not configured")
   426  		return
   427  	}
   428  
   429  	pool := &redis.Pool{
   430  		Dial: func() (redis.Conn, error) {
   431  			// TODO(stevvooe): Yet another use case for contextual timing.
   432  			ctx := context.WithValue(app, "redis.connect.startedat", time.Now())
   433  
   434  			done := func(err error) {
   435  				logger := ctxu.GetLoggerWithField(ctx, "redis.connect.duration",
   436  					ctxu.Since(ctx, "redis.connect.startedat"))
   437  				if err != nil {
   438  					logger.Errorf("redis: error connecting: %v", err)
   439  				} else {
   440  					logger.Infof("redis: connect %v", configuration.Redis.Addr)
   441  				}
   442  			}
   443  
   444  			conn, err := redis.DialTimeout("tcp",
   445  				configuration.Redis.Addr,
   446  				configuration.Redis.DialTimeout,
   447  				configuration.Redis.ReadTimeout,
   448  				configuration.Redis.WriteTimeout)
   449  			if err != nil {
   450  				ctxu.GetLogger(app).Errorf("error connecting to redis instance %s: %v",
   451  					configuration.Redis.Addr, err)
   452  				done(err)
   453  				return nil, err
   454  			}
   455  
   456  			// authorize the connection
   457  			if configuration.Redis.Password != "" {
   458  				if _, err = conn.Do("AUTH", configuration.Redis.Password); err != nil {
   459  					defer conn.Close()
   460  					done(err)
   461  					return nil, err
   462  				}
   463  			}
   464  
   465  			// select the database to use
   466  			if configuration.Redis.DB != 0 {
   467  				if _, err = conn.Do("SELECT", configuration.Redis.DB); err != nil {
   468  					defer conn.Close()
   469  					done(err)
   470  					return nil, err
   471  				}
   472  			}
   473  
   474  			done(nil)
   475  			return conn, nil
   476  		},
   477  		MaxIdle:     configuration.Redis.Pool.MaxIdle,
   478  		MaxActive:   configuration.Redis.Pool.MaxActive,
   479  		IdleTimeout: configuration.Redis.Pool.IdleTimeout,
   480  		TestOnBorrow: func(c redis.Conn, t time.Time) error {
   481  			// TODO(stevvooe): We can probably do something more interesting
   482  			// here with the health package.
   483  			_, err := c.Do("PING")
   484  			return err
   485  		},
   486  		Wait: false, // if a connection is not avialable, proceed without cache.
   487  	}
   488  
   489  	app.redis = pool
   490  
   491  	// setup expvar
   492  	registry := expvar.Get("registry")
   493  	if registry == nil {
   494  		registry = expvar.NewMap("registry")
   495  	}
   496  
   497  	registry.(*expvar.Map).Set("redis", expvar.Func(func() interface{} {
   498  		return map[string]interface{}{
   499  			"Config": configuration.Redis,
   500  			"Active": app.redis.ActiveCount(),
   501  		}
   502  	}))
   503  }
   504  
   505  // configureLogHook prepares logging hook parameters.
   506  func (app *App) configureLogHook(configuration *configuration.Configuration) {
   507  	entry, ok := ctxu.GetLogger(app).(*log.Entry)
   508  	if !ok {
   509  		// somehow, we are not using logrus
   510  		return
   511  	}
   512  
   513  	logger := entry.Logger
   514  
   515  	for _, configHook := range configuration.Log.Hooks {
   516  		if !configHook.Disabled {
   517  			switch configHook.Type {
   518  			case "mail":
   519  				hook := &logHook{}
   520  				hook.LevelsParam = configHook.Levels
   521  				hook.Mail = &mailer{
   522  					Addr:     configHook.MailOptions.SMTP.Addr,
   523  					Username: configHook.MailOptions.SMTP.Username,
   524  					Password: configHook.MailOptions.SMTP.Password,
   525  					Insecure: configHook.MailOptions.SMTP.Insecure,
   526  					From:     configHook.MailOptions.From,
   527  					To:       configHook.MailOptions.To,
   528  				}
   529  				logger.Hooks.Add(hook)
   530  			default:
   531  			}
   532  		}
   533  	}
   534  }
   535  
   536  // configureSecret creates a random secret if a secret wasn't included in the
   537  // configuration.
   538  func (app *App) configureSecret(configuration *configuration.Configuration) {
   539  	if configuration.HTTP.Secret == "" {
   540  		var secretBytes [randomSecretSize]byte
   541  		if _, err := cryptorand.Read(secretBytes[:]); err != nil {
   542  			panic(fmt.Sprintf("could not generate random bytes for HTTP secret: %v", err))
   543  		}
   544  		configuration.HTTP.Secret = string(secretBytes[:])
   545  		ctxu.GetLogger(app).Warn("No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable.")
   546  	}
   547  }
   548  
   549  func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   550  	defer r.Body.Close() // ensure that request body is always closed.
   551  
   552  	// Instantiate an http context here so we can track the error codes
   553  	// returned by the request router.
   554  	ctx := defaultContextManager.context(app, w, r)
   555  
   556  	defer func() {
   557  		status, ok := ctx.Value("http.response.status").(int)
   558  		if ok && status >= 200 && status <= 399 {
   559  			ctxu.GetResponseLogger(ctx).Infof("response completed")
   560  		}
   561  	}()
   562  	defer defaultContextManager.release(ctx)
   563  
   564  	// NOTE(stevvooe): Total hack to get instrumented responsewriter from context.
   565  	var err error
   566  	w, err = ctxu.GetResponseWriter(ctx)
   567  	if err != nil {
   568  		ctxu.GetLogger(ctx).Warnf("response writer not found in context")
   569  	}
   570  
   571  	// Set a header with the Docker Distribution API Version for all responses.
   572  	w.Header().Add("Docker-Distribution-API-Version", "registry/2.0")
   573  	app.router.ServeHTTP(w, r)
   574  }
   575  
   576  // dispatchFunc takes a context and request and returns a constructed handler
   577  // for the route. The dispatcher will use this to dynamically create request
   578  // specific handlers for each endpoint without creating a new router for each
   579  // request.
   580  type dispatchFunc func(ctx *Context, r *http.Request) http.Handler
   581  
   582  // TODO(stevvooe): dispatchers should probably have some validation error
   583  // chain with proper error reporting.
   584  
   585  // dispatcher returns a handler that constructs a request specific context and
   586  // handler, using the dispatch factory function.
   587  func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
   588  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   589  		for headerName, headerValues := range app.Config.HTTP.Headers {
   590  			for _, value := range headerValues {
   591  				w.Header().Add(headerName, value)
   592  			}
   593  		}
   594  
   595  		context := app.context(w, r)
   596  
   597  		if err := app.authorized(w, r, context); err != nil {
   598  			ctxu.GetLogger(context).Warnf("error authorizing context: %v", err)
   599  			return
   600  		}
   601  
   602  		// Add username to request logging
   603  		context.Context = ctxu.WithLogger(context.Context, ctxu.GetLogger(context.Context, "auth.user.name"))
   604  
   605  		if app.nameRequired(r) {
   606  			nameRef, err := reference.ParseNamed(getName(context))
   607  			if err != nil {
   608  				ctxu.GetLogger(context).Errorf("error parsing reference from context: %v", err)
   609  				context.Errors = append(context.Errors, distribution.ErrRepositoryNameInvalid{
   610  					Name:   getName(context),
   611  					Reason: err,
   612  				})
   613  				if err := errcode.ServeJSON(w, context.Errors); err != nil {
   614  					ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   615  				}
   616  				return
   617  			}
   618  			repository, err := app.registry.Repository(context, nameRef)
   619  
   620  			if err != nil {
   621  				ctxu.GetLogger(context).Errorf("error resolving repository: %v", err)
   622  
   623  				switch err := err.(type) {
   624  				case distribution.ErrRepositoryUnknown:
   625  					context.Errors = append(context.Errors, v2.ErrorCodeNameUnknown.WithDetail(err))
   626  				case distribution.ErrRepositoryNameInvalid:
   627  					context.Errors = append(context.Errors, v2.ErrorCodeNameInvalid.WithDetail(err))
   628  				}
   629  
   630  				if err := errcode.ServeJSON(w, context.Errors); err != nil {
   631  					ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   632  				}
   633  				return
   634  			}
   635  
   636  			// assign and decorate the authorized repository with an event bridge.
   637  			context.Repository = notifications.Listen(
   638  				repository,
   639  				app.eventBridge(context, r))
   640  
   641  			context.Repository, err = applyRepoMiddleware(context.Context, context.Repository, app.Config.Middleware["repository"])
   642  			if err != nil {
   643  				ctxu.GetLogger(context).Errorf("error initializing repository middleware: %v", err)
   644  				context.Errors = append(context.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
   645  
   646  				if err := errcode.ServeJSON(w, context.Errors); err != nil {
   647  					ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   648  				}
   649  				return
   650  			}
   651  		}
   652  
   653  		dispatch(context, r).ServeHTTP(w, r)
   654  		// Automated error response handling here. Handlers may return their
   655  		// own errors if they need different behavior (such as range errors
   656  		// for layer upload).
   657  		if context.Errors.Len() > 0 {
   658  			if err := errcode.ServeJSON(w, context.Errors); err != nil {
   659  				ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   660  			}
   661  
   662  			app.logError(context, context.Errors)
   663  		}
   664  	})
   665  }
   666  
   667  func (app *App) logError(context context.Context, errors errcode.Errors) {
   668  	for _, e1 := range errors {
   669  		var c ctxu.Context
   670  
   671  		switch e1.(type) {
   672  		case errcode.Error:
   673  			e, _ := e1.(errcode.Error)
   674  			c = ctxu.WithValue(context, "err.code", e.Code)
   675  			c = ctxu.WithValue(c, "err.message", e.Code.Message())
   676  			c = ctxu.WithValue(c, "err.detail", e.Detail)
   677  		case errcode.ErrorCode:
   678  			e, _ := e1.(errcode.ErrorCode)
   679  			c = ctxu.WithValue(context, "err.code", e)
   680  			c = ctxu.WithValue(c, "err.message", e.Message())
   681  		default:
   682  			// just normal go 'error'
   683  			c = ctxu.WithValue(context, "err.code", errcode.ErrorCodeUnknown)
   684  			c = ctxu.WithValue(c, "err.message", e1.Error())
   685  		}
   686  
   687  		c = ctxu.WithLogger(c, ctxu.GetLogger(c,
   688  			"err.code",
   689  			"err.message",
   690  			"err.detail"))
   691  		ctxu.GetResponseLogger(c).Errorf("response completed with error")
   692  	}
   693  }
   694  
   695  // context constructs the context object for the application. This only be
   696  // called once per request.
   697  func (app *App) context(w http.ResponseWriter, r *http.Request) *Context {
   698  	ctx := defaultContextManager.context(app, w, r)
   699  	ctx = ctxu.WithVars(ctx, r)
   700  	ctx = ctxu.WithLogger(ctx, ctxu.GetLogger(ctx,
   701  		"vars.name",
   702  		"vars.reference",
   703  		"vars.digest",
   704  		"vars.uuid"))
   705  
   706  	context := &Context{
   707  		App:     app,
   708  		Context: ctx,
   709  	}
   710  
   711  	if app.httpHost.Scheme != "" && app.httpHost.Host != "" {
   712  		// A "host" item in the configuration takes precedence over
   713  		// X-Forwarded-Proto and X-Forwarded-Host headers, and the
   714  		// hostname in the request.
   715  		context.urlBuilder = v2.NewURLBuilder(&app.httpHost)
   716  	} else {
   717  		context.urlBuilder = v2.NewURLBuilderFromRequest(r)
   718  	}
   719  
   720  	return context
   721  }
   722  
   723  // authorized checks if the request can proceed with access to the requested
   724  // repository. If it succeeds, the context may access the requested
   725  // repository. An error will be returned if access is not available.
   726  func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Context) error {
   727  	ctxu.GetLogger(context).Debug("authorizing request")
   728  	repo := getName(context)
   729  
   730  	if app.accessController == nil {
   731  		return nil // access controller is not enabled.
   732  	}
   733  
   734  	var accessRecords []auth.Access
   735  
   736  	if repo != "" {
   737  		accessRecords = appendAccessRecords(accessRecords, r.Method, repo)
   738  		if fromRepo := r.FormValue("from"); fromRepo != "" {
   739  			// mounting a blob from one repository to another requires pull (GET)
   740  			// access to the source repository.
   741  			accessRecords = appendAccessRecords(accessRecords, "GET", fromRepo)
   742  		}
   743  	} else {
   744  		// Only allow the name not to be set on the base route.
   745  		if app.nameRequired(r) {
   746  			// For this to be properly secured, repo must always be set for a
   747  			// resource that may make a modification. The only condition under
   748  			// which name is not set and we still allow access is when the
   749  			// base route is accessed. This section prevents us from making
   750  			// that mistake elsewhere in the code, allowing any operation to
   751  			// proceed.
   752  			if err := errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized); err != nil {
   753  				ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   754  			}
   755  			return fmt.Errorf("forbidden: no repository name")
   756  		}
   757  		accessRecords = appendCatalogAccessRecord(accessRecords, r)
   758  	}
   759  
   760  	ctx, err := app.accessController.Authorized(context.Context, accessRecords...)
   761  	if err != nil {
   762  		switch err := err.(type) {
   763  		case auth.Challenge:
   764  			// Add the appropriate WWW-Auth header
   765  			err.SetHeaders(w)
   766  
   767  			if err := errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized.WithDetail(accessRecords)); err != nil {
   768  				ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   769  			}
   770  		default:
   771  			// This condition is a potential security problem either in
   772  			// the configuration or whatever is backing the access
   773  			// controller. Just return a bad request with no information
   774  			// to avoid exposure. The request should not proceed.
   775  			ctxu.GetLogger(context).Errorf("error checking authorization: %v", err)
   776  			w.WriteHeader(http.StatusBadRequest)
   777  		}
   778  
   779  		return err
   780  	}
   781  
   782  	// TODO(stevvooe): This pattern needs to be cleaned up a bit. One context
   783  	// should be replaced by another, rather than replacing the context on a
   784  	// mutable object.
   785  	context.Context = ctx
   786  	return nil
   787  }
   788  
   789  // eventBridge returns a bridge for the current request, configured with the
   790  // correct actor and source.
   791  func (app *App) eventBridge(ctx *Context, r *http.Request) notifications.Listener {
   792  	actor := notifications.ActorRecord{
   793  		Name: getUserName(ctx, r),
   794  	}
   795  	request := notifications.NewRequestRecord(ctxu.GetRequestID(ctx), r)
   796  
   797  	return notifications.NewBridge(ctx.urlBuilder, app.events.source, actor, request, app.events.sink)
   798  }
   799  
   800  // nameRequired returns true if the route requires a name.
   801  func (app *App) nameRequired(r *http.Request) bool {
   802  	route := mux.CurrentRoute(r)
   803  	routeName := route.GetName()
   804  	return route == nil || (routeName != v2.RouteNameBase && routeName != v2.RouteNameCatalog)
   805  }
   806  
   807  // apiBase implements a simple yes-man for doing overall checks against the
   808  // api. This can support auth roundtrips to support docker login.
   809  func apiBase(w http.ResponseWriter, r *http.Request) {
   810  	const emptyJSON = "{}"
   811  	// Provide a simple /v2/ 200 OK response with empty json response.
   812  	w.Header().Set("Content-Type", "application/json; charset=utf-8")
   813  	w.Header().Set("Content-Length", fmt.Sprint(len(emptyJSON)))
   814  
   815  	fmt.Fprint(w, emptyJSON)
   816  }
   817  
   818  // appendAccessRecords checks the method and adds the appropriate Access records to the records list.
   819  func appendAccessRecords(records []auth.Access, method string, repo string) []auth.Access {
   820  	resource := auth.Resource{
   821  		Type: "repository",
   822  		Name: repo,
   823  	}
   824  
   825  	switch method {
   826  	case "GET", "HEAD":
   827  		records = append(records,
   828  			auth.Access{
   829  				Resource: resource,
   830  				Action:   "pull",
   831  			})
   832  	case "POST", "PUT", "PATCH":
   833  		records = append(records,
   834  			auth.Access{
   835  				Resource: resource,
   836  				Action:   "pull",
   837  			},
   838  			auth.Access{
   839  				Resource: resource,
   840  				Action:   "push",
   841  			})
   842  	case "DELETE":
   843  		// DELETE access requires full admin rights, which is represented
   844  		// as "*". This may not be ideal.
   845  		records = append(records,
   846  			auth.Access{
   847  				Resource: resource,
   848  				Action:   "*",
   849  			})
   850  	}
   851  	return records
   852  }
   853  
   854  // Add the access record for the catalog if it's our current route
   855  func appendCatalogAccessRecord(accessRecords []auth.Access, r *http.Request) []auth.Access {
   856  	route := mux.CurrentRoute(r)
   857  	routeName := route.GetName()
   858  
   859  	if routeName == v2.RouteNameCatalog {
   860  		resource := auth.Resource{
   861  			Type: "registry",
   862  			Name: "catalog",
   863  		}
   864  
   865  		accessRecords = append(accessRecords,
   866  			auth.Access{
   867  				Resource: resource,
   868  				Action:   "*",
   869  			})
   870  	}
   871  	return accessRecords
   872  }
   873  
   874  // applyRegistryMiddleware wraps a registry instance with the configured middlewares
   875  func applyRegistryMiddleware(ctx context.Context, registry distribution.Namespace, middlewares []configuration.Middleware) (distribution.Namespace, error) {
   876  	for _, mw := range middlewares {
   877  		rmw, err := registrymiddleware.Get(ctx, mw.Name, mw.Options, registry)
   878  		if err != nil {
   879  			return nil, fmt.Errorf("unable to configure registry middleware (%s): %s", mw.Name, err)
   880  		}
   881  		registry = rmw
   882  	}
   883  	return registry, nil
   884  
   885  }
   886  
   887  // applyRepoMiddleware wraps a repository with the configured middlewares
   888  func applyRepoMiddleware(ctx context.Context, repository distribution.Repository, middlewares []configuration.Middleware) (distribution.Repository, error) {
   889  	for _, mw := range middlewares {
   890  		rmw, err := repositorymiddleware.Get(ctx, mw.Name, mw.Options, repository)
   891  		if err != nil {
   892  			return nil, err
   893  		}
   894  		repository = rmw
   895  	}
   896  	return repository, nil
   897  }
   898  
   899  // applyStorageMiddleware wraps a storage driver with the configured middlewares
   900  func applyStorageMiddleware(driver storagedriver.StorageDriver, middlewares []configuration.Middleware) (storagedriver.StorageDriver, error) {
   901  	for _, mw := range middlewares {
   902  		smw, err := storagemiddleware.Get(mw.Name, mw.Options, driver)
   903  		if err != nil {
   904  			return nil, fmt.Errorf("unable to configure storage middleware (%s): %v", mw.Name, err)
   905  		}
   906  		driver = smw
   907  	}
   908  	return driver, nil
   909  }
   910  
   911  // uploadPurgeDefaultConfig provides a default configuration for upload
   912  // purging to be used in the absence of configuration in the
   913  // confifuration file
   914  func uploadPurgeDefaultConfig() map[interface{}]interface{} {
   915  	config := map[interface{}]interface{}{}
   916  	config["enabled"] = true
   917  	config["age"] = "168h"
   918  	config["interval"] = "24h"
   919  	config["dryrun"] = false
   920  	return config
   921  }
   922  
   923  func badPurgeUploadConfig(reason string) {
   924  	panic(fmt.Sprintf("Unable to parse upload purge configuration: %s", reason))
   925  }
   926  
   927  // startUploadPurger schedules a goroutine which will periodically
   928  // check upload directories for old files and delete them
   929  func startUploadPurger(ctx context.Context, storageDriver storagedriver.StorageDriver, log ctxu.Logger, config map[interface{}]interface{}) {
   930  	if config["enabled"] == false {
   931  		return
   932  	}
   933  
   934  	var purgeAgeDuration time.Duration
   935  	var err error
   936  	purgeAge, ok := config["age"]
   937  	if ok {
   938  		ageStr, ok := purgeAge.(string)
   939  		if !ok {
   940  			badPurgeUploadConfig("age is not a string")
   941  		}
   942  		purgeAgeDuration, err = time.ParseDuration(ageStr)
   943  		if err != nil {
   944  			badPurgeUploadConfig(fmt.Sprintf("Cannot parse duration: %s", err.Error()))
   945  		}
   946  	} else {
   947  		badPurgeUploadConfig("age missing")
   948  	}
   949  
   950  	var intervalDuration time.Duration
   951  	interval, ok := config["interval"]
   952  	if ok {
   953  		intervalStr, ok := interval.(string)
   954  		if !ok {
   955  			badPurgeUploadConfig("interval is not a string")
   956  		}
   957  
   958  		intervalDuration, err = time.ParseDuration(intervalStr)
   959  		if err != nil {
   960  			badPurgeUploadConfig(fmt.Sprintf("Cannot parse interval: %s", err.Error()))
   961  		}
   962  	} else {
   963  		badPurgeUploadConfig("interval missing")
   964  	}
   965  
   966  	var dryRunBool bool
   967  	dryRun, ok := config["dryrun"]
   968  	if ok {
   969  		dryRunBool, ok = dryRun.(bool)
   970  		if !ok {
   971  			badPurgeUploadConfig("cannot parse dryrun")
   972  		}
   973  	} else {
   974  		badPurgeUploadConfig("dryrun missing")
   975  	}
   976  
   977  	go func() {
   978  		rand.Seed(time.Now().Unix())
   979  		jitter := time.Duration(rand.Int()%60) * time.Minute
   980  		log.Infof("Starting upload purge in %s", jitter)
   981  		time.Sleep(jitter)
   982  
   983  		for {
   984  			storage.PurgeUploads(ctx, storageDriver, time.Now().Add(-purgeAgeDuration), !dryRunBool)
   985  			log.Infof("Starting upload purge in %s", intervalDuration)
   986  			time.Sleep(intervalDuration)
   987  		}
   988  	}()
   989  }