// Run sets up and starts a TLS server that can be cancelled using the // given configuration. The context it is passed is the context it should // use directly for the TLS server, and generate children off for requests func Run(ctx context.Context, conf Config) error { tcpAddr, err := net.ResolveTCPAddr("tcp", conf.Addr) if err != nil { return err } var lsnr net.Listener lsnr, err = net.ListenTCP("tcp", tcpAddr) if err != nil { return err } if conf.TLSConfig != nil { logrus.Info("Enabling TLS") lsnr = tls.NewListener(lsnr, conf.TLSConfig) } var ac auth.AccessController if conf.AuthMethod == "token" { authOptions, ok := conf.AuthOpts.(map[string]interface{}) if !ok { return fmt.Errorf("auth.options must be a map[string]interface{}") } ac, err = auth.GetAccessController(conf.AuthMethod, authOptions) if err != nil { return err } } svr := http.Server{ Addr: conf.Addr, Handler: RootHandler( ctx, ac, conf.Trust, conf.ConsistentCacheControlConfig, conf.CurrentCacheControlConfig, conf.RepoPrefixes), } logrus.Info("Starting on ", conf.Addr) err = svr.Serve(lsnr) return err }
// Run sets up and starts a TLS server that can be cancelled using the // given configuration. The context it is passed is the context it should // use directly for the TLS server, and generate children off for requests func Run(ctx context.Context, addr string, tlsConfig *tls.Config, trust signed.CryptoService, authMethod string, authOpts interface{}) error { tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return err } var lsnr net.Listener lsnr, err = net.ListenTCP("tcp", tcpAddr) if err != nil { return err } if tlsConfig != nil { logrus.Info("Enabling TLS") lsnr = tls.NewListener(lsnr, tlsConfig) } var ac auth.AccessController if authMethod == "token" { authOptions, ok := authOpts.(map[string]interface{}) if !ok { return fmt.Errorf("auth.options must be a map[string]interface{}") } ac, err = auth.GetAccessController(authMethod, authOptions) if err != nil { return err } } svr := http.Server{ Addr: addr, Handler: RootHandler(ac, ctx, trust), } logrus.Info("Starting on ", addr) err = svr.Serve(lsnr) return err }
// NewApp takes a configuration and returns a configured app, ready to serve // requests. The app only implements ServeHTTP and can be wrapped in other // handlers accordingly. func NewApp(ctx context.Context, configuration *configuration.Configuration) *App { app := &App{ Config: configuration, Context: ctx, router: v2.RouterWithPrefix(configuration.HTTP.Prefix), isCache: configuration.Proxy.RemoteURL != "", } // Register the handler dispatchers. app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler { return http.HandlerFunc(apiBase) }) app.register(v2.RouteNameManifest, imageManifestDispatcher) app.register(v2.RouteNameCatalog, catalogDispatcher) app.register(v2.RouteNameTags, tagsDispatcher) app.register(v2.RouteNameBlob, blobDispatcher) app.register(v2.RouteNameBlobUpload, blobUploadDispatcher) app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher) var err error app.driver, err = factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters()) if err != nil { // TODO(stevvooe): Move the creation of a service into a protected // method, where this is created lazily. Its status can be queried via // a health check. panic(err) } purgeConfig := uploadPurgeDefaultConfig() if mc, ok := configuration.Storage["maintenance"]; ok { if v, ok := mc["uploadpurging"]; ok { purgeConfig, ok = v.(map[interface{}]interface{}) if !ok { panic("uploadpurging config key must contain additional keys") } } if v, ok := mc["readonly"]; ok { readOnly, ok := v.(map[interface{}]interface{}) if !ok { panic("readonly config key must contain additional keys") } if readOnlyEnabled, ok := readOnly["enabled"]; ok { app.readOnly, ok = readOnlyEnabled.(bool) if !ok { panic("readonly's enabled config key must have a boolean value") } } } } startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig) app.driver, err = applyStorageMiddleware(app.driver, configuration.Middleware["storage"]) if err != nil { panic(err) } app.configureSecret(configuration) app.configureEvents(configuration) app.configureRedis(configuration) app.configureLogHook(configuration) // Generate an ephemeral key to be used for signing converted manifests // for clients that don't support schema2. app.trustKey, err = libtrust.GenerateECP256PrivateKey() if err != nil { panic(err) } if configuration.HTTP.Host != "" { u, err := url.Parse(configuration.HTTP.Host) if err != nil { panic(fmt.Sprintf(`could not parse http "host" parameter: %v`, err)) } app.httpHost = *u } options := []storage.RegistryOption{} if app.isCache { options = append(options, storage.DisableDigestResumption) } // configure deletion if d, ok := configuration.Storage["delete"]; ok { e, ok := d["enabled"] if ok { if deleteEnabled, ok := e.(bool); ok && deleteEnabled { options = append(options, storage.EnableDelete) } } } // configure redirects var redirectDisabled bool if redirectConfig, ok := configuration.Storage["redirect"]; ok { v := redirectConfig["disable"] switch v := v.(type) { case bool: redirectDisabled = v default: panic(fmt.Sprintf("invalid type for redirect config: %#v", redirectConfig)) } } if redirectDisabled { ctxu.GetLogger(app).Infof("backend redirection disabled") } else { options = append(options, storage.EnableRedirect) } // configure storage caches if cc, ok := configuration.Storage["cache"]; ok { v, ok := cc["blobdescriptor"] if !ok { // Backwards compatible: "layerinfo" == "blobdescriptor" v = cc["layerinfo"] } switch v { case "redis": if app.redis == nil { panic("redis configuration required to use for layerinfo cache") } cacheProvider := rediscache.NewRedisBlobDescriptorCacheProvider(app.redis) localOptions := append(options, storage.BlobDescriptorCacheProvider(cacheProvider)) app.registry, err = storage.NewRegistry(app, app.driver, localOptions...) if err != nil { panic("could not create registry: " + err.Error()) } ctxu.GetLogger(app).Infof("using redis blob descriptor cache") case "inmemory": cacheProvider := memorycache.NewInMemoryBlobDescriptorCacheProvider() localOptions := append(options, storage.BlobDescriptorCacheProvider(cacheProvider)) app.registry, err = storage.NewRegistry(app, app.driver, localOptions...) if err != nil { panic("could not create registry: " + err.Error()) } ctxu.GetLogger(app).Infof("using inmemory blob descriptor cache") default: if v != "" { ctxu.GetLogger(app).Warnf("unknown cache type %q, caching disabled", configuration.Storage["cache"]) } } } if app.registry == nil { // configure the registry if no cache section is available. app.registry, err = storage.NewRegistry(app.Context, app.driver, options...) if err != nil { panic("could not create registry: " + err.Error()) } } app.registry, err = applyRegistryMiddleware(app.Context, app.registry, configuration.Middleware["registry"]) if err != nil { panic(err) } authType := configuration.Auth.Type() if authType != "" { accessController, err := auth.GetAccessController(configuration.Auth.Type(), configuration.Auth.Parameters()) if err != nil { panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err)) } app.accessController = accessController ctxu.GetLogger(app).Debugf("configured %q access controller", authType) } // configure as a pull through cache if configuration.Proxy.RemoteURL != "" { app.registry, err = proxy.NewRegistryPullThroughCache(ctx, app.registry, app.driver, configuration.Proxy) if err != nil { panic(err.Error()) } app.isCache = true ctxu.GetLogger(app).Info("Registry configured as a proxy cache to ", configuration.Proxy.RemoteURL) } return app }
func main() { var ( issuer = &TokenIssuer{} pkFile string addr string debug bool err error passwdFile string realm string cert string certKey string ) flag.StringVar(&issuer.Issuer, "issuer", "distribution-token-server", "Issuer string for token") flag.StringVar(&pkFile, "key", "", "Private key file") flag.StringVar(&addr, "addr", "localhost:8080", "Address to listen on") flag.BoolVar(&debug, "debug", false, "Debug mode") flag.StringVar(&passwdFile, "passwd", ".htpasswd", "Passwd file") flag.StringVar(&realm, "realm", "", "Authentication realm") flag.StringVar(&cert, "tlscert", "", "Certificate file for TLS") flag.StringVar(&certKey, "tlskey", "", "Certificate key for TLS") flag.Parse() if debug { logrus.SetLevel(logrus.DebugLevel) } if pkFile == "" { issuer.SigningKey, err = libtrust.GenerateECP256PrivateKey() if err != nil { logrus.Fatalf("Error generating private key: %v", err) } logrus.Debugf("Using newly generated key with id %s", issuer.SigningKey.KeyID()) } else { issuer.SigningKey, err = libtrust.LoadKeyFile(pkFile) if err != nil { logrus.Fatalf("Error loading key file %s: %v", pkFile, err) } logrus.Debugf("Loaded private key with id %s", issuer.SigningKey.KeyID()) } if realm == "" { logrus.Fatalf("Must provide realm") } ac, err := auth.GetAccessController("htpasswd", map[string]interface{}{ "realm": realm, "path": passwdFile, }) if err != nil { logrus.Fatalf("Error initializing access controller: %v", err) } // TODO: Make configurable issuer.Expiration = 15 * time.Minute ctx := context.Background() ts := &tokenServer{ issuer: issuer, accessController: ac, refreshCache: map[string]refreshToken{}, } router := mux.NewRouter() router.Path("/token/").Methods("GET").Handler(handlerWithContext(ctx, ts.getToken)) router.Path("/token/").Methods("POST").Handler(handlerWithContext(ctx, ts.postToken)) if cert == "" { err = http.ListenAndServe(addr, router) } else if certKey == "" { logrus.Fatalf("Must provide certficate (-tlscert) and key (-tlskey)") } else { err = http.ListenAndServeTLS(addr, cert, certKey, router) } if err != nil { logrus.Infof("Error serving: %v", err) } }
// NewApp takes a configuration and returns a configured app, ready to serve // requests. The app only implements ServeHTTP and can be wrapped in other // handlers accordingly. func NewApp(ctx context.Context, configuration configuration.Configuration) *App { app := &App{ Config: configuration, Context: ctx, router: v2.RouterWithPrefix(configuration.HTTP.Prefix), isCache: configuration.Proxy.RemoteURL != "", } app.Context = ctxu.WithLogger(app.Context, ctxu.GetLogger(app, "instance.id")) // Register the handler dispatchers. app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler { return http.HandlerFunc(apiBase) }) app.register(v2.RouteNameManifest, imageManifestDispatcher) app.register(v2.RouteNameCatalog, catalogDispatcher) app.register(v2.RouteNameTags, tagsDispatcher) app.register(v2.RouteNameBlob, blobDispatcher) app.register(v2.RouteNameBlobUpload, blobUploadDispatcher) app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher) var err error app.driver, err = factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters()) if err != nil { // TODO(stevvooe): Move the creation of a service into a protected // method, where this is created lazily. Its status can be queried via // a health check. panic(err) } purgeConfig := uploadPurgeDefaultConfig() if mc, ok := configuration.Storage["maintenance"]; ok { for k, v := range mc { switch k { case "uploadpurging": purgeConfig = v.(map[interface{}]interface{}) } } } startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig) app.driver, err = applyStorageMiddleware(app.driver, configuration.Middleware["storage"]) if err != nil { panic(err) } app.configureSecret(&configuration) app.configureEvents(&configuration) app.configureRedis(&configuration) app.configureLogHook(&configuration) // configure deletion var deleteEnabled bool if d, ok := configuration.Storage["delete"]; ok { e, ok := d["enabled"] if ok { if deleteEnabled, ok = e.(bool); !ok { deleteEnabled = false } } } // configure redirects var redirectDisabled bool if redirectConfig, ok := configuration.Storage["redirect"]; ok { v := redirectConfig["disable"] switch v := v.(type) { case bool: redirectDisabled = v default: panic(fmt.Sprintf("invalid type for redirect config: %#v", redirectConfig)) } if redirectDisabled { ctxu.GetLogger(app).Infof("backend redirection disabled") } } // configure storage caches if cc, ok := configuration.Storage["cache"]; ok { v, ok := cc["blobdescriptor"] if !ok { // Backwards compatible: "layerinfo" == "blobdescriptor" v = cc["layerinfo"] } switch v { case "redis": if app.redis == nil { panic("redis configuration required to use for layerinfo cache") } app.registry = storage.NewRegistryWithDriver(app, app.driver, rediscache.NewRedisBlobDescriptorCacheProvider(app.redis), deleteEnabled, !redirectDisabled, app.isCache) ctxu.GetLogger(app).Infof("using redis blob descriptor cache") case "inmemory": app.registry = storage.NewRegistryWithDriver(app, app.driver, memorycache.NewInMemoryBlobDescriptorCacheProvider(), deleteEnabled, !redirectDisabled, app.isCache) ctxu.GetLogger(app).Infof("using inmemory blob descriptor cache") default: if v != "" { ctxu.GetLogger(app).Warnf("unknown cache type %q, caching disabled", configuration.Storage["cache"]) } } } if app.registry == nil { // configure the registry if no cache section is available. app.registry = storage.NewRegistryWithDriver(app.Context, app.driver, nil, deleteEnabled, !redirectDisabled, app.isCache) } app.registry, err = applyRegistryMiddleware(app.Context, app.registry, configuration.Middleware["registry"]) if err != nil { panic(err) } authType := configuration.Auth.Type() if authType != "" { accessController, err := auth.GetAccessController(configuration.Auth.Type(), configuration.Auth.Parameters()) if err != nil { panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err)) } app.accessController = accessController ctxu.GetLogger(app).Debugf("configured %q access controller", authType) } // configure as a pull through cache if configuration.Proxy.RemoteURL != "" { app.registry, err = proxy.NewRegistryPullThroughCache(ctx, app.registry, app.driver, configuration.Proxy) if err != nil { panic(err.Error()) } app.isCache = true ctxu.GetLogger(app).Info("Registry configured as a proxy cache to ", configuration.Proxy.RemoteURL) } return app }
// Run sets up and starts a TLS server that can be cancelled using the // given configuration. The context it is passed is the context it should // use directly for the TLS server, and generate children off for requests func Run(ctx context.Context, addr, tlsCertFile, tlsKeyFile string, trust signed.CryptoService, authMethod string, authOpts interface{}) error { tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return err } var lsnr net.Listener lsnr, err = net.ListenTCP("tcp", tcpAddr) if err != nil { return err } if tlsCertFile != "" && tlsKeyFile != "" { keypair, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile) if err != nil { return err } tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, PreferServerCipherSuites: true, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, Certificates: []tls.Certificate{keypair}, Rand: rand.Reader, } logrus.Info("Enabling TLS") lsnr = tls.NewListener(lsnr, tlsConfig) } else if tlsCertFile != "" || tlsKeyFile != "" { return fmt.Errorf("Partial TLS configuration found. Either include both a cert and key file in the configuration, or include neither to disable TLS.") } var ac auth.AccessController if authMethod == "token" { authOptions, ok := authOpts.(map[string]interface{}) if !ok { return fmt.Errorf("auth.options must be a map[string]interface{}") } ac, err = auth.GetAccessController(authMethod, authOptions) if err != nil { return err } } hand := utils.RootHandlerFactory(ac, ctx, trust) r := mux.NewRouter() r.Methods("GET").Path("/v2/").Handler(hand(handlers.MainHandler)) r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(hand(handlers.AtomicUpdateHandler, "push", "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.json").Handler(hand(handlers.GetTimestampHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.key").Handler(hand(handlers.GetTimestampKeyHandler, "push", "pull")) r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) r.Methods("GET", "POST", "PUT", "HEAD", "DELETE").Path("/{other:.*}").Handler(hand(utils.NotFoundHandler)) svr := http.Server{ Addr: addr, Handler: r, } logrus.Info("Starting on ", addr) err = svr.Serve(lsnr) return err }
// Run sets up and starts a TLS server that can be cancelled using the // given configuration. The context it is passed is the context it should // use directly for the TLS server, and generate children off for requests func Run(ctx context.Context, addr, tlsCertFile, tlsKeyFile string, trust signed.CryptoService, authMethod string, authOpts interface{}) error { tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return err } var lsnr net.Listener lsnr, err = net.ListenTCP("tcp", tcpAddr) if err != nil { return err } keypair, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile) if err != nil { // must be able to run without certs. In prod, users may // want load balancer to terminate TLS logrus.Errorf("[Notary Server] Error loading TLS keys %s", err) } else { tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, PreferServerCipherSuites: true, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, Certificates: []tls.Certificate{keypair}, Rand: rand.Reader, } lsnr = tls.NewListener(lsnr, tlsConfig) } var ac auth.AccessController if authMethod == "" { ac = nil } else { authOptions, ok := authOpts.(map[string]interface{}) if !ok { return fmt.Errorf("auth.options must be a map[string]interface{}") } ac, err = auth.GetAccessController(authMethod, authOptions) if err != nil { return err } } hand := utils.RootHandlerFactory(ac, ctx, trust) r := mux.NewRouter() r.Methods("GET").Path("/v2/").Handler(hand(handlers.MainHandler)) r.Methods("POST").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/").Handler(hand(handlers.AtomicUpdateHandler, "push", "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/{tufRole:(root|targets|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/timestamp.json").Handler(hand(handlers.GetTimestampHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/timestamp.key").Handler(hand(handlers.GetTimestampKeyHandler, "push", "pull")) r.Methods("DELETE").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) svr := http.Server{ Addr: addr, Handler: r, } logrus.Info("[Notary Server] : Starting on ", addr) err = svr.Serve(lsnr) return err }
// NewApp takes a configuration and returns a configured app, ready to serve // requests. The app only implements ServeHTTP and can be wrapped in other // handlers accordingly. func NewApp(ctx context.Context, configuration configuration.Configuration) *App { app := &App{ Config: configuration, Context: ctx, router: v2.RouterWithPrefix(configuration.HTTP.Prefix), } app.Context = ctxu.WithLogger(app.Context, ctxu.GetLogger(app, "instance.id")) // Register the handler dispatchers. app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler { return http.HandlerFunc(apiBase) }) app.register(v2.RouteNameManifest, imageManifestDispatcher) app.register(v2.RouteNameTags, tagsDispatcher) app.register(v2.RouteNameBlob, layerDispatcher) app.register(v2.RouteNameBlobUpload, layerUploadDispatcher) app.register(v2.RouteNameBlobUploadChunk, layerUploadDispatcher) var err error app.driver, err = factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters()) if err != nil { // TODO(stevvooe): Move the creation of a service into a protected // method, where this is created lazily. Its status can be queried via // a health check. panic(err) } startUploadPurger(app.driver, ctxu.GetLogger(app)) app.driver, err = applyStorageMiddleware(app.driver, configuration.Middleware["storage"]) if err != nil { panic(err) } app.configureEvents(&configuration) app.configureRedis(&configuration) // configure storage caches if cc, ok := configuration.Storage["cache"]; ok { switch cc["layerinfo"] { case "redis": if app.redis == nil { panic("redis configuration required to use for layerinfo cache") } app.registry = storage.NewRegistryWithDriver(app.driver, cache.NewRedisLayerInfoCache(app.redis)) ctxu.GetLogger(app).Infof("using redis layerinfo cache") case "inmemory": app.registry = storage.NewRegistryWithDriver(app.driver, cache.NewInMemoryLayerInfoCache()) ctxu.GetLogger(app).Infof("using inmemory layerinfo cache") default: if cc["layerinfo"] != "" { ctxu.GetLogger(app).Warnf("unkown cache type %q, caching disabled", configuration.Storage["cache"]) } } } if app.registry == nil { // configure the registry if no cache section is available. app.registry = storage.NewRegistryWithDriver(app.driver, nil) } app.registry, err = applyRegistryMiddleware(app.registry, configuration.Middleware["registry"]) if err != nil { panic(err) } authType := configuration.Auth.Type() if authType != "" { accessController, err := auth.GetAccessController(configuration.Auth.Type(), configuration.Auth.Parameters()) if err != nil { panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err)) } app.accessController = accessController } return app }