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 }