Repository: docker/notary
Pull request #304 (2015-11-20 05:11:47)

Make the server/signer configurations more similar
This factors out some config file parsing utilities common to server and signer.  This makes the config files look a lot more similar, but it also adds a few changes:

1.  Both server/signer server TLS have the following parameters now:
    - `tls_cert_file` (signer was previously `cert_file`)
    - `tls_key_file` (signer was previously `key_file`)

2. Both the server/signer have the following http parameters now:
   - `http_addr` (server was previously `addr`)

3. Both the server/signer have the following logging parameters now:
   - `debug` as a string value (signer was previously an int)

4. Both server/signer now support Bugsnag reporting (previously the signer did not)

5. The default alias for the signer DB store are also available as config file parameters under "storage" now.  The password remains the same, because I'm not sure how best to deal with that.

6. The tls files are now parsed as paths relative to the config file, if they aren't absolute paths or empty.

7. Both the server and signer have mandatory storage sections now, and both also now support a memory backend (previously, server's storage section was optional, and signer's storage section only supported mysql)

Apologies for the massive change.  Tested this with `docker-compose up` and initting/adding/publishing/listing a repo (but not the bugsnag part or alias change).

Fixes #304.


Patch:
diff --git a/Dockerfile.server b/Dockerfile.server
index be430f77..a2273bc5 100644
--- a/Dockerfile.server
+++ b/Dockerfile.server
@@ -20,4 +20,4 @@ RUN go install \
     ${NOTARYPKG}/cmd/notary-server
 
 ENTRYPOINT [ "notary-server" ]
-CMD [ "-config", "cmd/notary-server/config.json" ]
+CMD [ "-config=fixtures/server-config-local.json" ]
diff --git a/Dockerfile.signer b/Dockerfile.signer
index e4400bdb..81ce6472 100644
--- a/Dockerfile.signer
+++ b/Dockerfile.signer
@@ -38,4 +38,4 @@ RUN go install \
 
 
 ENTRYPOINT [ "notary-signer" ]
-CMD [ "-config=cmd/notary-signer/config.json" ]
+CMD [ "-config=fixtures/signer-config-local.json" ]
diff --git a/cmd/notary-server/main.go b/cmd/notary-server/main.go
index 729e1b19..34f81313 100644
--- a/cmd/notary-server/main.go
+++ b/cmd/notary-server/main.go
@@ -13,18 +13,16 @@ import (
 	"time"
 
 	"github.com/Sirupsen/logrus"
-	"github.com/bugsnag/bugsnag-go"
 	"github.com/docker/distribution/health"
 	_ "github.com/docker/distribution/registry/auth/htpasswd"
 	_ "github.com/docker/distribution/registry/auth/token"
+	"github.com/docker/notary/server/storage"
 	"github.com/docker/notary/signer/client"
 	"github.com/docker/notary/tuf/signed"
 	_ "github.com/go-sql-driver/mysql"
 	"golang.org/x/net/context"
 
-	bugsnag_hook "github.com/Sirupsen/logrus/hooks/bugsnag"
 	"github.com/docker/notary/server"
-	"github.com/docker/notary/server/storage"
 	"github.com/docker/notary/utils"
 	"github.com/docker/notary/version"
 	"github.com/spf13/viper"
@@ -36,46 +34,49 @@ const DebugAddress = "localhost:8080"
 var (
 	debug      bool
 	configFile string
+	envPrefix  = "NOTARY_SERVER"
 	mainViper  = viper.New()
 )
 
 func init() {
-	// set default log level to Error
-	mainViper.SetDefault("logging", map[string]interface{}{"level": 2})
-
+	utils.SetupViper(mainViper, envPrefix)
 	// Setup flags
 	flag.StringVar(&configFile, "config", "", "Path to configuration file")
 	flag.BoolVar(&debug, "debug", false, "Enable the debugging server on localhost:8080")
 }
 
-// optionally sets up TLS for the server - if no TLS configuration is
-// specified, TLS is not enabled.
-func serverTLS(configuration *viper.Viper) (*tls.Config, error) {
-	tlsCertFile := configuration.GetString("server.tls_cert_file")
-	tlsKeyFile := configuration.GetString("server.tls_key_file")
-
-	if tlsCertFile == "" && tlsKeyFile == "" {
-		return nil, nil
-	} else if tlsCertFile == "" || tlsKeyFile == "" {
-		return nil, fmt.Errorf("Partial TLS configuration found. Either include both a cert and key file in the configuration, or include neither to disable TLS.")
+// 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.ConfigureServerTLS(&utils.ServerTLSOpts{
-		ServerCertFile: tlsCertFile,
-		ServerKeyFile:  tlsKeyFile,
-	})
+	tlsOpts, err := utils.ParseServerTLS(configuration, false)
 	if err != nil {
-		return nil, fmt.Errorf("Unable to set up TLS: %s", err.Error())
+		return "", nil, fmt.Errorf(err.Error())
 	}
-	return tlsConfig, nil
+	// do not support this yet since the client doesn't have client cert support
+	if tlsOpts != nil {
+		tlsOpts.ClientCAFile = ""
+		tlsConfig, err := utils.ConfigureServerTLS(tlsOpts)
+		if err != nil {
+			return "", nil, fmt.Errorf(
+				"unable to set up TLS for server: %s", err.Error())
+		}
+		return httpAddr, tlsConfig, nil
+	}
+	return httpAddr, nil, nil
 }
 
 // sets up TLS for the GRPC connection to notary-signer
 func grpcTLS(configuration *viper.Viper) (*tls.Config, error) {
-	rootCA := configuration.GetString("trust_service.tls_ca_file")
+	rootCA := utils.GetPathRelativeToConfig(configuration, "trust_service.tls_ca_file")
 	serverName := configuration.GetString("trust_service.hostname")
-	clientCert := configuration.GetString("trust_service.tls_client_cert")
-	clientKey := configuration.GetString("trust_service.tls_client_key")
+	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("Partial TLS configuration found. Either include both a client cert and client key file in the configuration, or include neither.")
@@ -94,6 +95,28 @@ func grpcTLS(configuration *viper.Viper) (*tls.Config, error) {
 	return tlsConfig, nil
 }
 
+func getStore(configuration *viper.Viper, allowedBackends []string) (
+	storage.MetaStore, error) {
+
+	storeConfig, err := utils.ParseStorage(configuration, allowedBackends)
+	if err != nil {
+		return nil, err
+	}
+	logrus.Infof("Using %s backend", storeConfig.Backend)
+
+	if storeConfig.Backend == utils.MemoryBackend {
+		return storage.NewMemStorage(), nil
+	}
+
+	store, err := storage.NewSQLStorage(storeConfig.Backend, storeConfig.Source)
+	if err != nil {
+		return nil, fmt.Errorf("Error starting DB driver: ", err.Error())
+	}
+	health.RegisterPeriodicFunc(
+		"DB operational", store.CheckHealth, time.Second*60)
+	return store, nil
+}
+
 func main() {
 	flag.Usage = usage
 	flag.Parse()
@@ -115,40 +138,27 @@ func main() {
 	mainViper.SetConfigName(strings.TrimSuffix(filename, ext))
 	mainViper.AddConfigPath(configPath)
 
-	// Automatically accept configuration options from the environment
-	mainViper.SetEnvPrefix("NOTARY_SERVER")
-	mainViper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
-	mainViper.AutomaticEnv()
-
 	err := mainViper.ReadInConfig()
 	if err != nil {
 		logrus.Error("Viper Error: ", err.Error())
 		logrus.Error("Could not read config at ", configFile)
 		os.Exit(1)
 	}
-	lvl, err := logrus.ParseLevel(mainViper.GetString("logging.level"))
+
+	// default is error level
+	lvl, err := utils.ParseLogLevel(mainViper, logrus.ErrorLevel)
 	if err != nil {
-		lvl = logrus.ErrorLevel
-		logrus.Error("Could not parse log level from config. Defaulting to ErrorLevel")
+		logrus.Fatal(err.Error())
 	}
 	logrus.SetLevel(lvl)
 
-	// set up bugsnag and attach to logrus
-	bugs := mainViper.GetString("reporting.bugsnag")
-	if bugs != "" {
-		apiKey := mainViper.GetString("reporting.bugsnag_api_key")
-		releaseStage := mainViper.GetString("reporting.bugsnag_release_stage")
-		bugsnag.Configure(bugsnag.Configuration{
-			APIKey:       apiKey,
-			ReleaseStage: releaseStage,
-		})
-		hook, err := bugsnag_hook.NewBugsnagHook()
-		if err != nil {
-			logrus.Error("Could not attach bugsnag to logrus: ", err.Error())
-		} else {
-			logrus.AddHook(hook)
-		}
+	// parse bugsnag config
+	bugsnagConf, err := utils.ParseBugsnag(mainViper)
+	if err != nil {
+		logrus.Fatal(err.Error())
 	}
+	utils.SetUpBugsnag(bugsnagConf)
+
 	keyAlgo := mainViper.GetString("trust_service.key_algorithm")
 	if keyAlgo == "" {
 		logrus.Fatal("no key algorithm configured.")
@@ -188,23 +198,13 @@ func main() {
 		trust = signed.NewEd25519()
 	}
 
-	if mainViper.GetString("storage.backend") == "mysql" {
-		logrus.Info("Using mysql backend")
-		dbURL := mainViper.GetString("storage.db_url")
-		store, err := storage.NewSQLStorage("mysql", dbURL)
-		if err != nil {
-			logrus.Fatal("Error starting DB driver: ", err.Error())
-			return // not strictly needed but let's be explicit
-		}
-		health.RegisterPeriodicFunc(
-			"DB operational", store.CheckHealth, time.Second*60)
-		ctx = context.WithValue(ctx, "metaStore", store)
-	} else {
-		logrus.Debug("Using memory backend")
-		ctx = context.WithValue(ctx, "metaStore", storage.NewMemStorage())
+	store, err := getStore(mainViper, []string{utils.MySQLBackend, utils.MemoryBackend})
+	if err != nil {
+		logrus.Fatal(err.Error())
 	}
+	ctx = context.WithValue(ctx, "metaStore", store)
 
-	tlsConfig, err := serverTLS(mainViper)
+	httpAddr, tlsConfig, err := getAddrAndTLSConfig(mainViper)
 	if err != nil {
 		logrus.Fatal(err.Error())
 	}
@@ -212,7 +212,7 @@ func main() {
 	logrus.Info("Starting Server")
 	err = server.Run(
 		ctx,
-		mainViper.GetString("server.addr"),
+		httpAddr,
 		tlsConfig,
 		trust,
 		mainViper.GetString("auth.type"),
diff --git a/cmd/notary-signer/config.json b/cmd/notary-signer/config.json
deleted file mode 100644
index 70a44fa3..00000000
--- a/cmd/notary-signer/config.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-	"server": {
-		"http_addr": ":4444",
-		"grpc_addr": ":7899",
-		"cert_file": "./fixtures/notary-signer.crt",
-		"key_file": "./fixtures/notary-signer.key",
-		"client_ca_file": "./fixtures/notary-server.crt"
-	},
-	"crypto": {
-		"pkcslib": "/usr/local/lib/softhsm/libsofthsm2.so"
-	},
-	"logging": {
-		"level": 5
-	},
-	"storage": {
-		"backend": "mysql",
-		"db_url": "root@tcp(notarymysql:3306)/notary"
-	}
-}
diff --git a/cmd/notary-signer/main.go b/cmd/notary-signer/main.go
index fdaa91e8..092dedd0 100644
--- a/cmd/notary-signer/main.go
+++ b/cmd/notary-signer/main.go
@@ -22,14 +22,16 @@ import (
 
 	"github.com/docker/distribution/health"
 	"github.com/docker/notary/cryptoservice"
+	"github.com/docker/notary/passphrase"
 	"github.com/docker/notary/signer"
 	"github.com/docker/notary/signer/api"
 	"github.com/docker/notary/signer/keydbstore"
+	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/utils"
 	"github.com/docker/notary/version"
 	_ "github.com/go-sql-driver/mysql"
-	"github.com/miekg/pkcs11"
+	_ "github.com/mattn/go-sqlite3"
 	"github.com/spf13/viper"
 
 	"github.com/Sirupsen/logrus"
@@ -38,10 +40,8 @@ import (
 
 const (
 	debugAddr       = "localhost:8080"
-	dbType          = "mysql"
 	envPrefix       = "NOTARY_SIGNER"
 	defaultAliasEnv = "DEFAULT_ALIAS"
-	pinCode         = "PIN"
 )
 
 var (
@@ -51,13 +51,7 @@ var (
 )
 
 func init() {
-	// set default log level to Error
-	mainViper.SetDefault("logging", map[string]interface{}{"level": 2})
-
-	mainViper.SetEnvPrefix(envPrefix)
-	mainViper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
-	mainViper.AutomaticEnv()
-
+	utils.SetupViper(mainViper, envPrefix)
 	// Setup flags
 	flag.StringVar(&configFile, "config", "", "Path to configuration file")
 	flag.BoolVar(&debug, "debug", false, "show the version and exit")
@@ -73,28 +67,114 @@ func passphraseRetriever(keyName, alias string, createNew bool, attempts int) (p
 	return passphrase, false, nil
 }
 
-// parses and sets up the TLS for the signer http + grpc server
-func signerTLS(configuration *viper.Viper, printUsage bool) (*tls.Config, error) {
-	certFile := configuration.GetString("server.cert_file")
-	keyFile := configuration.GetString("server.key_file")
-	if certFile == "" || keyFile == "" {
-		if printUsage {
-			usage()
+// Reads the configuration file for storage setup, and sets up the cryptoservice
+// mapping
+func setUpCryptoservices(configuration *viper.Viper, allowedBackends []string) (
+	signer.CryptoServiceIndex, error) {
+
+	storeConfig, err := utils.ParseStorage(configuration, allowedBackends)
+	if err != nil {
+		return nil, err
+	}
+
+	var keyStore trustmanager.KeyStore
+	if storeConfig.Backend == utils.MemoryBackend {
+		keyStore = trustmanager.NewKeyMemoryStore(
+			passphrase.ConstantRetriever("memory-db-ignore"))
+	} else {
+		defaultAlias := configuration.GetString("storage.default_alias")
+		if defaultAlias == "" {
+			// backwards compatibility - support this environment variable
+			defaultAlias = configuration.GetString(defaultAliasEnv)
 		}
-		return nil, fmt.Errorf("Certificate and key are mandatory")
+
+		if defaultAlias == "" {
+			return nil, fmt.Errorf("must provide a default alias for the key DB")
+		}
+		logrus.Debug("Default Alias: ", defaultAlias)
+
+		dbSQL, err := sql.Open(storeConfig.Backend, storeConfig.Source)
+		if err != nil {
+			return nil, fmt.Errorf("failed to open the %s database: %s, %v",
+				storeConfig.Backend, storeConfig.Source, err)
+		}
+		logrus.Debugf("Using %s DB: %s", storeConfig.Backend, storeConfig.Source)
+
+		dbStore, err := keydbstore.NewKeyDBStore(
+			passphraseRetriever, defaultAlias, storeConfig.Backend, dbSQL)
+		if err != nil {
+			return nil, fmt.Errorf("failed to create a new keydbstore: %v", err)
+		}
+
+		health.RegisterPeriodicFunc(
+			"DB operational", dbStore.HealthCheck, time.Second*60)
+		keyStore = dbStore
 	}
 
-	clientCAFile := configuration.GetString("server.client_ca_file")
-	tlsConfig, err := utils.ConfigureServerTLS(&utils.ServerTLSOpts{
-		ServerCertFile:    certFile,
-		ServerKeyFile:     keyFile,
-		RequireClientAuth: clientCAFile != "",
-		ClientCAFile:      clientCAFile,
-	})
+	cryptoService := cryptoservice.NewCryptoService("", keyStore)
+	cryptoServices := make(signer.CryptoServiceIndex)
+	cryptoServices[data.ED25519Key] = cryptoService
+	cryptoServices[data.ECDSAKey] = cryptoService
+	return cryptoServices, nil
+}
+
+// set up the GRPC server
+func setupGRPCServer(grpcAddr string, tlsConfig *tls.Config,
+	cryptoServices signer.CryptoServiceIndex) (*grpc.Server, net.Listener, error) {
+
+	//RPC server setup
+	kms := &api.KeyManagementServer{CryptoServices: cryptoServices,
+		HealthChecker: health.CheckStatus}
+	ss := &api.SignerServer{CryptoServices: cryptoServices,
+		HealthChecker: health.CheckStatus}
+
+	lis, err := net.Listen("tcp", grpcAddr)
 	if err != nil {
-		return nil, fmt.Errorf("Unable to set up TLS: %s", err.Error())
+		return nil, nil, fmt.Errorf("grpc server failed to listen on %s: %v",
+			grpcAddr, err)
+	}
+
+	creds := credentials.NewTLS(tlsConfig)
+	opts := []grpc.ServerOption{grpc.Creds(creds)}
+	grpcServer := grpc.NewServer(opts...)
+
+	pb.RegisterKeyManagementServer(grpcServer, kms)
+	pb.RegisterSignerServer(grpcServer, ss)
+
+	return grpcServer, lis, nil
+}
+
+func setupHTTPServer(httpAddr string, tlsConfig *tls.Config,
+	cryptoServices signer.CryptoServiceIndex) http.Server {
+
+	return http.Server{
+		Addr:      httpAddr,
+		Handler:   api.Handlers(cryptoServices),
+		TLSConfig: tlsConfig,
 	}
-	return tlsConfig, nil
+}
+
+func getAddrAndTLSConfig(configuration *viper.Viper) (string, string, *tls.Config, error) {
+	tlsOpts, err := utils.ParseServerTLS(configuration, true)
+	if err != nil {
+		return "", "", nil, fmt.Errorf("unable to set up TLS: %s", err.Error())
+	}
+	tlsConfig, err := utils.ConfigureServerTLS(tlsOpts)
+	if err != nil {
+		return "", "", nil, fmt.Errorf("unable to set up TLS: %s", err.Error())
+	}
+
+	grpcAddr := configuration.GetString("server.grpc_addr")
+	if grpcAddr == "" {
+		return "", "", nil, fmt.Errorf("grpc listen address required for server")
+	}
+
+	httpAddr := configuration.GetString("server.http_addr")
+	if httpAddr == "" {
+		return "", "", nil, fmt.Errorf("http listen address required for server")
+	}
+
+	return httpAddr, grpcAddr, tlsConfig, nil
 }
 
 func main() {
@@ -122,78 +202,47 @@ func main() {
 		os.Exit(1)
 	}
 
-	logrus.SetLevel(logrus.Level(mainViper.GetInt("logging.level")))
-
-	tlsConfig, err := signerTLS(mainViper, true)
+	// default is error level
+	lvl, err := utils.ParseLogLevel(mainViper, logrus.ErrorLevel)
 	if err != nil {
-		logrus.Fatalf(err.Error())
+		logrus.Fatal(err.Error())
 	}
+	logrus.SetLevel(lvl)
 
-	cryptoServices := make(signer.CryptoServiceIndex)
-
-	configDBType := strings.ToLower(mainViper.GetString("storage.backend"))
-	dbURL := mainViper.GetString("storage.db_url")
-	if configDBType != dbType || dbURL == "" {
-		usage()
-		log.Fatalf("Currently only a MySQL database backend is supported.")
-	}
-	dbSQL, err := sql.Open(configDBType, dbURL)
+	// parse bugsnag config
+	bugsnagConf, err := utils.ParseBugsnag(mainViper)
 	if err != nil {
-		log.Fatalf("failed to open the database: %s, %v", dbURL, err)
+		logrus.Fatal(err.Error())
 	}
+	utils.SetUpBugsnag(bugsnagConf)
 
-	defaultAlias := mainViper.GetString(defaultAliasEnv)
-	logrus.Debug("Default Alias: ", defaultAlias)
-	keyStore, err := keydbstore.NewKeyDBStore(passphraseRetriever, defaultAlias, configDBType, dbSQL)
+	// parse server config
+	httpAddr, grpcAddr, tlsConfig, err := getAddrAndTLSConfig(mainViper)
 	if err != nil {
-		log.Fatalf("failed to create a new keydbstore: %v", err)
+		logrus.Fatal(err.Error())
 	}
 
-	health.RegisterPeriodicFunc(
-		"DB operational", keyStore.HealthCheck, time.Second*60)
-
-	cryptoService := cryptoservice.NewCryptoService("", keyStore)
-
-	cryptoServices[data.ED25519Key] = cryptoService
-	cryptoServices[data.ECDSAKey] = cryptoService
-
-	//RPC server setup
-	kms := &api.KeyManagementServer{CryptoServices: cryptoServices,
-		HealthChecker: health.CheckStatus}
-	ss := &api.SignerServer{CryptoServices: cryptoServices,
-		HealthChecker: health.CheckStatus}
-
-	rpcAddr := mainViper.GetString("server.grpc_addr")
-	lis, err := net.Listen("tcp", rpcAddr)
+	// setup the cryptoservices
+	cryptoServices, err := setUpCryptoservices(mainViper,
+		[]string{utils.MySQLBackend, utils.MemoryBackend})
 	if err != nil {
-		log.Fatalf("failed to listen %v", err)
+		logrus.Fatal(err.Error())
 	}
-	creds := credentials.NewTLS(tlsConfig)
-	opts := []grpc.ServerOption{grpc.Creds(creds)}
-	grpcServer := grpc.NewServer(opts...)
 
-	pb.RegisterKeyManagementServer(grpcServer, kms)
-	pb.RegisterSignerServer(grpcServer, ss)
-
-	go grpcServer.Serve(lis)
-
-	httpAddr := mainViper.GetString("server.http_addr")
-	if httpAddr == "" {
-		log.Fatalf("Server address is required")
-	}
-	//HTTP server setup
-	server := http.Server{
-		Addr:      httpAddr,
-		Handler:   api.Handlers(cryptoServices),
-		TLSConfig: tlsConfig,
+	grpcServer, lis, err := setupGRPCServer(grpcAddr, tlsConfig, cryptoServices)
+	if err != nil {
+		logrus.Fatal(err.Error())
 	}
 
+	httpServer := setupHTTPServer(httpAddr, tlsConfig, cryptoServices)
+
 	if debug {
-		log.Println("RPC server listening on", rpcAddr)
+		log.Println("RPC server listening on", grpcAddr)
 		log.Println("HTTP server listening on", httpAddr)
 	}
 
-	err = server.ListenAndServeTLS("", "")
+	go grpcServer.Serve(lis)
+	err = httpServer.ListenAndServeTLS("", "")
 	if err != nil {
 		log.Fatal("HTTPS server failed to start:", err)
 	}
@@ -213,45 +262,3 @@ func debugServer(addr string) {
 		log.Fatalf("error listening on debug interface: %v", err)
 	}
 }
-
-// SetupHSMEnv is a method that depends on the existences
-func SetupHSMEnv(libraryPath, pin string) (*pkcs11.Ctx, pkcs11.SessionHandle) {
-	p := pkcs11.New(libraryPath)
-
-	if p == nil {
-		log.Fatalf("Failed to init library")
-	}
-
-	if err := p.Initialize(); err != nil {
-		log.Fatalf("Initialize error %s\n", err.Error())
-	}
-
-	slots, err := p.GetSlotList(true)
-	if err != nil {
-		log.Fatalf("Failed to list HSM slots %s", err)
-	}
-	// Check to see if we got any slots from the HSM.
-	if len(slots) < 1 {
-		log.Fatalln("No HSM Slots found")
-	}
-
-	// CKF_SERIAL_SESSION: TRUE if cryptographic functions are performed in serial with the application; FALSE if the functions may be performed in parallel with the application.
-	// CKF_RW_SESSION: TRUE if the session is read/write; FALSE if the session is read-only
-	session, err := p.OpenSession(slots[0], pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
-	if err != nil {
-		log.Fatalf("Failed to Start Session with HSM %s", err)
-	}
-
-	if err = p.Login(session, pkcs11.CKU_USER, pin); err != nil {
-		log.Fatalf("User PIN %s\n", err.Error())
-	}
-
-	return p, session
-}
-
-func cleanup(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) {
-	ctx.Destroy()
-	ctx.Finalize()
-	ctx.CloseSession(session)
-	ctx.Logout(session)
-}
diff --git a/docker-compose.yml b/docker-compose.yml
index 00e94712..e774a3a9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,7 +8,8 @@ notaryserver:
    - "8080"
    - "4443:4443"
   environment:
-    SERVICE_NAME: notary
+   - SERVICE_NAME=notary
+  command: -config=fixtures/server-config.json
 notarysigner:
   volumes:
    - /dev/bus/usb/003/010:/dev/bus/usb/002/010
@@ -17,6 +18,7 @@ notarysigner:
   dockerfile: Dockerfile.signer
   links:
    - notarymysql
+  command: -config=fixtures/signer-config.json
 notarymysql:
   build: ./notarymysql/
   ports:
diff --git a/fixtures/server-config-local.json b/fixtures/server-config-local.json
new file mode 100644
index 00000000..410d1cc8
--- /dev/null
+++ b/fixtures/server-config-local.json
@@ -0,0 +1,22 @@
+{
+	"server": {
+		"http_addr": ":4443",
+		"tls_key_file": "./notary-server.key",
+		"tls_cert_file": "./notary-server.crt"
+	},
+	"trust_service": {
+	  "type": "remote",
+	  "hostname": "notarysigner",
+	  "port": "7899",
+	  "tls_ca_file": "./root-ca.crt",
+	  "key_algorithm": "ecdsa",
+	  "tls_client_cert": "./notary-server.crt",
+	  "tls_client_key": "./notary-server.key"
+	},
+	"logging": {
+		"level": "debug"
+	},
+	"storage": {
+		"backend": "memory"
+	}
+}
diff --git a/cmd/notary-server/config.json b/fixtures/server-config.json
similarity index 50%
rename from cmd/notary-server/config.json
rename to fixtures/server-config.json
index a99f15a4..5a1b56cd 100644
--- a/cmd/notary-server/config.json
+++ b/fixtures/server-config.json
@@ -1,17 +1,17 @@
 {
 	"server": {
-		"addr": ":4443",
-		"tls_key_file": "./fixtures/notary-server.key",
-		"tls_cert_file": "./fixtures/notary-server.crt"
+		"http_addr": ":4443",
+		"tls_key_file": "./notary-server.key",
+		"tls_cert_file": "./notary-server.crt"
 	},
 	"trust_service": {
 	  "type": "remote",
 	  "hostname": "notarysigner",
 	  "port": "7899",
-	  "tls_ca_file": "./fixtures/root-ca.crt",
+	  "tls_ca_file": "./root-ca.crt",
 	  "key_algorithm": "ecdsa",
-	  "tls_client_cert": "./fixtures/notary-server.crt",
-	  "tls_client_key": "./fixtures/notary-server.key"
+	  "tls_client_cert": "./notary-server.crt",
+	  "tls_client_key": "./notary-server.key"
 	},
 	"logging": {
 		"level": "debug"
diff --git a/fixtures/signer-config-local.json b/fixtures/signer-config-local.json
new file mode 100644
index 00000000..e5da0101
--- /dev/null
+++ b/fixtures/signer-config-local.json
@@ -0,0 +1,15 @@
+{
+	"server": {
+		"http_addr": ":4444",
+		"grpc_addr": ":7899",
+		"tls_cert_file": "./notary-signer.crt",
+		"tls_key_file": "./notary-signer.key",
+		"client_ca_file": "./notary-server.crt"
+	},
+	"logging": {
+		"level": "debug"
+	},
+	"storage": {
+		"backend": "memory"
+	}
+}
diff --git a/fixtures/signer-config.json b/fixtures/signer-config.json
new file mode 100644
index 00000000..780b7c5a
--- /dev/null
+++ b/fixtures/signer-config.json
@@ -0,0 +1,16 @@
+{
+	"server": {
+		"http_addr": ":4444",
+		"grpc_addr": ":7899",
+		"tls_cert_file": "./notary-signer.crt",
+		"tls_key_file": "./notary-signer.key",
+		"client_ca_file": "./notary-server.crt"
+	},
+	"logging": {
+		"level": "debug"
+	},
+	"storage": {
+		"backend": "mysql",
+		"db_url": "root@tcp(notarymysql:3306)/notary"
+	}
+}
diff --git a/utils/configuration.go b/utils/configuration.go
new file mode 100644
index 00000000..e0cf346d
--- /dev/null
+++ b/utils/configuration.go
@@ -0,0 +1,161 @@
+// Common configuration elements that may be resused
+
+package utils
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+
+	"github.com/Sirupsen/logrus"
+	bugsnag_hook "github.com/Sirupsen/logrus/hooks/bugsnag"
+	"github.com/bugsnag/bugsnag-go"
+	"github.com/spf13/viper"
+)
+
+// Specifies the list of recognized backends
+const (
+	MemoryBackend = "memory"
+	MySQLBackend  = "mysql"
+	SqliteBackend = "sqlite3"
+)
+
+// Storage is a configuration about what storage backend a server should use
+type Storage struct {
+	Backend string
+	Source  string
+}
+
+// GetPathRelativeToConfig gets a configuration key which is a path, and if
+// it is not empty or an absolute path, returns the absolute path relative
+// to the configuration file
+func GetPathRelativeToConfig(configuration *viper.Viper, key string) string {
+	configFile := configuration.ConfigFileUsed()
+	p := configuration.GetString(key)
+	if p == "" || filepath.IsAbs(p) {
+		return p
+	}
+	return filepath.Join(filepath.Dir(configFile), p)
+}
+
+// ParseServerTLS tries to parse out a valid ServerTLSOpts from a Viper:
+// - If TLS is required, both the cert and key must be provided
+// - If TLS is not requried, either both the cert and key must be provided or
+//	 neither must be provided
+// The files are relative to the config file used to populate the instance
+// of viper.
+func ParseServerTLS(configuration *viper.Viper, tlsRequired bool) (*ServerTLSOpts, error) {
+	//  unmarshalling into objects does not seem to pick up env vars
+	tlsOpts := ServerTLSOpts{
+		ServerCertFile: GetPathRelativeToConfig(configuration, "server.tls_cert_file"),
+		ServerKeyFile:  GetPathRelativeToConfig(configuration, "server.tls_key_file"),
+		ClientCAFile:   GetPathRelativeToConfig(configuration, "server.client_ca_file"),
+	}
+
+	cert, key := tlsOpts.ServerCertFile, tlsOpts.ServerKeyFile
+	if tlsRequired {
+		if cert == "" || key == "" {
+			return nil, fmt.Errorf("both the TLS certificate and key are mandatory")
+		}
+	} else {
+		if (cert == "" && key != "") || (cert != "" && key == "") {
+			return nil, fmt.Errorf(
+				"either include both a cert and key file, or neither to disable TLS")
+		}
+	}
+
+	if cert == "" && key == "" && tlsOpts.ClientCAFile == "" {
+		return nil, nil
+	}
+
+	return &tlsOpts, nil
+}
+
+// ParseLogLevel tries to parse out a log level from a Viper.  If there is no
+// configuration, defaults to the provided error level
+func ParseLogLevel(configuration *viper.Viper, defaultLevel logrus.Level) (
+	logrus.Level, error) {
+
+	logStr := configuration.GetString("logging.level")
+	if logStr == "" {
+		return defaultLevel, nil
+	}
+	return logrus.ParseLevel(logStr)
+}
+
+// ParseStorage tries to parse out Storage from a Viper.  If backend and
+// URL are not provided, returns a nil pointer.  Storage is required (if
+// a backend is not provided, an error will be returned.)
+func ParseStorage(configuration *viper.Viper, allowedBackends []string) (*Storage, error) {
+	store := Storage{
+		Backend: configuration.GetString("storage.backend"),
+		Source:  configuration.GetString("storage.db_url"),
+	}
+
+	supported := false
+	store.Backend = strings.ToLower(store.Backend)
+	for _, backend := range allowedBackends {
+		if backend == store.Backend {
+			supported = true
+			break
+		}
+	}
+
+	if !supported {
+		return nil, fmt.Errorf(
+			"must specify one of these supported backends: %s",
+			strings.Join(allowedBackends, ", "))
+	}
+
+	if store.Backend == MemoryBackend {
+		return &Storage{Backend: MemoryBackend}, nil
+	}
+	if store.Source == "" {
+		return nil, fmt.Errorf(
+			"must provide a non-empty database source for %s", store.Backend)
+	}
+	return &store, nil
+}
+
+// ParseBugsnag tries to parse out a Bugsnag Configuration from a Viper.
+// If no values are provided, returns a nil pointer.
+func ParseBugsnag(configuration *viper.Viper) (*bugsnag.Configuration, error) {
+	// can't unmarshal because we can't add tags to the bugsnag.Configuration
+	// struct
+	bugconf := bugsnag.Configuration{
+		APIKey:       configuration.GetString("reporting.bugsnag.api_key"),
+		ReleaseStage: configuration.GetString("reporting.bugsnag.release_stage"),
+		Endpoint:     configuration.GetString("reporting.bugsnag.endpoint"),
+	}
+	if bugconf.APIKey == "" && bugconf.ReleaseStage == "" && bugconf.Endpoint == "" {
+		return nil, nil
+	}
+	if bugconf.APIKey == "" {
+		return nil, fmt.Errorf("must provide an API key for bugsnag")
+	}
+	return &bugconf, nil
+}
+
+// utilities for setting up/acting on common configurations
+
+// SetupViper sets up an instance of viper to also look at environment
+// variables
+func SetupViper(configuration *viper.Viper, envPrefix string) {
+	configuration.SetEnvPrefix(envPrefix)
+	configuration.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
+	configuration.AutomaticEnv()
+}
+
+// SetUpBugsnag configures bugsnag and sets up a logrus hook
+func SetUpBugsnag(config *bugsnag.Configuration) error {
+	if config != nil {
+		bugsnag.Configure(*config)
+		hook, err := bugsnag_hook.NewBugsnagHook()
+		if err != nil {
+			return err
+		}
+		logrus.AddHook(hook)
+		logrus.Debug("Adding logrus hook for Bugsnag")
+	}
+	return nil
+}
diff --git a/utils/tls_config.go b/utils/tls_config.go
index 15050f84..9c7033cb 100644
--- a/utils/tls_config.go
+++ b/utils/tls_config.go
@@ -44,10 +44,9 @@ func poolFromFile(filename string) (*x509.CertPool, error) {
 // ServerTLSOpts generates a tls configuration for servers using the
 // provided parameters.
 type ServerTLSOpts struct {
-	ServerCertFile    string
-	ServerKeyFile     string
-	RequireClientAuth bool
-	ClientCAFile      string
+	ServerCertFile string
+	ServerKeyFile  string
+	ClientCAFile   string
 }
 
 // ConfigureServerTLS specifies a set of ciphersuites, the server cert and key,
@@ -73,16 +72,13 @@ func ConfigureServerTLS(opts *ServerTLSOpts) (*tls.Config, error) {
 		Rand:                     rand.Reader,
 	}
 
-	if opts.RequireClientAuth {
-		tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
-	}
-
 	if opts.ClientCAFile != "" {
 		pool, err := poolFromFile(opts.ClientCAFile)
 		if err != nil {
 			return nil, err
 		}
 		tlsConfig.ClientCAs = pool
+		tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
 	}
 
 	return tlsConfig, nil


Test patch:
diff --git a/cmd/notary-server/main_test.go b/cmd/notary-server/main_test.go
index 0ab2dfbb..82e0fad2 100644
--- a/cmd/notary-server/main_test.go
+++ b/cmd/notary-server/main_test.go
@@ -4,9 +4,13 @@ import (
 	"bytes"
 	"crypto/tls"
 	"fmt"
+	"io/ioutil"
+	"os"
 	"strings"
 	"testing"
 
+	"github.com/docker/notary/server/storage"
+	"github.com/docker/notary/utils"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
 )
@@ -18,63 +22,71 @@ const (
 )
 
 // initializes a viper object with test configuration
-func configure(jsonConfig []byte) *viper.Viper {
+func configure(jsonConfig string) *viper.Viper {
 	config := viper.New()
 	config.SetConfigType("json")
-	config.ReadConfig(bytes.NewBuffer(jsonConfig))
+	config.ReadConfig(bytes.NewBuffer([]byte(jsonConfig)))
 	return config
 }
 
-// If neither the cert nor the key are provided, a nil tls config is returned.
-func TestServerTLSMissingCertAndKey(t *testing.T) {
-	tlsConfig, err := serverTLS(configure([]byte(`{"server": {}}`)))
-	assert.NoError(t, err)
-	assert.Nil(t, tlsConfig)
-}
-
-// Cert and Key either both have to be empty or both have to be provided.
-func TestServerTLSMissingCertAndOrKey(t *testing.T) {
-	configs := []string{
-		fmt.Sprintf(`{"tls_cert_file": "%s"}`, Cert),
-		fmt.Sprintf(`{"tls_key_file": "%s"}`, Key),
+func TestGetAddrAndTLSConfigInvalidTLS(t *testing.T) {
+	invalids := []string{
+		`{"server": {
+				"http_addr": ":1234",
+				"tls_key_file": "nope"
+		}}`,
 	}
-	for _, serverConfig := range configs {
-		config := configure(
-			[]byte(fmt.Sprintf(`{"server": %s}`, serverConfig)))
-		tlsConfig, err := serverTLS(config)
+	for _, configJSON := range invalids {
+		_, _, err := getAddrAndTLSConfig(configure(configJSON))
 		assert.Error(t, err)
-		assert.Nil(t, tlsConfig)
-		assert.True(t,
-			strings.Contains(err.Error(), "Partial TLS configuration found."))
 	}
 }
 
-// The rest of the functionality of serverTLS depends upon
-// utils.ConfigureServerTLS, so this test just asserts that if successful,
-// the correct tls.Config is returned based on all the configuration parameters
-func TestServerTLSSuccess(t *testing.T) {
-	keypair, err := tls.LoadX509KeyPair(Cert, Key)
-	assert.NoError(t, err, "Unable to load cert and key for testing")
+func TestGetAddrAndTLSConfigNoHTTPAddr(t *testing.T) {
+	_, _, err := getAddrAndTLSConfig(configure(fmt.Sprintf(`{
+		"server": {
+			"tls_cert_file": "%s",
+			"tls_key_file": "%s"
+		}
+	}`, Cert, Key)))
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "http listen address required for server")
+}
 
-	config := fmt.Sprintf(
-		`{"server": {"tls_cert_file": "%s", "tls_key_file": "%s"}}`,
-		Cert, Key)
-	tlsConfig, err := serverTLS(configure([]byte(config)))
+func TestGetAddrAndTLSConfigSuccessWithTLS(t *testing.T) {
+	httpAddr, tlsConf, err := getAddrAndTLSConfig(configure(fmt.Sprintf(`{
+		"server": {
+			"http_addr": ":2345",
+			"tls_cert_file": "%s",
+			"tls_key_file": "%s"
+		}
+	}`, Cert, Key)))
 	assert.NoError(t, err)
-	assert.Equal(t, []tls.Certificate{keypair}, tlsConfig.Certificates)
+	assert.Equal(t, ":2345", httpAddr)
+	assert.NotNil(t, tlsConf)
 }
 
-// The rest of the functionality of serverTLS depends upon
-// utils.ConfigureServerTLS, so this test just asserts that if it fails,
-// the error is propogated.
-func TestServerTLSFailure(t *testing.T) {
-	config := fmt.Sprintf(
-		`{"server": {"tls_cert_file": "non-exist", "tls_key_file": "%s"}}`,
-		Key)
-	tlsConfig, err := serverTLS(configure([]byte(config)))
-	assert.Error(t, err)
-	assert.Nil(t, tlsConfig)
-	assert.True(t, strings.Contains(err.Error(), "Unable to set up TLS"))
+func TestGetAddrAndTLSConfigSuccessWithoutTLS(t *testing.T) {
+	httpAddr, tlsConf, err := getAddrAndTLSConfig(configure(
+		`{"server": {"http_addr": ":2345"}}`))
+	assert.NoError(t, err)
+	assert.Equal(t, ":2345", httpAddr)
+	assert.Nil(t, tlsConf)
+}
+
+// We don't support client CAs yet on notary server
+func TestGetAddrAndTLSConfigSkipClientTLS(t *testing.T) {
+	httpAddr, tlsConf, err := getAddrAndTLSConfig(configure(fmt.Sprintf(`{
+		"server": {
+			"http_addr": ":2345",
+			"tls_cert_file": "%s",
+			"tls_key_file": "%s",
+			"client_ca_file": "%s"
+		}
+	}`, Cert, Key, Root)))
+	assert.NoError(t, err)
+	assert.Equal(t, ":2345", httpAddr)
+	assert.Nil(t, tlsConf.ClientCAs)
 }
 
 // Client cert and Key either both have to be empty or both have to be
@@ -88,7 +100,7 @@ func TestGrpcTLSMissingCertOrKey(t *testing.T) {
 		jsonConfig := fmt.Sprintf(
 			`{"trust_service": {"hostname": "notary-signer", %s}}`,
 			trustConfig)
-		config := configure([]byte(jsonConfig))
+		config := configure(jsonConfig)
 		tlsConfig, err := grpcTLS(config)
 		assert.Error(t, err)
 		assert.Nil(t, tlsConfig)
@@ -101,7 +113,7 @@ func TestGrpcTLSMissingCertOrKey(t *testing.T) {
 // the provided serverName is still returned.
 func TestGrpcTLSNoConfig(t *testing.T) {
 	tlsConfig, err := grpcTLS(
-		configure([]byte(`{"trust_service": {"hostname": "notary-signer"}}`)))
+		configure(`{"trust_service": {"hostname": "notary-signer"}}`))
 	assert.NoError(t, err)
 	assert.Equal(t, "notary-signer", tlsConfig.ServerName)
 	assert.Nil(t, tlsConfig.RootCAs)
@@ -110,18 +122,22 @@ func TestGrpcTLSNoConfig(t *testing.T) {
 
 // The rest of the functionality of grpcTLS depends upon
 // utils.ConfigureClientTLS, so this test just asserts that if successful,
-// the correct tls.Config is returned based on all the configuration parameters
+// the correct tls.Config is returned based on all the configuration parameters,
+// and that it gets the path relative to the config file
 func TestGrpcTLSSuccess(t *testing.T) {
 	keypair, err := tls.LoadX509KeyPair(Cert, Key)
 	assert.NoError(t, err, "Unable to load cert and key for testing")
 
-	config := fmt.Sprintf(
-		`{"trust_service": {
+	configJSON := `{
+		"trust_service": {
             "hostname": "notary-server",
-            "tls_client_cert": "%s",
-            "tls_client_key": "%s"}}`,
-		Cert, Key)
-	tlsConfig, err := grpcTLS(configure([]byte(config)))
+            "tls_client_cert": "notary-server.crt",
+            "tls_client_key": "notary-server.key"
+        }
+    }`
+	config := configure(configJSON)
+	config.SetConfigFile("../../fixtures/config.json")
+	tlsConfig, err := grpcTLS(config)
 	assert.NoError(t, err)
 	assert.Equal(t, []tls.Certificate{keypair}, tlsConfig.Certificates)
 }
@@ -136,9 +152,41 @@ func TestGrpcTLSFailure(t *testing.T) {
             "tls_client_cert": "no-exist",
             "tls_client_key": "%s"}}`,
 		Key)
-	tlsConfig, err := grpcTLS(configure([]byte(config)))
+	tlsConfig, err := grpcTLS(configure(config))
 	assert.Error(t, err)
 	assert.Nil(t, tlsConfig)
 	assert.True(t, strings.Contains(err.Error(),
 		"Unable to configure TLS to the trust service"))
 }
+
+// Just to ensure that errors are propogated
+func TestGetStoreInvalid(t *testing.T) {
+	config := `{"storage": {"backend": "asdf", "db_url": "/tmp/1234"}}`
+
+	_, err := getStore(configure(config), []string{"mysql"})
+	assert.Error(t, err)
+}
+
+func TestGetStoreDBStore(t *testing.T) {
+	tmpFile, err := ioutil.TempFile("/tmp", "sqlite3")
+	assert.NoError(t, err)
+	tmpFile.Close()
+	defer os.Remove(tmpFile.Name())
+
+	config := fmt.Sprintf(`{"storage": {"backend": "%s", "db_url": "%s"}}`,
+		utils.SqliteBackend, tmpFile.Name())
+
+	store, err := getStore(configure(config), []string{utils.SqliteBackend})
+	assert.NoError(t, err)
+	_, ok := store.(*storage.SQLStorage)
+	assert.True(t, ok)
+}
+
+func TestGetMemoryStore(t *testing.T) {
+	config := fmt.Sprintf(`{"storage": {"backend": "%s"}}`, utils.MemoryBackend)
+	store, err := getStore(configure(config),
+		[]string{utils.MySQLBackend, utils.MemoryBackend})
+	assert.NoError(t, err)
+	_, ok := store.(*storage.MemStorage)
+	assert.True(t, ok)
+}
diff --git a/cmd/notary-signer/main_test.go b/cmd/notary-signer/main_test.go
index 0864708f..bd219ae7 100644
--- a/cmd/notary-signer/main_test.go
+++ b/cmd/notary-signer/main_test.go
@@ -6,9 +6,16 @@ import (
 	"bytes"
 	"crypto/tls"
 	"fmt"
-	"strings"
+	"io/ioutil"
+	"os"
 	"testing"
 
+	"github.com/docker/notary/signer"
+	"github.com/docker/notary/signer/keydbstore"
+	"github.com/docker/notary/tuf/data"
+	"github.com/docker/notary/utils"
+	"github.com/jinzhu/gorm"
+	_ "github.com/mattn/go-sqlite3"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
 )
@@ -20,54 +27,187 @@ const (
 )
 
 // initializes a viper object with test configuration
-func configure(jsonConfig []byte) *viper.Viper {
+func configure(jsonConfig string) *viper.Viper {
 	config := viper.New()
 	config.SetConfigType("json")
-	config.ReadConfig(bytes.NewBuffer(jsonConfig))
+	config.ReadConfig(bytes.NewBuffer([]byte(jsonConfig)))
 	return config
 }
 
-func TestSignerTLSMissingCertAndOrKey(t *testing.T) {
-	configs := []string{
-		"{}",
-		fmt.Sprintf(`{"cert_file": "%s"}`, Cert),
-		fmt.Sprintf(`{"key_file": "%s"}`, Key),
+// If the TLS configuration is invalid, an error is returned.  This doesn't test
+// all the cases of the TLS configuration being invalid, since it's just
+// calling configuration.ParseTLSConfig - this test just makes sure the
+// error is propogated.
+func TestGetAddrAndTLSConfigInvalidTLS(t *testing.T) {
+	invalids := []string{
+		`{"server": {"http_addr": ":1234", "grpc_addr": ":2345"}}`,
+		`{"server": {
+				"http_addr": ":1234",
+				"grpc_addr": ":2345",
+				"tls_cert_file": "nope",
+				"tls_key_file": "nope"
+		}}`,
 	}
-	for _, serverConfig := range configs {
-		config := configure(
-			[]byte(fmt.Sprintf(`{"server": %s}`, serverConfig)))
-		tlsConfig, err := signerTLS(config, false)
+	for _, configJSON := range invalids {
+		_, _, _, err := getAddrAndTLSConfig(configure(configJSON))
 		assert.Error(t, err)
-		assert.Nil(t, tlsConfig)
-		assert.Equal(t, "Certificate and key are mandatory", err.Error())
+		assert.Contains(t, err.Error(), "unable to set up TLS")
 	}
 }
 
-// The rest of the functionality of signerTLS depends upon
-// utils.ConfigureServerTLS, so this test just asserts that if successful,
-// the correct tls.Config is returned based on all the configuration parameters
-func TestSignerTLSSuccess(t *testing.T) {
-	keypair, err := tls.LoadX509KeyPair(Cert, Key)
-	assert.NoError(t, err, "Unable to load cert and key for testing")
-
-	config := fmt.Sprintf(
-		`{"server": {"cert_file": "%s", "key_file": "%s", "client_ca_file": "%s"}}`,
-		Cert, Key, Cert)
-	tlsConfig, err := signerTLS(configure([]byte(config)), false)
+// If a GRPC address is not provided, an error is returned.
+func TestGetAddrAndTLSConfigNoGRPCAddr(t *testing.T) {
+	_, _, _, err := getAddrAndTLSConfig(configure(fmt.Sprintf(`{
+		"server": {
+			"http_addr": ":1234",
+			"tls_cert_file": "%s",
+			"tls_key_file": "%s"
+		}
+	}`, Cert, Key)))
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "grpc listen address required for server")
+}
+
+// If an HTTP address is not provided, an error is returned.
+func TestGetAddrAndTLSConfigNoHTTPAddr(t *testing.T) {
+	_, _, _, err := getAddrAndTLSConfig(configure(fmt.Sprintf(`{
+		"server": {
+			"grpc_addr": ":1234",
+			"tls_cert_file": "%s",
+			"tls_key_file": "%s"
+		}
+	}`, Cert, Key)))
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "http listen address required for server")
+}
+
+// Success parsing a valid TLS config, HTTP address, and GRPC address.
+func TestGetAddrAndTLSConfigSuccess(t *testing.T) {
+	httpAddr, grpcAddr, tlsConf, err := getAddrAndTLSConfig(configure(fmt.Sprintf(`{
+		"server": {
+			"http_addr": ":2345",
+			"grpc_addr": ":1234",
+			"tls_cert_file": "%s",
+			"tls_key_file": "%s"
+		}
+	}`, Cert, Key)))
+	assert.NoError(t, err)
+	assert.Equal(t, ":2345", httpAddr)
+	assert.Equal(t, ":1234", grpcAddr)
+	assert.NotNil(t, tlsConf)
+}
+
+// If a default alias is not provided to a DB backend, an error is returned.
+func TestSetupCryptoServicesDBStoreNoDefaultAlias(t *testing.T) {
+	tmpFile, err := ioutil.TempFile("/tmp", "sqlite3")
+	assert.NoError(t, err)
+	tmpFile.Close()
+	defer os.Remove(tmpFile.Name())
+
+	_, err = setUpCryptoservices(
+		configure(fmt.Sprintf(
+			`{"storage": {"backend": "%s", "db_url": "%s"}}`,
+			utils.SqliteBackend, tmpFile.Name())),
+		[]string{utils.SqliteBackend})
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "must provide a default alias for the key DB")
+}
+
+// If a default alias *is* provided to a valid DB backend, a valid
+// CryptoService is returned.  (This depends on ParseStorage, which is tested
+// separately, so this doesn't test all the possible cases of storage
+// success/failure).
+func TestSetupCryptoServicesDBStoreSuccess(t *testing.T) {
+	tmpFile, err := ioutil.TempFile("/tmp", "sqlite3")
+	assert.NoError(t, err)
+	tmpFile.Close()
+	defer os.Remove(tmpFile.Name())
+
+	// Ensure that the private_key table exists
+	db, err := gorm.Open("sqlite3", tmpFile.Name())
+	assert.NoError(t, err)
+	var (
+		gormKey = keydbstore.GormPrivateKey{}
+		count   int
+	)
+	db.CreateTable(&gormKey)
+	db.Model(&gormKey).Count(&count)
+	assert.Equal(t, 0, count)
+
+	cryptoServices, err := setUpCryptoservices(
+		configure(fmt.Sprintf(
+			`{"storage": {"backend": "%s", "db_url": "%s"},
+			"default_alias": "timestamp"}`,
+			utils.SqliteBackend, tmpFile.Name())),
+		[]string{utils.SqliteBackend})
+	assert.NoError(t, err)
+	assert.Len(t, cryptoServices, 2)
+
+	edService, ok := cryptoServices[data.ED25519Key]
+	assert.True(t, ok)
+
+	ecService, ok := cryptoServices[data.ECDSAKey]
+	assert.True(t, ok)
+
+	assert.Equal(t, edService, ecService)
+
+	// since the keystores are not exposed by CryptoService, try creating
+	// a key and seeing if it is in the sqlite DB.
+	os.Setenv("NOTARY_SIGNER_TIMESTAMP", "password")
+	defer os.Unsetenv("NOTARY_SIGNER_TIMESTAMP")
+
+	_, err = ecService.Create("timestamp", data.ECDSAKey)
 	assert.NoError(t, err)
-	assert.Equal(t, []tls.Certificate{keypair}, tlsConfig.Certificates)
-	assert.NotNil(t, tlsConfig.ClientCAs)
+	db.Model(&gormKey).Count(&count)
+	assert.Equal(t, 1, count)
 }
 
-// The rest of the functionality of signerTLS depends upon
-// utils.ConfigureServerTLS, so this test just asserts that if it fails,
-// the error is propogated.
-func TestSignerTLSFailure(t *testing.T) {
-	config := fmt.Sprintf(
-		`{"server": {"cert_file": "%s", "key_file": "%s", "client_ca_file": "%s"}}`,
-		Cert, Key, "non-existant")
-	tlsConfig, err := signerTLS(configure([]byte(config)), false)
+// If a memory backend is specified, then a default alias is not needed, and
+// a valid CryptoService is returned.
+func TestSetupCryptoServicesMemoryStore(t *testing.T) {
+	config := configure(fmt.Sprintf(`{"storage": {"backend": "%s"}}`,
+		utils.MemoryBackend))
+	cryptoServices, err := setUpCryptoservices(config,
+		[]string{utils.SqliteBackend, utils.MemoryBackend})
+	assert.NoError(t, err)
+	assert.Len(t, cryptoServices, 2)
+
+	edService, ok := cryptoServices[data.ED25519Key]
+	assert.True(t, ok)
+
+	ecService, ok := cryptoServices[data.ECDSAKey]
+	assert.True(t, ok)
+
+	assert.Equal(t, edService, ecService)
+
+	// since the keystores are not exposed by CryptoService, try creating
+	// and getting the key
+	pubKey, err := ecService.Create("", data.ECDSAKey)
+	assert.NoError(t, err)
+	privKey, _, err := ecService.GetPrivateKey(pubKey.ID())
+	assert.NoError(t, err)
+	assert.NotNil(t, privKey)
+}
+
+func TestSetupHTTPServer(t *testing.T) {
+	httpServer := setupHTTPServer(":4443", nil, make(signer.CryptoServiceIndex))
+	assert.Equal(t, ":4443", httpServer.Addr)
+	assert.Nil(t, httpServer.TLSConfig)
+}
+
+func TestSetupGRPCServerInvalidAddress(t *testing.T) {
+	_, _, err := setupGRPCServer("nope", nil, make(signer.CryptoServiceIndex))
 	assert.Error(t, err)
-	assert.Nil(t, tlsConfig)
-	assert.True(t, strings.Contains(err.Error(), "Unable to set up TLS"))
+	assert.Contains(t, err.Error(), "grpc server failed to listen on nope")
+}
+
+func TestSetupGRPCServerSuccess(t *testing.T) {
+	tlsConf := tls.Config{InsecureSkipVerify: true}
+	grpcServer, lis, err := setupGRPCServer(":7899", &tlsConf,
+		make(signer.CryptoServiceIndex))
+	defer lis.Close()
+	assert.NoError(t, err)
+	assert.Equal(t, "[::]:7899", lis.Addr().String())
+	assert.Equal(t, "tcp", lis.Addr().Network())
+	assert.NotNil(t, grpcServer)
 }
diff --git a/utils/configuration_test.go b/utils/configuration_test.go
new file mode 100644
index 00000000..308f8983
--- /dev/null
+++ b/utils/configuration_test.go
@@ -0,0 +1,337 @@
+package utils
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/bugsnag/bugsnag-go"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/assert"
+)
+
+const envPrefix = "NOTARY_TESTING_ENV_PREFIX"
+
+// initializes a viper object with test configuration
+func configure(jsonConfig string) *viper.Viper {
+	config := viper.New()
+	SetupViper(config, envPrefix)
+	config.SetConfigType("json")
+	config.ReadConfig(bytes.NewBuffer([]byte(jsonConfig)))
+	return config
+}
+
+// Sets the environment variables in the given map, prefixed by envPrefix.
+func setupEnvironmentVariables(t *testing.T, vars map[string]string) {
+	for k, v := range vars {
+		err := os.Setenv(fmt.Sprintf("%s_%s", envPrefix, k), v)
+		assert.NoError(t, err)
+	}
+}
+
+// Unsets whatever environment variables were set with this map
+func cleanupEnvironmentVariables(t *testing.T, vars map[string]string) {
+	for k := range vars {
+		err := os.Unsetenv(fmt.Sprintf("%s_%s", envPrefix, k))
+		assert.NoError(t, err)
+	}
+
+}
+
+// An error is returned if the log level is not parsable
+func TestParseInvalidLogLevel(t *testing.T) {
+	_, err := ParseLogLevel(configure(`{"logging": {"level": "horatio"}}`),
+		logrus.DebugLevel)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "not a valid logrus Level")
+}
+
+// If there is no logging level configured it is set to the default level
+func TestParseNoLogLevel(t *testing.T) {
+	empties := []string{`{}`, `{"logging": {}}`}
+	for _, configJSON := range empties {
+		lvl, err := ParseLogLevel(configure(configJSON), logrus.DebugLevel)
+		assert.NoError(t, err)
+		assert.Equal(t, logrus.DebugLevel, lvl)
+	}
+}
+
+// If there is logging level configured, it is set to the configured one
+func TestParseLogLevel(t *testing.T) {
+	lvl, err := ParseLogLevel(configure(`{"logging": {"level": "error"}}`),
+		logrus.DebugLevel)
+	assert.NoError(t, err)
+	assert.Equal(t, logrus.ErrorLevel, lvl)
+}
+
+func TestParseLogLevelWithEnvironmentVariables(t *testing.T) {
+	vars := map[string]string{"LOGGING_LEVEL": "error"}
+	setupEnvironmentVariables(t, vars)
+	defer cleanupEnvironmentVariables(t, vars)
+
+	lvl, err := ParseLogLevel(configure(`{}`),
+		logrus.DebugLevel)
+	assert.NoError(t, err)
+	assert.Equal(t, logrus.ErrorLevel, lvl)
+}
+
+// An error is returned if there's no API key
+func TestParseInvalidBugsnag(t *testing.T) {
+	_, err := ParseBugsnag(configure(
+		`{"reporting": {"bugsnag": {"endpoint": "http://12345"}}}`))
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "must provide an API key")
+}
+
+// If there's no bugsnag, a nil pointer is returned
+func TestParseNoBugsnag(t *testing.T) {
+	empties := []string{`{}`, `{"reporting": {}}`}
+	for _, configJSON := range empties {
+		bugconf, err := ParseBugsnag(configure(configJSON))
+		assert.NoError(t, err)
+		assert.Nil(t, bugconf)
+	}
+}
+
+func TestParseBugsnag(t *testing.T) {
+	config := configure(`{
+		"reporting": {
+			"bugsnag": {
+				"api_key": "12345",
+				"release_stage": "production",
+				"endpoint": "http://1234.com"
+			}
+		}
+	}`)
+
+	expected := bugsnag.Configuration{
+		APIKey:       "12345",
+		ReleaseStage: "production",
+		Endpoint:     "http://1234.com",
+	}
+
+	bugconf, err := ParseBugsnag(config)
+	assert.NoError(t, err)
+	assert.Equal(t, expected, *bugconf)
+}
+
+func TestParseBugsnagWithEnvironmentVariables(t *testing.T) {
+	config := configure(`{
+		"reporting": {
+			"bugsnag": {
+				"api_key": "12345",
+				"release_stage": "staging"
+			}
+		}
+	}`)
+
+	vars := map[string]string{
+		"REPORTING_BUGSNAG_RELEASE_STAGE": "production",
+		"REPORTING_BUGSNAG_ENDPOINT":      "http://1234.com",
+	}
+	setupEnvironmentVariables(t, vars)
+	defer cleanupEnvironmentVariables(t, vars)
+
+	expected := bugsnag.Configuration{
+		APIKey:       "12345",
+		ReleaseStage: "production",
+		Endpoint:     "http://1234.com",
+	}
+
+	bugconf, err := ParseBugsnag(config)
+	assert.NoError(t, err)
+	assert.Equal(t, expected, *bugconf)
+}
+
+// If the storage backend is invalid or not provided, an error is returned.
+func TestParseInvalidStorageBackend(t *testing.T) {
+	invalids := []string{
+		`{"storage": {"backend": "postgres", "db_url": "1234"}}`,
+		`{"storage": {"db_url": "12345"}}`,
+		`{"storage": {}}`,
+		`{}`,
+	}
+	for _, configJSON := range invalids {
+		_, err := ParseStorage(configure(configJSON),
+			[]string{MySQLBackend, SqliteBackend})
+		assert.Error(t, err, fmt.Sprintf("'%s' should be an error", configJSON))
+		assert.Contains(t, err.Error(),
+			"must specify one of these supported backends: mysql, sqlite3")
+	}
+}
+
+// If there is no DB url for non-memory backends, an error is returned.
+func TestParseInvalidStorageNoDBSource(t *testing.T) {
+	invalids := []string{
+		`{"storage": {"backend": "%s"}}`,
+		`{"storage": {"backend": "%s", "db_url": ""}}`,
+	}
+	for _, backend := range []string{MySQLBackend, SqliteBackend} {
+		for _, configJSONFmt := range invalids {
+			configJSON := fmt.Sprintf(configJSONFmt, backend)
+			_, err := ParseStorage(configure(configJSON),
+				[]string{MySQLBackend, SqliteBackend})
+			assert.Error(t, err, fmt.Sprintf("'%s' should be an error", configJSON))
+			assert.Contains(t, err.Error(),
+				fmt.Sprintf("must provide a non-empty database source for %s", backend))
+		}
+	}
+}
+
+// If a memory storage backend is specified, no DB URL is necessary for a
+// successful storage parse.
+func TestParseStorageMemoryStore(t *testing.T) {
+	config := configure(`{"storage": {"backend": "MEMORY"}}`)
+	expected := Storage{Backend: MemoryBackend}
+
+	store, err := ParseStorage(config, []string{MySQLBackend, MemoryBackend})
+	assert.NoError(t, err)
+	assert.Equal(t, expected, *store)
+}
+
+// A supported backend with DB source will be successfully parsed.
+func TestParseStorageDBStore(t *testing.T) {
+	config := configure(`{
+		"storage": {
+			"backend": "MySQL",
+			"db_url": "username:passord@tcp(hostname:1234)/dbname"
+		}
+	}`)
+
+	expected := Storage{
+		Backend: "mysql",
+		Source:  "username:passord@tcp(hostname:1234)/dbname",
+	}
+
+	store, err := ParseStorage(config, []string{"mysql"})
+	assert.NoError(t, err)
+	assert.Equal(t, expected, *store)
+}
+
+func TestParseStorageWithEnvironmentVariables(t *testing.T) {
+	config := configure(`{
+		"storage": {
+			"db_url": "username:passord@tcp(hostname:1234)/dbname"
+		}
+	}`)
+
+	vars := map[string]string{"STORAGE_BACKEND": "MySQL"}
+	setupEnvironmentVariables(t, vars)
+	defer cleanupEnvironmentVariables(t, vars)
+
+	expected := Storage{
+		Backend: "mysql",
+		Source:  "username:passord@tcp(hostname:1234)/dbname",
+	}
+
+	store, err := ParseStorage(config, []string{"mysql"})
+	assert.NoError(t, err)
+	assert.Equal(t, expected, *store)
+}
+
+// If TLS is required and the parameters are missing, an error is returned
+func TestParseTLSNoTLSWhenRequired(t *testing.T) {
+	invalids := []string{
+		`{"server": {"tls_cert_file": "path/to/cert"}}`,
+		`{"server": {"tls_key_file": "path/to/key"}}`,
+	}
+	for _, configJSON := range invalids {
+		_, err := ParseServerTLS(configure(configJSON), true)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(),
+			"both the TLS certificate and key are mandatory")
+	}
+}
+
+// If TLS is not and the cert/key are partially provided, an error is returned
+func TestParseTLSPartialTLS(t *testing.T) {
+	invalids := []string{
+		`{"server": {"tls_cert_file": "path/to/cert"}}`,
+		`{"server": {"tls_key_file": "path/to/key"}}`,
+	}
+	for _, configJSON := range invalids {
+		_, err := ParseServerTLS(configure(configJSON), false)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(),
+			"either include both a cert and key file, or neither to disable TLS")
+	}
+}
+
+func TestParseTLSNoTLSNotRequired(t *testing.T) {
+	config := configure(`{
+		"server": {}
+	}`)
+
+	tlsOpts, err := ParseServerTLS(config, false)
+	assert.NoError(t, err)
+	assert.Nil(t, tlsOpts)
+}
+
+func TestParseTLSWithTLS(t *testing.T) {
+	config := configure(`{
+		"server": {
+			"tls_cert_file": "path/to/cert",
+			"tls_key_file": "path/to/key",
+			"client_ca_file": "path/to/clientca"
+		}
+	}`)
+
+	expected := ServerTLSOpts{
+		ServerCertFile: "path/to/cert",
+		ServerKeyFile:  "path/to/key",
+		ClientCAFile:   "path/to/clientca",
+	}
+
+	tlsOpts, err := ParseServerTLS(config, false)
+	assert.NoError(t, err)
+	assert.Equal(t, expected, *tlsOpts)
+}
+
+func TestParseTLSWithTLSRelativeToConfigFile(t *testing.T) {
+	config := configure(`{
+		"server": {
+			"tls_cert_file": "path/to/cert",
+			"tls_key_file": "/abspath/to/key",
+			"client_ca_file": ""
+		}
+	}`)
+	config.SetConfigFile("/opt/me.json")
+
+	expected := ServerTLSOpts{
+		ServerCertFile: "/opt/path/to/cert",
+		ServerKeyFile:  "/abspath/to/key",
+		ClientCAFile:   "",
+	}
+
+	tlsOpts, err := ParseServerTLS(config, false)
+	assert.NoError(t, err)
+	assert.Equal(t, expected, *tlsOpts)
+}
+
+func TestParseTLSWithEnvironmentVariables(t *testing.T) {
+	config := configure(`{
+		"server": {
+			"tls_cert_file": "path/to/cert",
+			"client_ca_file": "nosuchfile"
+		}
+	}`)
+
+	vars := map[string]string{
+		"SERVER_TLS_KEY_FILE":   "path/to/key",
+		"SERVER_CLIENT_CA_FILE": "path/to/clientca",
+	}
+	setupEnvironmentVariables(t, vars)
+	defer cleanupEnvironmentVariables(t, vars)
+
+	expected := ServerTLSOpts{
+		ServerCertFile: "path/to/cert",
+		ServerKeyFile:  "path/to/key",
+		ClientCAFile:   "path/to/clientca",
+	}
+
+	tlsOpts, err := ParseServerTLS(config, true)
+	assert.NoError(t, err)
+	assert.Equal(t, expected, *tlsOpts)
+}
diff --git a/utils/tls_config_test.go b/utils/tls_config_test.go
index 3854de22..35a5d782 100644
--- a/utils/tls_config_test.go
+++ b/utils/tls_config_test.go
@@ -60,10 +60,9 @@ func TestConfigServerTLSFailsIfUnableToLoadCerts(t *testing.T) {
 		files[i] = "not-real-file"
 
 		result, err := ConfigureServerTLS(&ServerTLSOpts{
-			ServerCertFile:    files[0],
-			ServerKeyFile:     files[1],
-			RequireClientAuth: true,
-			ClientCAFile:      files[2],
+			ServerCertFile: files[0],
+			ServerKeyFile:  files[1],
+			ClientCAFile:   files[2],
 		})
 		assert.Nil(t, result)
 		assert.Error(t, err)
@@ -106,7 +105,7 @@ func TestConfigServerTLSWithEmptyCACertFile(t *testing.T) {
 
 // If server cert and key are provided, and client cert file is provided with
 // one cert, a valid tls.Config is returned with the clientCAs set to that
-// cert.
+// cert.  ClientAuth is set to RequireAndVerifyClientCert.
 func TestConfigServerTLSWithOneCACert(t *testing.T) {
 	keypair, err := tls.LoadX509KeyPair(ServerCert, ServerKey)
 	assert.NoError(t, err)
@@ -119,13 +118,13 @@ func TestConfigServerTLSWithOneCACert(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, []tls.Certificate{keypair}, tlsConfig.Certificates)
 	assert.True(t, tlsConfig.PreferServerCipherSuites)
-	assert.Equal(t, tls.NoClientCert, tlsConfig.ClientAuth)
+	assert.Equal(t, tls.RequireAndVerifyClientCert, tlsConfig.ClientAuth)
 	assert.Len(t, tlsConfig.ClientCAs.Subjects(), 1)
 }
 
 // If server cert and key are provided, and client cert file is provided with
 // multiple certs, a valid tls.Config is returned with the clientCAs set to
-// the valid cert.
+// the valid cert.  ClientAuth is set to RequireAndVerifyClientCert.
 func TestConfigServerTLSWithMultipleCACerts(t *testing.T) {
 	tempFilename := generateMultiCert(t)
 	defer os.RemoveAll(tempFilename)
@@ -141,27 +140,8 @@ func TestConfigServerTLSWithMultipleCACerts(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, []tls.Certificate{keypair}, tlsConfig.Certificates)
 	assert.True(t, tlsConfig.PreferServerCipherSuites)
-	assert.Equal(t, tls.NoClientCert, tlsConfig.ClientAuth)
-	assert.Len(t, tlsConfig.ClientCAs.Subjects(), 2)
-}
-
-// If server cert and key are provided, and client auth is disabled, then
-// a valid tls.Config is returned with ClientAuth set to
-// RequireAndVerifyClientCert
-func TestConfigServerTLSClientAuthEnabled(t *testing.T) {
-	keypair, err := tls.LoadX509KeyPair(ServerCert, ServerKey)
-	assert.NoError(t, err)
-
-	tlsConfig, err := ConfigureServerTLS(&ServerTLSOpts{
-		ServerCertFile:    ServerCert,
-		ServerKeyFile:     ServerKey,
-		RequireClientAuth: true,
-	})
-	assert.NoError(t, err)
-	assert.Equal(t, []tls.Certificate{keypair}, tlsConfig.Certificates)
-	assert.True(t, tlsConfig.PreferServerCipherSuites)
 	assert.Equal(t, tls.RequireAndVerifyClientCert, tlsConfig.ClientAuth)
-	assert.Nil(t, tlsConfig.ClientCAs)
+	assert.Len(t, tlsConfig.ClientCAs.Subjects(), 2)
 }
 
 // The skipVerify boolean gets set on the tls.Config's InsecureSkipBoolean