github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/app/server_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"bufio"
     8  	"crypto/tls"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"os"
    15  	"path"
    16  	"strconv"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/getsentry/sentry-go"
    22  	"github.com/mattermost/mattermost-server/v5/mlog"
    23  
    24  	"github.com/mattermost/mattermost-server/v5/config"
    25  	"github.com/mattermost/mattermost-server/v5/model"
    26  	"github.com/mattermost/mattermost-server/v5/store/storetest"
    27  	"github.com/mattermost/mattermost-server/v5/utils/fileutils"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  func TestStartServerSuccess(t *testing.T) {
    32  	s, err := NewServer()
    33  	require.NoError(t, err)
    34  
    35  	s.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
    36  	serverErr := s.Start()
    37  
    38  	client := &http.Client{}
    39  	checkEndpoint(t, client, "http://localhost:"+strconv.Itoa(s.ListenAddr.Port)+"/", http.StatusNotFound)
    40  
    41  	s.Shutdown()
    42  	require.NoError(t, serverErr)
    43  }
    44  
    45  func TestReadReplicaDisabledBasedOnLicense(t *testing.T) {
    46  	t.Skip("TODO: fix flaky test")
    47  	cfg := model.Config{}
    48  	cfg.SetDefaults()
    49  	driverName := os.Getenv("MM_SQLSETTINGS_DRIVERNAME")
    50  	if driverName == "" {
    51  		driverName = model.DATABASE_DRIVER_POSTGRES
    52  	}
    53  	dsn := ""
    54  	if driverName == model.DATABASE_DRIVER_POSTGRES {
    55  		dsn = os.Getenv("TEST_DATABASE_POSTGRESQL_DSN")
    56  	} else {
    57  		dsn = os.Getenv("TEST_DATABASE_MYSQL_DSN")
    58  	}
    59  	cfg.SqlSettings = *storetest.MakeSqlSettings(driverName)
    60  	if dsn != "" {
    61  		cfg.SqlSettings.DataSource = &dsn
    62  	}
    63  	cfg.SqlSettings.DataSourceReplicas = []string{*cfg.SqlSettings.DataSource}
    64  	cfg.SqlSettings.DataSourceSearchReplicas = []string{*cfg.SqlSettings.DataSource}
    65  
    66  	t.Run("Read Replicas with no License", func(t *testing.T) {
    67  		s, err := NewServer(func(server *Server) error {
    68  			configStore, _ := config.NewMemoryStoreWithOptions(&config.MemoryStoreOptions{InitialConfig: cfg.Clone()})
    69  			server.configStore = configStore
    70  			return nil
    71  		})
    72  		require.NoError(t, err)
    73  		defer s.Shutdown()
    74  		require.Same(t, s.sqlStore.GetMaster(), s.sqlStore.GetReplica())
    75  		require.Len(t, s.Config().SqlSettings.DataSourceReplicas, 1)
    76  	})
    77  
    78  	t.Run("Read Replicas With License", func(t *testing.T) {
    79  		s, err := NewServer(func(server *Server) error {
    80  			configStore, _ := config.NewMemoryStoreWithOptions(&config.MemoryStoreOptions{InitialConfig: cfg.Clone()})
    81  			server.configStore = configStore
    82  			server.licenseValue.Store(model.NewTestLicense())
    83  			return nil
    84  		})
    85  		require.NoError(t, err)
    86  		defer s.Shutdown()
    87  		require.NotSame(t, s.sqlStore.GetMaster(), s.sqlStore.GetReplica())
    88  		require.Len(t, s.Config().SqlSettings.DataSourceReplicas, 1)
    89  	})
    90  
    91  	t.Run("Search Replicas with no License", func(t *testing.T) {
    92  		s, err := NewServer(func(server *Server) error {
    93  			configStore, _ := config.NewMemoryStoreWithOptions(&config.MemoryStoreOptions{InitialConfig: cfg.Clone()})
    94  			server.configStore = configStore
    95  			return nil
    96  		})
    97  		require.NoError(t, err)
    98  		defer s.Shutdown()
    99  		require.Same(t, s.sqlStore.GetMaster(), s.sqlStore.GetSearchReplica())
   100  		require.Len(t, s.Config().SqlSettings.DataSourceSearchReplicas, 1)
   101  	})
   102  
   103  	t.Run("Search Replicas With License", func(t *testing.T) {
   104  		s, err := NewServer(func(server *Server) error {
   105  			configStore, _ := config.NewMemoryStoreWithOptions(&config.MemoryStoreOptions{InitialConfig: cfg.Clone()})
   106  			server.configStore = configStore
   107  			server.licenseValue.Store(model.NewTestLicense())
   108  			return nil
   109  		})
   110  		require.NoError(t, err)
   111  		defer s.Shutdown()
   112  		require.NotSame(t, s.sqlStore.GetMaster(), s.sqlStore.GetSearchReplica())
   113  		require.Len(t, s.Config().SqlSettings.DataSourceSearchReplicas, 1)
   114  	})
   115  }
   116  
   117  func TestStartServerRateLimiterCriticalError(t *testing.T) {
   118  	// Attempt to use Rate Limiter with an invalid config
   119  	ms, err := config.NewMemoryStoreWithOptions(&config.MemoryStoreOptions{
   120  		SkipValidation: true,
   121  	})
   122  	require.NoError(t, err)
   123  
   124  	config := ms.Get()
   125  	*config.RateLimitSettings.Enable = true
   126  	*config.RateLimitSettings.MaxBurst = -100
   127  	_, err = ms.Set(config)
   128  	require.NoError(t, err)
   129  
   130  	s, err := NewServer(ConfigStore(ms))
   131  	require.NoError(t, err)
   132  
   133  	serverErr := s.Start()
   134  	s.Shutdown()
   135  	require.Error(t, serverErr)
   136  }
   137  
   138  func TestStartServerPortUnavailable(t *testing.T) {
   139  	s, err := NewServer()
   140  	require.NoError(t, err)
   141  
   142  	// Listen on the next available port
   143  	listener, err := net.Listen("tcp", ":0")
   144  	require.NoError(t, err)
   145  
   146  	// Attempt to listen on the port used above.
   147  	s.UpdateConfig(func(cfg *model.Config) {
   148  		*cfg.ServiceSettings.ListenAddress = listener.Addr().String()
   149  	})
   150  
   151  	serverErr := s.Start()
   152  	s.Shutdown()
   153  	require.Error(t, serverErr)
   154  }
   155  
   156  func TestStartServerTLSSuccess(t *testing.T) {
   157  	s, err := NewServer()
   158  	require.NoError(t, err)
   159  
   160  	testDir, _ := fileutils.FindDir("tests")
   161  	s.UpdateConfig(func(cfg *model.Config) {
   162  		*cfg.ServiceSettings.ListenAddress = ":0"
   163  		*cfg.ServiceSettings.ConnectionSecurity = "TLS"
   164  		*cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem")
   165  		*cfg.ServiceSettings.TLSCertFile = path.Join(testDir, "tls_test_cert.pem")
   166  	})
   167  	serverErr := s.Start()
   168  
   169  	tr := &http.Transport{
   170  		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   171  	}
   172  
   173  	client := &http.Client{Transport: tr}
   174  	checkEndpoint(t, client, "https://localhost:"+strconv.Itoa(s.ListenAddr.Port)+"/", http.StatusNotFound)
   175  
   176  	s.Shutdown()
   177  	require.NoError(t, serverErr)
   178  }
   179  
   180  func TestStartServerTLSVersion(t *testing.T) {
   181  	s, err := NewServer()
   182  	require.NoError(t, err)
   183  
   184  	testDir, _ := fileutils.FindDir("tests")
   185  	s.UpdateConfig(func(cfg *model.Config) {
   186  		*cfg.ServiceSettings.ListenAddress = ":0"
   187  		*cfg.ServiceSettings.ConnectionSecurity = "TLS"
   188  		*cfg.ServiceSettings.TLSMinVer = "1.2"
   189  		*cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem")
   190  		*cfg.ServiceSettings.TLSCertFile = path.Join(testDir, "tls_test_cert.pem")
   191  	})
   192  	serverErr := s.Start()
   193  
   194  	tr := &http.Transport{
   195  		TLSClientConfig: &tls.Config{
   196  			InsecureSkipVerify: true,
   197  			MaxVersion:         tls.VersionTLS11,
   198  		},
   199  	}
   200  
   201  	client := &http.Client{Transport: tr}
   202  	err = checkEndpoint(t, client, "https://localhost:"+strconv.Itoa(s.ListenAddr.Port)+"/", http.StatusNotFound)
   203  
   204  	if !strings.Contains(err.Error(), "remote error: tls: protocol version not supported") {
   205  		t.Errorf("Expected protocol version error, got %s", err)
   206  	}
   207  
   208  	client.Transport = &http.Transport{
   209  		TLSClientConfig: &tls.Config{
   210  			InsecureSkipVerify: true,
   211  		},
   212  	}
   213  
   214  	err = checkEndpoint(t, client, "https://localhost:"+strconv.Itoa(s.ListenAddr.Port)+"/", http.StatusNotFound)
   215  
   216  	if err != nil {
   217  		t.Errorf("Expected nil, got %s", err)
   218  	}
   219  
   220  	s.Shutdown()
   221  	require.NoError(t, serverErr)
   222  }
   223  
   224  func TestStartServerTLSOverwriteCipher(t *testing.T) {
   225  	s, err := NewServer()
   226  	require.NoError(t, err)
   227  
   228  	testDir, _ := fileutils.FindDir("tests")
   229  	s.UpdateConfig(func(cfg *model.Config) {
   230  		*cfg.ServiceSettings.ListenAddress = ":0"
   231  		*cfg.ServiceSettings.ConnectionSecurity = "TLS"
   232  		cfg.ServiceSettings.TLSOverwriteCiphers = []string{
   233  			"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
   234  			"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
   235  		}
   236  		*cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem")
   237  		*cfg.ServiceSettings.TLSCertFile = path.Join(testDir, "tls_test_cert.pem")
   238  	})
   239  	serverErr := s.Start()
   240  
   241  	tr := &http.Transport{
   242  		TLSClientConfig: &tls.Config{
   243  			InsecureSkipVerify: true,
   244  			CipherSuites: []uint16{
   245  				tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
   246  			},
   247  			MaxVersion: tls.VersionTLS12,
   248  		},
   249  	}
   250  
   251  	client := &http.Client{Transport: tr}
   252  	err = checkEndpoint(t, client, "https://localhost:"+strconv.Itoa(s.ListenAddr.Port)+"/", http.StatusNotFound)
   253  	require.Error(t, err, "Expected error due to Cipher mismatch")
   254  	if !strings.Contains(err.Error(), "remote error: tls: handshake failure") {
   255  		t.Errorf("Expected protocol version error, got %s", err)
   256  	}
   257  
   258  	client.Transport = &http.Transport{
   259  		TLSClientConfig: &tls.Config{
   260  			InsecureSkipVerify: true,
   261  			CipherSuites: []uint16{
   262  				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
   263  				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
   264  			},
   265  			MaxVersion: tls.VersionTLS12,
   266  		},
   267  	}
   268  
   269  	err = checkEndpoint(t, client, "https://localhost:"+strconv.Itoa(s.ListenAddr.Port)+"/", http.StatusNotFound)
   270  
   271  	if err != nil {
   272  		t.Errorf("Expected nil, got %s", err)
   273  	}
   274  
   275  	s.Shutdown()
   276  	require.NoError(t, serverErr)
   277  }
   278  
   279  func checkEndpoint(t *testing.T, client *http.Client, url string, expectedStatus int) error {
   280  	res, err := client.Get(url)
   281  
   282  	if err != nil {
   283  		return err
   284  	}
   285  
   286  	defer res.Body.Close()
   287  
   288  	if res.StatusCode != expectedStatus {
   289  		t.Errorf("Response code was %d; want %d", res.StatusCode, expectedStatus)
   290  	}
   291  
   292  	return nil
   293  }
   294  
   295  func TestPanicLog(t *testing.T) {
   296  	// Creating a temp file to collect logs
   297  	tmpfile, err := ioutil.TempFile("", "mlog")
   298  	if err != nil {
   299  		require.NoError(t, err)
   300  	}
   301  
   302  	defer func() {
   303  		require.NoError(t, tmpfile.Close())
   304  		require.NoError(t, os.Remove(tmpfile.Name()))
   305  	}()
   306  
   307  	// Creating logger to log to console and temp file
   308  	logger := mlog.NewLogger(&mlog.LoggerConfiguration{
   309  		EnableConsole: true,
   310  		ConsoleJson:   true,
   311  		EnableFile:    true,
   312  		FileLocation:  tmpfile.Name(),
   313  		FileLevel:     mlog.LevelInfo,
   314  	})
   315  
   316  	// Creating a server with logger
   317  	s, err := NewServer(SetLogger(logger))
   318  	require.NoError(t, err)
   319  
   320  	// Route for just panicing
   321  	s.Router.HandleFunc("/panic", func(writer http.ResponseWriter, request *http.Request) {
   322  		s.Log.Info("inside panic handler")
   323  		panic("log this panic")
   324  	})
   325  
   326  	testDir, _ := fileutils.FindDir("tests")
   327  	s.UpdateConfig(func(cfg *model.Config) {
   328  		*cfg.ServiceSettings.ListenAddress = ":0"
   329  		*cfg.ServiceSettings.ConnectionSecurity = "TLS"
   330  		*cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem")
   331  		*cfg.ServiceSettings.TLSCertFile = path.Join(testDir, "tls_test_cert.pem")
   332  	})
   333  	serverErr := s.Start()
   334  	require.NoError(t, serverErr)
   335  
   336  	// Calling panic route
   337  	tr := &http.Transport{
   338  		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   339  	}
   340  
   341  	client := &http.Client{Transport: tr}
   342  	client.Get("https://localhost:" + strconv.Itoa(s.ListenAddr.Port) + "/panic")
   343  
   344  	err = s.Shutdown()
   345  	require.NoError(t, err)
   346  
   347  	// Checking whether panic was logged
   348  	var panicLogged = false
   349  	var infoLogged = false
   350  
   351  	_, err = tmpfile.Seek(0, 0)
   352  	require.NoError(t, err)
   353  
   354  	scanner := bufio.NewScanner(tmpfile)
   355  	for scanner.Scan() {
   356  		if !infoLogged && strings.Contains(scanner.Text(), "inside panic handler") {
   357  			infoLogged = true
   358  		}
   359  		if strings.Contains(scanner.Text(), "log this panic") {
   360  			panicLogged = true
   361  			break
   362  		}
   363  	}
   364  
   365  	if !infoLogged {
   366  		t.Error("Info log line was supposed to be logged")
   367  	}
   368  
   369  	if !panicLogged {
   370  		t.Error("Panic was supposed to be logged")
   371  	}
   372  }
   373  
   374  func TestSentry(t *testing.T) {
   375  	if testing.Short() {
   376  		t.SkipNow()
   377  	}
   378  
   379  	client := &http.Client{Timeout: 5 * time.Second, Transport: &http.Transport{
   380  		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   381  	}}
   382  	data1 := make(chan bool, 1)
   383  
   384  	server1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   385  		t.Log("Received sentry request for some reason")
   386  		data1 <- true
   387  	}))
   388  	defer server1.Close()
   389  
   390  	// make sure we don't report anything when sentry is disabled
   391  	_, port, _ := net.SplitHostPort(server1.Listener.Addr().String())
   392  	SENTRY_DSN = fmt.Sprintf("http://test:test@localhost:%s/123", port)
   393  
   394  	testDir, _ := fileutils.FindDir("tests")
   395  	s, err := NewServer(func(server *Server) error {
   396  		configStore, _ := config.NewFileStore("config.json", true)
   397  		server.configStore = configStore
   398  		server.UpdateConfig(func(cfg *model.Config) {
   399  			*cfg.ServiceSettings.ListenAddress = ":0"
   400  			*cfg.LogSettings.EnableSentry = false
   401  			*cfg.ServiceSettings.ConnectionSecurity = "TLS"
   402  			*cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem")
   403  			*cfg.ServiceSettings.TLSCertFile = path.Join(testDir, "tls_test_cert.pem")
   404  		})
   405  		return nil
   406  	})
   407  
   408  	require.NoError(t, err)
   409  	// Route for just panicing
   410  	s.Router.HandleFunc("/panic", func(writer http.ResponseWriter, request *http.Request) {
   411  		panic("log this panic")
   412  	})
   413  
   414  	serverErr := s.Start()
   415  	require.NoError(t, serverErr)
   416  	defer s.Shutdown()
   417  	resp, err := client.Get("https://localhost:" + strconv.Itoa(s.ListenAddr.Port) + "/panic")
   418  	require.Nil(t, resp)
   419  	require.Error(t, err)
   420  
   421  	sentry.Flush(time.Second * 1)
   422  	select {
   423  	case <-data1:
   424  		require.Fail(t, "Sentry received a message, even though it's disabled!")
   425  	case <-time.After(time.Second * 1):
   426  		t.Log("Sentry request didn't arrive. Good!")
   427  	}
   428  
   429  	// check successful report
   430  	data2 := make(chan bool, 1)
   431  	server2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   432  		t.Log("Received sentry request!")
   433  		data2 <- true
   434  	}))
   435  	defer server2.Close()
   436  	_, port, _ = net.SplitHostPort(server2.Listener.Addr().String())
   437  	SENTRY_DSN = fmt.Sprintf("http://test:test@localhost:%s/123", port)
   438  	s2, err := NewServer(func(server *Server) error {
   439  		configStore, _ := config.NewFileStore("config.json", true)
   440  		server.configStore = configStore
   441  		server.UpdateConfig(func(cfg *model.Config) {
   442  			*cfg.ServiceSettings.ListenAddress = ":0"
   443  			*cfg.ServiceSettings.ConnectionSecurity = "TLS"
   444  			*cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem")
   445  			*cfg.ServiceSettings.TLSCertFile = path.Join(testDir, "tls_test_cert.pem")
   446  			*cfg.LogSettings.EnableSentry = true
   447  
   448  		})
   449  		return nil
   450  	})
   451  	require.NoError(t, err)
   452  	// Route for just panicing
   453  	s2.Router.HandleFunc("/panic", func(writer http.ResponseWriter, request *http.Request) {
   454  		panic("log this panic")
   455  	})
   456  
   457  	require.NoError(t, s2.Start())
   458  	defer s2.Shutdown()
   459  	resp, err = client.Get("https://localhost:" + strconv.Itoa(s2.ListenAddr.Port) + "/panic")
   460  	require.Nil(t, resp)
   461  	require.Error(t, err)
   462  	sentry.Flush(time.Second * 1)
   463  	select {
   464  	case <-data2:
   465  		t.Log("Sentry request arrived. Good!")
   466  	case <-time.After(time.Second * 2):
   467  		require.Fail(t, "Sentry report didn't arrive")
   468  	}
   469  }