package main import ( "crypto/tls" "fmt" "strconv" "time" "github.com/Sirupsen/logrus" "github.com/dancannon/gorethink" _ "github.com/docker/distribution/registry/auth/htpasswd" _ "github.com/docker/distribution/registry/auth/token" "github.com/docker/go-connections/tlsconfig" "github.com/docker/notary" "github.com/docker/notary/server" "github.com/docker/notary/server/storage" "github.com/docker/notary/signer/client" "github.com/docker/notary/storage/rethinkdb" "github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/signed" "github.com/docker/notary/utils" _ "github.com/go-sql-driver/mysql" "github.com/spf13/viper" "golang.org/x/net/context" ) // get the address for the HTTP server, and parses the optional TLS // configuration for the server - if no TLS configuration is specified, // TLS is not enabled. func getAddrAndTLSConfig(configuration *viper.Viper) (string, *tls.Config, error) { httpAddr := configuration.GetString("server.http_addr") if httpAddr == "" { return "", nil, fmt.Errorf("http listen address required for server") } tlsConfig, err := utils.ParseServerTLS(configuration, false) if err != nil { return "", nil, fmt.Errorf(err.Error()) } return httpAddr, tlsConfig, nil } // sets up TLS for the GRPC connection to notary-signer func grpcTLS(configuration *viper.Viper) (*tls.Config, error) { rootCA := utils.GetPathRelativeToConfig(configuration, "trust_service.tls_ca_file") clientCert := utils.GetPathRelativeToConfig(configuration, "trust_service.tls_client_cert") clientKey := utils.GetPathRelativeToConfig(configuration, "trust_service.tls_client_key") if clientCert == "" && clientKey != "" || clientCert != "" && clientKey == "" { return nil, fmt.Errorf("either pass both client key and cert, or neither") } tlsConfig, err := tlsconfig.Client(tlsconfig.Options{ CAFile: rootCA, CertFile: clientCert, KeyFile: clientKey, }) if err != nil { return nil, fmt.Errorf( "Unable to configure TLS to the trust service: %s", err.Error()) } return tlsConfig, nil } // parses the configuration and returns a backing store for the TUF files func getStore(configuration *viper.Viper, hRegister healthRegister) ( storage.MetaStore, error) { var store storage.MetaStore backend := configuration.GetString("storage.backend") logrus.Infof("Using %s backend", backend) switch backend { case notary.MemoryBackend: return storage.NewMemStorage(), nil case notary.MySQLBackend, notary.SQLiteBackend: storeConfig, err := utils.ParseSQLStorage(configuration) if err != nil { return nil, err } s, err := storage.NewSQLStorage(storeConfig.Backend, storeConfig.Source) if err != nil { return nil, fmt.Errorf("Error starting %s driver: %s", backend, err.Error()) } store = *storage.NewTUFMetaStorage(s) hRegister("DB operational", s.CheckHealth, time.Minute) case notary.RethinkDBBackend: var sess *gorethink.Session storeConfig, err := utils.ParseRethinkDBStorage(configuration) if err != nil { return nil, err } tlsOpts := tlsconfig.Options{ CAFile: storeConfig.CA, CertFile: storeConfig.Cert, KeyFile: storeConfig.Key, } sess, err = rethinkdb.Connection(tlsOpts, storeConfig.Source) if err != nil { return nil, fmt.Errorf("Error starting %s driver: %s", backend, err.Error()) } s := storage.NewRethinkDBStorage(storeConfig.DBName, sess) store = *storage.NewTUFMetaStorage(s) hRegister("DB operational", s.CheckHealth, time.Minute) default: return nil, fmt.Errorf("%s is not a supported storage backend", backend) } return store, nil } type signerFactory func(hostname, port string, tlsConfig *tls.Config) *client.NotarySigner type healthRegister func(name string, checkFunc func() error, duration time.Duration) // parses the configuration and determines which trust service and key algorithm // to return func getTrustService(configuration *viper.Viper, sFactory signerFactory, hRegister healthRegister) (signed.CryptoService, string, error) { switch configuration.GetString("trust_service.type") { case "local": logrus.Info("Using local signing service, which requires ED25519. " + "Ignoring all other trust_service parameters, including keyAlgorithm") return signed.NewEd25519(), data.ED25519Key, nil case "remote": default: return nil, "", fmt.Errorf( "must specify either a \"local\" or \"remote\" type for trust_service") } keyAlgo := configuration.GetString("trust_service.key_algorithm") if keyAlgo != data.ED25519Key && keyAlgo != data.ECDSAKey && keyAlgo != data.RSAKey { return nil, "", fmt.Errorf("invalid key algorithm configured: %s", keyAlgo) } clientTLS, err := grpcTLS(configuration) if err != nil { return nil, "", err } logrus.Info("Using remote signing service") notarySigner := sFactory( configuration.GetString("trust_service.hostname"), configuration.GetString("trust_service.port"), clientTLS, ) minute := 1 * time.Minute hRegister( "Trust operational", // If the trust service fails, the server is degraded but not // exactly unhealthy, so always return healthy and just log an // error. func() error { err := notarySigner.CheckHealth(minute) if err != nil { logrus.Error("Trust not fully operational: ", err.Error()) } return nil }, minute) return notarySigner, keyAlgo, nil } // Parse the cache configurations for GET-ting current and checksummed metadata, // returning the configuration for current (non-content-addressed) metadata // first, then the configuration for consistent (content-addressed) metadata // second. The configuration consists mainly of the max-age (an integer in seconds, // just like in the Cache-Control header) for each type of metadata. // The max-age must be between 0 and 31536000 (one year in seconds, which is // the recommended maximum time data is cached), else parsing will return an error. // A max-age of 0 will disable caching for that type of download (consistent or current). func getCacheConfig(configuration *viper.Viper) (current, consistent utils.CacheControlConfig, err error) { cccs := make(map[string]utils.CacheControlConfig) currentOpt, consistentOpt := "current_metadata", "consistent_metadata" defaults := map[string]int{ currentOpt: int(notary.CurrentMetadataCacheMaxAge.Seconds()), consistentOpt: int(notary.ConsistentMetadataCacheMaxAge.Seconds()), } maxMaxAge := int(notary.CacheMaxAgeLimit.Seconds()) for optionName, seconds := range defaults { m := configuration.GetString(fmt.Sprintf("caching.max_age.%s", optionName)) if m != "" { seconds, err = strconv.Atoi(m) if err != nil || seconds < 0 || seconds > maxMaxAge { return nil, nil, fmt.Errorf( "must specify a cache-control max-age between 0 and %v", maxMaxAge) } } cccs[optionName] = utils.NewCacheControlConfig(seconds, optionName == currentOpt) } current = cccs[currentOpt] consistent = cccs[consistentOpt] return } func parseServerConfig(configFilePath string, hRegister healthRegister) (context.Context, server.Config, error) { config := viper.New() utils.SetupViper(config, envPrefix) // parse viper config if err := utils.ParseViper(config, configFilePath); err != nil { return nil, server.Config{}, err } ctx := context.Background() // default is error level lvl, err := utils.ParseLogLevel(config, logrus.ErrorLevel) if err != nil { return nil, server.Config{}, err } logrus.SetLevel(lvl) // parse bugsnag config bugsnagConf, err := utils.ParseBugsnag(config) if err != nil { return ctx, server.Config{}, err } utils.SetUpBugsnag(bugsnagConf) trust, keyAlgo, err := getTrustService(config, client.NewNotarySigner, hRegister) if err != nil { return nil, server.Config{}, err } ctx = context.WithValue(ctx, "keyAlgorithm", keyAlgo) store, err := getStore(config, hRegister) if err != nil { return nil, server.Config{}, err } ctx = context.WithValue(ctx, "metaStore", store) currentCache, consistentCache, err := getCacheConfig(config) if err != nil { return nil, server.Config{}, err } httpAddr, tlsConfig, err := getAddrAndTLSConfig(config) if err != nil { return nil, server.Config{}, err } return ctx, server.Config{ Addr: httpAddr, TLSConfig: tlsConfig, Trust: trust, AuthMethod: config.GetString("auth.type"), AuthOpts: config.Get("auth.options"), CurrentCacheControlConfig: currentCache, ConsistentCacheControlConfig: consistentCache, }, nil }