package main import ( "bytes" "crypto/tls" "fmt" "io/ioutil" "os" "strings" "testing" "time" "github.com/docker/notary/server/storage" "github.com/docker/notary/signer/client" "github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/signed" "github.com/docker/notary/utils" "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) const ( Cert = "../../fixtures/notary-server.crt" Key = "../../fixtures/notary-server.key" Root = "../../fixtures/root-ca.crt" ) // initializes a viper object with test configuration func configure(jsonConfig string) *viper.Viper { config := viper.New() config.SetConfigType("json") config.ReadConfig(bytes.NewBuffer([]byte(jsonConfig))) return config } func TestGetAddrAndTLSConfigInvalidTLS(t *testing.T) { invalids := []string{ `{"server": { "http_addr": ":1234", "tls_key_file": "nope" }}`, } for _, configJSON := range invalids { _, _, err := getAddrAndTLSConfig(configure(configJSON)) assert.Error(t, err) } } 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") } 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) } // If neither "remote" nor "local" is passed for "trust_service.type", an // error is returned. func TestGetInvalidTrustService(t *testing.T) { invalids := []string{ `{"trust_service": {"type": "bruhaha", "key_algorithm": "rsa"}}`, `{}`, } var registerCalled = 0 var fakeRegister = func(_ string, _ func() error, _ time.Duration) { registerCalled++ } for _, config := range invalids { _, _, err := getTrustService(configure(config), client.NewNotarySigner, fakeRegister) assert.Error(t, err) assert.Contains(t, err.Error(), "must specify either a \"local\" or \"remote\" type for trust_service") } // no health function ever registered assert.Equal(t, 0, registerCalled) } // If a local trust service is specified, a local trust service will be used // with an ED22519 algorithm no matter what algorithm was specified. No health // function is configured. func TestGetLocalTrustService(t *testing.T) { localConfig := `{"trust_service": {"type": "local", "key_algorithm": "meh"}}` var registerCalled = 0 var fakeRegister = func(_ string, _ func() error, _ time.Duration) { registerCalled++ } trust, algo, err := getTrustService(configure(localConfig), client.NewNotarySigner, fakeRegister) assert.NoError(t, err) assert.IsType(t, &signed.Ed25519{}, trust) assert.Equal(t, data.ED25519Key, algo) // no health function ever registered assert.Equal(t, 0, registerCalled) } // Invalid key algorithms result in an error if a remote trust service was // specified. func TestGetTrustServiceInvalidKeyAlgorithm(t *testing.T) { configTemplate := ` { "trust_service": { "type": "remote", "hostname": "blah", "port": "1234", "key_algorithm": "%s" } }` badKeyAlgos := []string{ fmt.Sprintf(configTemplate, ""), fmt.Sprintf(configTemplate, data.ECDSAx509Key), fmt.Sprintf(configTemplate, "random"), } var registerCalled = 0 var fakeRegister = func(_ string, _ func() error, _ time.Duration) { registerCalled++ } for _, config := range badKeyAlgos { _, _, err := getTrustService(configure(config), client.NewNotarySigner, fakeRegister) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid key algorithm") } // no health function ever registered assert.Equal(t, 0, registerCalled) } // template to be used for testing TLS parsing with the trust service var trustTLSConfigTemplate = ` { "trust_service": { "type": "remote", "hostname": "notary-signer", "port": "1234", "key_algorithm": "ecdsa", %s } }` // Client cert and Key either both have to be empty or both have to be // provided. func TestGetTrustServiceTLSMissingCertOrKey(t *testing.T) { configs := []string{ fmt.Sprintf(`"tls_client_cert": "%s"`, Cert), fmt.Sprintf(`"tls_client_key": "%s"`, Key), } var registerCalled = 0 var fakeRegister = func(_ string, _ func() error, _ time.Duration) { registerCalled++ } for _, clientTLSConfig := range configs { jsonConfig := fmt.Sprintf(trustTLSConfigTemplate, clientTLSConfig) config := configure(jsonConfig) _, _, err := getTrustService(config, client.NewNotarySigner, fakeRegister) assert.Error(t, err) assert.True(t, strings.Contains(err.Error(), "Partial TLS configuration found.")) } // no health function ever registered assert.Equal(t, 0, registerCalled) } // If no TLS configuration is provided for the host server, no TLS config will // be set for the trust service. func TestGetTrustServiceNoTLSConfig(t *testing.T) { config := `{ "trust_service": { "type": "remote", "hostname": "notary-signer", "port": "1234", "key_algorithm": "ecdsa" } }` var registerCalled = 0 var fakeRegister = func(_ string, _ func() error, _ time.Duration) { registerCalled++ } var tlsConfig *tls.Config var fakeNewSigner = func(_, _ string, c *tls.Config) *client.NotarySigner { tlsConfig = c return &client.NotarySigner{} } trust, algo, err := getTrustService(configure(config), fakeNewSigner, fakeRegister) assert.NoError(t, err) assert.IsType(t, &client.NotarySigner{}, trust) assert.Equal(t, "ecdsa", algo) assert.Equal(t, "notary-signer", tlsConfig.ServerName) assert.Nil(t, tlsConfig.RootCAs) assert.Nil(t, tlsConfig.Certificates) // health function registered assert.Equal(t, 1, registerCalled) } // The rest of the functionality of getTrustService depends upon // utils.ConfigureClientTLS, so this test just asserts that if successful, // the correct tls.Config is returned based on all the configuration parameters func TestGetTrustServiceTLSSuccess(t *testing.T) { keypair, err := tls.LoadX509KeyPair(Cert, Key) assert.NoError(t, err, "Unable to load cert and key for testing") tlspart := fmt.Sprintf(`"tls_client_cert": "%s", "tls_client_key": "%s"`, Cert, Key) var registerCalled = 0 var fakeRegister = func(_ string, _ func() error, _ time.Duration) { registerCalled++ } var tlsConfig *tls.Config var fakeNewSigner = func(_, _ string, c *tls.Config) *client.NotarySigner { tlsConfig = c return &client.NotarySigner{} } trust, algo, err := getTrustService( configure(fmt.Sprintf(trustTLSConfigTemplate, tlspart)), fakeNewSigner, fakeRegister) assert.NoError(t, err) assert.IsType(t, &client.NotarySigner{}, trust) assert.Equal(t, "ecdsa", algo) assert.Equal(t, "notary-signer", tlsConfig.ServerName) assert.Equal(t, []tls.Certificate{keypair}, tlsConfig.Certificates) // health function registered assert.Equal(t, 1, registerCalled) } // The rest of the functionality of getTrustService depends upon // utils.ConfigureServerTLS, so this test just asserts that if it fails, // the error is propagated. func TestGetTrustServiceTLSFailure(t *testing.T) { tlspart := fmt.Sprintf(`"tls_client_cert": "none", "tls_client_key": "%s"`, Key) var registerCalled = 0 var fakeRegister = func(_ string, _ func() error, _ time.Duration) { registerCalled++ } _, _, err := getTrustService( configure(fmt.Sprintf(trustTLSConfigTemplate, tlspart)), client.NewNotarySigner, fakeRegister) assert.Error(t, err) assert.True(t, strings.Contains(err.Error(), "Unable to configure TLS to the trust service")) // no health function ever registered assert.Equal(t, 0, registerCalled) } // 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) }