...

Source file src/github.com/docker/distribution/registry/registry.go

Documentation: github.com/docker/distribution/registry

     1  package registry
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"os"
    11  	"os/signal"
    12  	"strings"
    13  	"syscall"
    14  	"time"
    15  
    16  	"rsc.io/letsencrypt"
    17  
    18  	logrus_bugsnag "github.com/Shopify/logrus-bugsnag"
    19  
    20  	logstash "github.com/bshuster-repo/logrus-logstash-hook"
    21  	"github.com/bugsnag/bugsnag-go"
    22  	"github.com/docker/distribution/configuration"
    23  	dcontext "github.com/docker/distribution/context"
    24  	"github.com/docker/distribution/health"
    25  	"github.com/docker/distribution/registry/handlers"
    26  	"github.com/docker/distribution/registry/listener"
    27  	"github.com/docker/distribution/uuid"
    28  	"github.com/docker/distribution/version"
    29  	"github.com/docker/go-metrics"
    30  	gorhandlers "github.com/gorilla/handlers"
    31  	log "github.com/sirupsen/logrus"
    32  	"github.com/spf13/cobra"
    33  	"github.com/yvasiyarov/gorelic"
    34  )
    35  
    36  // a map of TLS cipher suite names to constants in https://golang.org/pkg/crypto/tls/#pkg-constants
    37  var cipherSuites = map[string]uint16{
    38  	// TLS 1.0 - 1.2 cipher suites
    39  	"TLS_RSA_WITH_RC4_128_SHA":                      tls.TLS_RSA_WITH_RC4_128_SHA,
    40  	"TLS_RSA_WITH_3DES_EDE_CBC_SHA":                 tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
    41  	"TLS_RSA_WITH_AES_128_CBC_SHA":                  tls.TLS_RSA_WITH_AES_128_CBC_SHA,
    42  	"TLS_RSA_WITH_AES_256_CBC_SHA":                  tls.TLS_RSA_WITH_AES_256_CBC_SHA,
    43  	"TLS_RSA_WITH_AES_128_CBC_SHA256":               tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
    44  	"TLS_RSA_WITH_AES_128_GCM_SHA256":               tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
    45  	"TLS_RSA_WITH_AES_256_GCM_SHA384":               tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
    46  	"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA":              tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
    47  	"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA":          tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
    48  	"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA":          tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
    49  	"TLS_ECDHE_RSA_WITH_RC4_128_SHA":                tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
    50  	"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":           tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
    51  	"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA":            tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
    52  	"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA":            tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
    53  	"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":       tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
    54  	"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256":         tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
    55  	"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256":         tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    56  	"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":       tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    57  	"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":         tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
    58  	"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":       tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
    59  	"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256":   tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
    60  	"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
    61  	// TLS 1.3 cipher suites
    62  	"TLS_AES_128_GCM_SHA256":       tls.TLS_AES_128_GCM_SHA256,
    63  	"TLS_AES_256_GCM_SHA384":       tls.TLS_AES_256_GCM_SHA384,
    64  	"TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256,
    65  }
    66  
    67  // a list of default ciphersuites to utilize
    68  var defaultCipherSuites = []uint16{
    69  	tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
    70  	tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
    71  	tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
    72  	tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
    73  	tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    74  	tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    75  	tls.TLS_AES_128_GCM_SHA256,
    76  	tls.TLS_CHACHA20_POLY1305_SHA256,
    77  	tls.TLS_AES_256_GCM_SHA384,
    78  }
    79  
    80  // maps tls version strings to constants
    81  var defaultTLSVersionStr = "tls1.2"
    82  var tlsVersions = map[string]uint16{
    83  	// user specified values
    84  	"tls1.0": tls.VersionTLS10,
    85  	"tls1.1": tls.VersionTLS11,
    86  	"tls1.2": tls.VersionTLS12,
    87  	"tls1.3": tls.VersionTLS13,
    88  }
    89  
    90  // this channel gets notified when process receives signal. It is global to ease unit testing
    91  var quit = make(chan os.Signal, 1)
    92  
    93  // ServeCmd is a cobra command for running the registry.
    94  var ServeCmd = &cobra.Command{
    95  	Use:   "serve <config>",
    96  	Short: "`serve` stores and distributes Docker images",
    97  	Long:  "`serve` stores and distributes Docker images.",
    98  	Run: func(cmd *cobra.Command, args []string) {
    99  
   100  		// setup context
   101  		ctx := dcontext.WithVersion(dcontext.Background(), version.Version)
   102  
   103  		config, err := resolveConfiguration(args)
   104  		if err != nil {
   105  			fmt.Fprintf(os.Stderr, "configuration error: %v\n", err)
   106  			cmd.Usage()
   107  			os.Exit(1)
   108  		}
   109  
   110  		if config.HTTP.Debug.Addr != "" {
   111  			go func(addr string) {
   112  				log.Infof("debug server listening %v", addr)
   113  				if err := http.ListenAndServe(addr, nil); err != nil {
   114  					log.Fatalf("error listening on debug interface: %v", err)
   115  				}
   116  			}(config.HTTP.Debug.Addr)
   117  		}
   118  
   119  		registry, err := NewRegistry(ctx, config)
   120  		if err != nil {
   121  			log.Fatalln(err)
   122  		}
   123  
   124  		if config.HTTP.Debug.Prometheus.Enabled {
   125  			path := config.HTTP.Debug.Prometheus.Path
   126  			if path == "" {
   127  				path = "/metrics"
   128  			}
   129  			log.Info("providing prometheus metrics on ", path)
   130  			http.Handle(path, metrics.Handler())
   131  		}
   132  
   133  		if err = registry.ListenAndServe(); err != nil {
   134  			log.Fatalln(err)
   135  		}
   136  	},
   137  }
   138  
   139  // A Registry represents a complete instance of the registry.
   140  // TODO(aaronl): It might make sense for Registry to become an interface.
   141  type Registry struct {
   142  	config *configuration.Configuration
   143  	app    *handlers.App
   144  	server *http.Server
   145  }
   146  
   147  // NewRegistry creates a new registry from a context and configuration struct.
   148  func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Registry, error) {
   149  	var err error
   150  	ctx, err = configureLogging(ctx, config)
   151  	if err != nil {
   152  		return nil, fmt.Errorf("error configuring logger: %v", err)
   153  	}
   154  
   155  	configureBugsnag(config)
   156  
   157  	// inject a logger into the uuid library. warns us if there is a problem
   158  	// with uuid generation under low entropy.
   159  	uuid.Loggerf = dcontext.GetLogger(ctx).Warnf
   160  
   161  	app := handlers.NewApp(ctx, config)
   162  	// TODO(aaronl): The global scope of the health checks means NewRegistry
   163  	// can only be called once per process.
   164  	app.RegisterHealthChecks()
   165  	handler := configureReporting(app)
   166  	handler = alive("/", handler)
   167  	handler = health.Handler(handler)
   168  	handler = panicHandler(handler)
   169  	if !config.Log.AccessLog.Disabled {
   170  		handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler)
   171  	}
   172  
   173  	server := &http.Server{
   174  		Handler: handler,
   175  	}
   176  
   177  	return &Registry{
   178  		app:    app,
   179  		config: config,
   180  		server: server,
   181  	}, nil
   182  }
   183  
   184  // takes a list of cipher suites and converts it to a list of respective tls constants
   185  // if an empty list is provided, then the defaults will be used
   186  func getCipherSuites(names []string) ([]uint16, error) {
   187  	if len(names) == 0 {
   188  		return defaultCipherSuites, nil
   189  	}
   190  	cipherSuiteConsts := make([]uint16, len(names))
   191  	for i, name := range names {
   192  		cipherSuiteConst, ok := cipherSuites[name]
   193  		if !ok {
   194  			return nil, fmt.Errorf("unknown TLS cipher suite '%s' specified for http.tls.cipherSuites", name)
   195  		}
   196  		cipherSuiteConsts[i] = cipherSuiteConst
   197  	}
   198  	return cipherSuiteConsts, nil
   199  }
   200  
   201  // takes a list of cipher suite ids and converts it to a list of respective names
   202  func getCipherSuiteNames(ids []uint16) []string {
   203  	if len(ids) == 0 {
   204  		return nil
   205  	}
   206  	names := make([]string, len(ids))
   207  	for i, id := range ids {
   208  		names[i] = tls.CipherSuiteName(id)
   209  	}
   210  	return names
   211  }
   212  
   213  // ListenAndServe runs the registry's HTTP server.
   214  func (registry *Registry) ListenAndServe() error {
   215  	config := registry.config
   216  
   217  	ln, err := listener.NewListener(config.HTTP.Net, config.HTTP.Addr)
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	if config.HTTP.TLS.Certificate != "" || config.HTTP.TLS.LetsEncrypt.CacheFile != "" {
   223  		if config.HTTP.TLS.MinimumTLS == "" {
   224  			config.HTTP.TLS.MinimumTLS = defaultTLSVersionStr
   225  		}
   226  		tlsMinVersion, ok := tlsVersions[config.HTTP.TLS.MinimumTLS]
   227  		if !ok {
   228  			return fmt.Errorf("unknown minimum TLS level '%s' specified for http.tls.minimumtls", config.HTTP.TLS.MinimumTLS)
   229  		}
   230  		dcontext.GetLogger(registry.app).Infof("restricting TLS version to %s or higher", config.HTTP.TLS.MinimumTLS)
   231  
   232  		tlsCipherSuites, err := getCipherSuites(config.HTTP.TLS.CipherSuites)
   233  		if err != nil {
   234  			return err
   235  		}
   236  		dcontext.GetLogger(registry.app).Infof("restricting TLS cipher suites to: %s", strings.Join(getCipherSuiteNames(tlsCipherSuites), ","))
   237  
   238  		tlsConf := &tls.Config{
   239  			ClientAuth:   tls.NoClientCert,
   240  			NextProtos:   nextProtos(config),
   241  			MinVersion:   tlsMinVersion,
   242  			CipherSuites: tlsCipherSuites,
   243  		}
   244  
   245  		if config.HTTP.TLS.LetsEncrypt.CacheFile != "" {
   246  			if config.HTTP.TLS.Certificate != "" {
   247  				return fmt.Errorf("cannot specify both certificate and Let's Encrypt")
   248  			}
   249  			var m letsencrypt.Manager
   250  			if err := m.CacheFile(config.HTTP.TLS.LetsEncrypt.CacheFile); err != nil {
   251  				return err
   252  			}
   253  			if !m.Registered() {
   254  				if err := m.Register(config.HTTP.TLS.LetsEncrypt.Email, nil); err != nil {
   255  					return err
   256  				}
   257  			}
   258  			if len(config.HTTP.TLS.LetsEncrypt.Hosts) > 0 {
   259  				m.SetHosts(config.HTTP.TLS.LetsEncrypt.Hosts)
   260  			}
   261  			tlsConf.GetCertificate = m.GetCertificate
   262  		} else {
   263  			tlsConf.Certificates = make([]tls.Certificate, 1)
   264  			tlsConf.Certificates[0], err = tls.LoadX509KeyPair(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key)
   265  			if err != nil {
   266  				return err
   267  			}
   268  		}
   269  
   270  		if len(config.HTTP.TLS.ClientCAs) != 0 {
   271  			pool := x509.NewCertPool()
   272  
   273  			for _, ca := range config.HTTP.TLS.ClientCAs {
   274  				caPem, err := ioutil.ReadFile(ca)
   275  				if err != nil {
   276  					return err
   277  				}
   278  
   279  				if ok := pool.AppendCertsFromPEM(caPem); !ok {
   280  					return fmt.Errorf("could not add CA to pool")
   281  				}
   282  			}
   283  
   284  			for _, subj := range pool.Subjects() { //nolint:staticcheck // FIXME(thaJeztah): ignore SA1019: ac.(*accessController).rootCerts.Subjects has been deprecated since Go 1.18: if s was returned by SystemCertPool, Subjects will not include the system roots. (staticcheck)
   285  				dcontext.GetLogger(registry.app).Debugf("CA Subject: %s", string(subj))
   286  			}
   287  
   288  			tlsConf.ClientAuth = tls.RequireAndVerifyClientCert
   289  			tlsConf.ClientCAs = pool
   290  		}
   291  
   292  		ln = tls.NewListener(ln, tlsConf)
   293  		dcontext.GetLogger(registry.app).Infof("listening on %v, tls", ln.Addr())
   294  	} else {
   295  		dcontext.GetLogger(registry.app).Infof("listening on %v", ln.Addr())
   296  	}
   297  
   298  	if config.HTTP.DrainTimeout == 0 {
   299  		return registry.server.Serve(ln)
   300  	}
   301  
   302  	// setup channel to get notified on SIGTERM signal
   303  	signal.Notify(quit, syscall.SIGTERM)
   304  	serveErr := make(chan error)
   305  
   306  	// Start serving in goroutine and listen for stop signal in main thread
   307  	go func() {
   308  		serveErr <- registry.server.Serve(ln)
   309  	}()
   310  
   311  	select {
   312  	case err := <-serveErr:
   313  		return err
   314  	case <-quit:
   315  		dcontext.GetLogger(registry.app).Info("stopping server gracefully. Draining connections for ", config.HTTP.DrainTimeout)
   316  		// shutdown the server with a grace period of configured timeout
   317  		c, cancel := context.WithTimeout(context.Background(), config.HTTP.DrainTimeout)
   318  		defer cancel()
   319  		return registry.server.Shutdown(c)
   320  	}
   321  }
   322  
   323  func configureReporting(app *handlers.App) http.Handler {
   324  	var handler http.Handler = app
   325  
   326  	if app.Config.Reporting.Bugsnag.APIKey != "" {
   327  		handler = bugsnag.Handler(handler)
   328  	}
   329  
   330  	if app.Config.Reporting.NewRelic.LicenseKey != "" {
   331  		agent := gorelic.NewAgent()
   332  		agent.NewrelicLicense = app.Config.Reporting.NewRelic.LicenseKey
   333  		if app.Config.Reporting.NewRelic.Name != "" {
   334  			agent.NewrelicName = app.Config.Reporting.NewRelic.Name
   335  		}
   336  		agent.CollectHTTPStat = true
   337  		agent.Verbose = app.Config.Reporting.NewRelic.Verbose
   338  		agent.Run()
   339  
   340  		handler = agent.WrapHTTPHandler(handler)
   341  	}
   342  
   343  	return handler
   344  }
   345  
   346  // configureLogging prepares the context with a logger using the
   347  // configuration.
   348  func configureLogging(ctx context.Context, config *configuration.Configuration) (context.Context, error) {
   349  	log.SetLevel(logLevel(config.Log.Level))
   350  
   351  	formatter := config.Log.Formatter
   352  	if formatter == "" {
   353  		formatter = "text" // default formatter
   354  	}
   355  
   356  	switch formatter {
   357  	case "json":
   358  		log.SetFormatter(&log.JSONFormatter{
   359  			TimestampFormat: time.RFC3339Nano,
   360  		})
   361  	case "text":
   362  		log.SetFormatter(&log.TextFormatter{
   363  			TimestampFormat: time.RFC3339Nano,
   364  		})
   365  	case "logstash":
   366  		log.SetFormatter(&logstash.LogstashFormatter{
   367  			TimestampFormat: time.RFC3339Nano,
   368  		})
   369  	default:
   370  		// just let the library use default on empty string.
   371  		if config.Log.Formatter != "" {
   372  			return ctx, fmt.Errorf("unsupported logging formatter: %q", config.Log.Formatter)
   373  		}
   374  	}
   375  
   376  	if config.Log.Formatter != "" {
   377  		log.Debugf("using %q logging formatter", config.Log.Formatter)
   378  	}
   379  
   380  	if len(config.Log.Fields) > 0 {
   381  		// build up the static fields, if present.
   382  		var fields []interface{}
   383  		for k := range config.Log.Fields {
   384  			fields = append(fields, k)
   385  		}
   386  
   387  		ctx = dcontext.WithValues(ctx, config.Log.Fields)
   388  		ctx = dcontext.WithLogger(ctx, dcontext.GetLogger(ctx, fields...))
   389  	}
   390  
   391  	return ctx, nil
   392  }
   393  
   394  func logLevel(level configuration.Loglevel) log.Level {
   395  	l, err := log.ParseLevel(string(level))
   396  	if err != nil {
   397  		l = log.InfoLevel
   398  		log.Warnf("error parsing level %q: %v, using %q	", level, err, l)
   399  	}
   400  
   401  	return l
   402  }
   403  
   404  // configureBugsnag configures bugsnag reporting, if enabled
   405  func configureBugsnag(config *configuration.Configuration) {
   406  	if config.Reporting.Bugsnag.APIKey == "" {
   407  		return
   408  	}
   409  
   410  	bugsnagConfig := bugsnag.Configuration{
   411  		APIKey: config.Reporting.Bugsnag.APIKey,
   412  	}
   413  	if config.Reporting.Bugsnag.ReleaseStage != "" {
   414  		bugsnagConfig.ReleaseStage = config.Reporting.Bugsnag.ReleaseStage
   415  	}
   416  	if config.Reporting.Bugsnag.Endpoint != "" {
   417  		bugsnagConfig.Endpoint = config.Reporting.Bugsnag.Endpoint
   418  	}
   419  	bugsnag.Configure(bugsnagConfig)
   420  
   421  	// configure logrus bugsnag hook
   422  	hook, err := logrus_bugsnag.NewBugsnagHook()
   423  	if err != nil {
   424  		log.Fatalln(err)
   425  	}
   426  
   427  	log.AddHook(hook)
   428  }
   429  
   430  // panicHandler add an HTTP handler to web app. The handler recover the happening
   431  // panic. logrus.Panic transmits panic message to pre-config log hooks, which is
   432  // defined in config.yml.
   433  func panicHandler(handler http.Handler) http.Handler {
   434  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   435  		defer func() {
   436  			if err := recover(); err != nil {
   437  				log.Panic(fmt.Sprintf("%v", err))
   438  			}
   439  		}()
   440  		handler.ServeHTTP(w, r)
   441  	})
   442  }
   443  
   444  // alive simply wraps the handler with a route that always returns an http 200
   445  // response when the path is matched. If the path is not matched, the request
   446  // is passed to the provided handler. There is no guarantee of anything but
   447  // that the server is up. Wrap with other handlers (such as health.Handler)
   448  // for greater affect.
   449  func alive(path string, handler http.Handler) http.Handler {
   450  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   451  		if r.URL.Path == path {
   452  			w.Header().Set("Cache-Control", "no-cache")
   453  			w.WriteHeader(http.StatusOK)
   454  			return
   455  		}
   456  
   457  		handler.ServeHTTP(w, r)
   458  	})
   459  }
   460  
   461  func resolveConfiguration(args []string) (*configuration.Configuration, error) {
   462  	var configurationPath string
   463  
   464  	if len(args) > 0 {
   465  		configurationPath = args[0]
   466  	} else if os.Getenv("REGISTRY_CONFIGURATION_PATH") != "" {
   467  		configurationPath = os.Getenv("REGISTRY_CONFIGURATION_PATH")
   468  	}
   469  
   470  	if configurationPath == "" {
   471  		return nil, fmt.Errorf("configuration path unspecified")
   472  	}
   473  
   474  	fp, err := os.Open(configurationPath)
   475  	if err != nil {
   476  		return nil, err
   477  	}
   478  
   479  	defer fp.Close()
   480  
   481  	config, err := configuration.Parse(fp)
   482  	if err != nil {
   483  		return nil, fmt.Errorf("error parsing %s: %v", configurationPath, err)
   484  	}
   485  
   486  	return config, nil
   487  }
   488  
   489  func nextProtos(config *configuration.Configuration) []string {
   490  	switch config.HTTP.HTTP2.Disabled {
   491  	case true:
   492  		return []string{"http/1.1"}
   493  	default:
   494  		return []string{"h2", "http/1.1"}
   495  	}
   496  }
   497  

View as plain text