Use shared configuration parsing tools in notary-server.

This changes the 'addr' parameter of notary-server's config to
'http_addr', so we can add a GRPC server to notary-server if
necessary.  This also allows environment variables to override
the notary-server config file entries, as notary-signer already
does.

The bugsnag configuration has also been changed so that the
bugsnag parameters are under the "bugsnag" key.

Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
Ying Li 2015-11-19 21:03:03 -08:00
parent 9e5ac006ec
commit c43776d36f
3 changed files with 153 additions and 111 deletions

View File

@ -1,6 +1,6 @@
{
"server": {
"addr": ":4443",
"http_addr": ":4443",
"tls_key_file": "./fixtures/notary-server.key",
"tls_cert_file": "./fixtures/notary-server.crt"
},

View File

@ -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,38 +34,41 @@ 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
@ -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
}
if storeConfig != nil {
logrus.Infof("Using %s backend", storeConfig.Backend)
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
}
logrus.Debug("Using memory backend")
return storage.NewMemStorage(), 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{"mysql"})
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"),

View File

@ -4,9 +4,12 @@ import (
"bytes"
"crypto/tls"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/docker/notary/server/storage"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)
@ -18,63 +21,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")
config := fmt.Sprintf(
`{"server": {"tls_cert_file": "%s", "tls_key_file": "%s"}}`,
Cert, Key)
tlsConfig, err := serverTLS(configure([]byte(config)))
assert.NoError(t, err)
assert.Equal(t, []tls.Certificate{keypair}, tlsConfig.Certificates)
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")
}
// 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 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, ":2345", httpAddr)
assert.NotNil(t, tlsConf)
}
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 +99,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 +112,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)
@ -121,7 +132,7 @@ func TestGrpcTLSSuccess(t *testing.T) {
"tls_client_cert": "%s",
"tls_client_key": "%s"}}`,
Cert, Key)
tlsConfig, err := grpcTLS(configure([]byte(config)))
tlsConfig, err := grpcTLS(configure(config))
assert.NoError(t, err)
assert.Equal(t, []tls.Certificate{keypair}, tlsConfig.Certificates)
}
@ -136,9 +147,40 @@ 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": "sqlite3", "db_url": "%s"}}`,
tmpFile.Name())
store, err := getStore(configure(config), []string{"sqlite3"})
assert.NoError(t, err)
_, ok := store.(*storage.SQLStorage)
assert.True(t, ok)
}
func TestGetMemoryStore(t *testing.T) {
config := fmt.Sprintf(`{"storage": {}}`)
store, err := getStore(configure(config), []string{"mysql"})
assert.NoError(t, err)
_, ok := store.(*storage.MemStorage)
assert.True(t, ok)
}