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
37 var cipherSuites = map[string]uint16{
38
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
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
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
81 var defaultTLSVersionStr = "tls1.2"
82 var tlsVersions = map[string]uint16{
83
84 "tls1.0": tls.VersionTLS10,
85 "tls1.1": tls.VersionTLS11,
86 "tls1.2": tls.VersionTLS12,
87 "tls1.3": tls.VersionTLS13,
88 }
89
90
91 var quit = make(chan os.Signal, 1)
92
93
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
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
140
141 type Registry struct {
142 config *configuration.Configuration
143 app *handlers.App
144 server *http.Server
145 }
146
147
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
158
159 uuid.Loggerf = dcontext.GetLogger(ctx).Warnf
160
161 app := handlers.NewApp(ctx, config)
162
163
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
185
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
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
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() {
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
303 signal.Notify(quit, syscall.SIGTERM)
304 serveErr := make(chan error)
305
306
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
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
347
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"
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
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
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
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
422 hook, err := logrus_bugsnag.NewBugsnagHook()
423 if err != nil {
424 log.Fatalln(err)
425 }
426
427 log.AddHook(hook)
428 }
429
430
431
432
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
445
446
447
448
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