2017-04-12 08:27:57 -04:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2015-06-14 23:53:32 -08:00
// See License.txt for license information.
2017-01-13 13:53:37 -05:00
package app
2015-06-14 23:53:32 -08:00
import (
2017-10-16 08:09:43 -07:00
"context"
2018-11-07 10:20:07 -08:00
"crypto/ecdsa"
2016-10-03 16:03:15 -04:00
"crypto/tls"
2018-03-23 12:33:50 -04:00
"fmt"
2016-10-03 16:03:15 -04:00
"net"
2016-09-26 12:56:12 -04:00
"net/http"
2018-07-30 17:21:57 -04:00
"net/url"
2018-01-30 10:12:42 -08:00
"os"
2016-09-26 12:56:12 -04:00
"strings"
2018-11-07 10:20:07 -08:00
"sync"
"sync/atomic"
2016-09-26 12:56:12 -04:00
"time"
2015-06-14 23:53:32 -08:00
"github.com/gorilla/mux"
2018-02-07 13:41:15 +05:30
"github.com/pkg/errors"
2018-07-26 08:31:22 -07:00
"github.com/rs/cors"
2018-11-07 10:20:07 -08:00
"github.com/throttled/throttled"
2018-01-30 10:12:42 -08:00
"golang.org/x/crypto/acme/autocert"
2017-05-24 07:55:52 -07:00
2019-02-12 08:37:54 -05:00
"github.com/mattermost/mattermost-server/config"
2018-11-28 10:56:21 -08:00
"github.com/mattermost/mattermost-server/einterfaces"
2018-11-07 10:20:07 -08:00
"github.com/mattermost/mattermost-server/jobs"
2018-04-27 12:49:45 -07:00
"github.com/mattermost/mattermost-server/mlog"
2017-09-06 23:05:10 -07:00
"github.com/mattermost/mattermost-server/model"
2018-11-07 10:20:07 -08:00
"github.com/mattermost/mattermost-server/plugin"
2018-11-28 10:56:21 -08:00
"github.com/mattermost/mattermost-server/services/httpservice"
2019-01-24 16:11:32 -04:00
"github.com/mattermost/mattermost-server/services/imageproxy"
2018-12-17 08:51:46 -08:00
"github.com/mattermost/mattermost-server/services/timezones"
2017-09-06 23:05:10 -07:00
"github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/utils"
2015-06-14 23:53:32 -08:00
)
2018-11-28 10:56:21 -08:00
var MaxNotificationsPerChannelDefault int64 = 1000000
2015-06-14 23:53:32 -08:00
type Server struct {
2017-01-13 13:53:37 -05:00
Store store . Store
WebSocketRouter * WebSocketRouter
2018-06-21 14:31:51 -04:00
// RootRouter is the starting point for all HTTP requests to the server.
RootRouter * mux . Router
// Router is the starting point for all web, api4 and ws requests to the server. It differs
// from RootRouter only if the SiteURL contains a /subpath.
Router * mux . Router
Server * http . Server
ListenAddr * net . TCPAddr
RateLimiter * RateLimiter
2017-10-16 14:02:33 -07:00
didFinishListen chan struct { }
2018-11-07 10:20:07 -08:00
goroutineCount int32
goroutineExitSignal chan struct { }
2018-11-20 08:52:51 -05:00
PluginsEnvironment * plugin . Environment
2018-11-07 10:20:07 -08:00
PluginConfigListenerId string
2018-11-20 08:52:51 -05:00
PluginsLock sync . RWMutex
2018-11-07 10:20:07 -08:00
EmailBatching * EmailBatchingJob
EmailRateLimiter * throttled . GCRARateLimiter
Hubs [ ] * Hub
HubsStopCheckingForDeadlock chan bool
PushNotificationsHub PushNotificationsHub
2018-12-17 08:51:46 -08:00
runjobs bool
Jobs * jobs . JobServer
2018-11-07 10:20:07 -08:00
clusterLeaderListeners sync . Map
licenseValue atomic . Value
clientLicenseValue atomic . Value
licenseListeners map [ string ] func ( )
2018-12-17 08:51:46 -08:00
timezones * timezones . Timezones
2018-11-07 10:20:07 -08:00
newStore func ( ) store . Store
htmlTemplateWatcher * utils . HTMLTemplateWatcher
sessionCache * utils . Cache
2018-12-17 18:16:57 -05:00
seenPendingPostIdsCache * utils . Cache
2018-11-07 10:20:07 -08:00
configListenerId string
licenseListenerId string
logListenerId string
clusterLeaderListenerId string
2019-02-12 14:19:01 -04:00
configStore config . Store
2018-11-07 10:20:07 -08:00
asymmetricSigningKey * ecdsa . PrivateKey
MM-10516: Added support for PostActions in ephemeral posts (#10258)
* Added support for PostActions in ephemeral posts
The general approach is that we take all the metadata that DoPostAction
needs to process client DoPostActionRequests, and store it in a
serialized, encrypted Cookie field, in the PostAction struct.
The client then must send it back, and it is then used to process
PostActions as a fallback top the metadata in the database.
This PR adds a new config setting, `ServiceSettings.ActionCookieSecret`.
In a cluster environment it must be the same for all instances.
- Added type PostActionCookie, and a Cookie string to PostAction.
- Added App.AddActionCookiesToPost.
- Use App.AddActionCookiesToPost in api4.createEphemeralPost,
App.SendEphemeralPost, App.UpdateEphemeralPost.
- Added App.DoPostActionWithCookie to process incoming requests with
cookies. For backward compatibility, it prefers the metadata in the
database; falls back to cookie.
- Added plugin.API.UpdateEphemeralPost and plugin.API.DeleteEphemeralPost.
- Added App.encryptActionCookie/App.decryptActionCookie.
* Style
* Fixed an unfortunate typo, tested with matterpoll
* minor PR feedback
* Fixed uninitialized Context
* Fixed another test failure
* Fixed permission check
* Added api test for DoPostActionWithCookie
* Replaced config.ActionCookieSecret with Server.PostActionCookieSecret
Modeled after AsymetricSigningKey
* style
* Set DeleteAt in DeleteEphemeralPost
* PR feedback
* Removed deadwood comment
* Added EXPERIMENTAL comment to the 2 APIs in question
2019-03-01 10:15:31 -08:00
postActionCookieSecret [ ] byte
2018-11-07 10:20:07 -08:00
pluginCommands [ ] * PluginCommand
pluginCommandsLock sync . RWMutex
clientConfig map [ string ] string
clientConfigHash string
limitedClientConfig map [ string ] string
diagnosticId string
phase2PermissionsMigrationComplete bool
2018-11-28 10:56:21 -08:00
HTTPService httpservice . HTTPService
2019-01-24 16:11:32 -04:00
ImageProxy * imageproxy . ImageProxy
2018-11-28 10:56:21 -08:00
Log * mlog . Logger
2019-01-15 09:09:25 -08:00
joinCluster bool
startMetrics bool
startElasticsearch bool
2018-11-28 10:56:21 -08:00
AccountMigration einterfaces . AccountMigrationInterface
Cluster einterfaces . ClusterInterface
Compliance einterfaces . ComplianceInterface
DataRetention einterfaces . DataRetentionInterface
Elasticsearch einterfaces . ElasticsearchInterface
Ldap einterfaces . LdapInterface
MessageExport einterfaces . MessageExportInterface
Metrics einterfaces . MetricsInterface
Saml einterfaces . SamlInterface
}
func NewServer ( options ... Option ) ( * Server , error ) {
rootRouter := mux . NewRouter ( )
s := & Server {
2018-12-17 18:16:57 -05:00
goroutineExitSignal : make ( chan struct { } , 1 ) ,
RootRouter : rootRouter ,
licenseListeners : map [ string ] func ( ) { } ,
sessionCache : utils . NewLru ( model . SESSION_CACHE_SIZE ) ,
seenPendingPostIdsCache : utils . NewLru ( PENDING_POST_IDS_CACHE_SIZE ) ,
clientConfig : make ( map [ string ] string ) ,
2018-11-28 10:56:21 -08:00
}
for _ , option := range options {
2019-02-12 08:37:54 -05:00
if err := option ( s ) ; err != nil {
return nil , errors . Wrap ( err , "failed to apply option" )
}
2018-11-28 10:56:21 -08:00
}
2019-02-12 14:19:01 -04:00
if s . configStore == nil {
configStore , err := config . NewFileStore ( "config.json" , true )
if err != nil {
return nil , errors . Wrap ( err , "failed to load config" )
}
2018-11-28 10:56:21 -08:00
2019-02-12 14:19:01 -04:00
s . configStore = configStore
}
2018-11-28 10:56:21 -08:00
2019-02-14 23:22:11 +05:30
if s . Log == nil {
s . Log = mlog . NewLogger ( utils . MloggerConfigFromLoggerConfig ( & s . Config ( ) . LogSettings ) )
}
2018-11-28 10:56:21 -08:00
// Redirect default golang logger to this logger
mlog . RedirectStdLog ( s . Log )
// Use this app logger as the global logger (eventually remove all instances of global logging)
mlog . InitGlobalLogger ( s . Log )
s . logListenerId = s . AddConfigListener ( func ( _ , after * model . Config ) {
s . Log . ChangeLevels ( utils . MloggerConfigFromLoggerConfig ( & after . LogSettings ) )
} )
2018-11-30 08:07:08 -08:00
s . HTTPService = httpservice . MakeHTTPService ( s . FakeApp ( ) )
2019-04-24 11:55:37 -04:00
s . ImageProxy = imageproxy . MakeImageProxy ( s , s . HTTPService , s . Log )
2019-01-24 16:11:32 -04:00
2019-03-12 17:50:48 +00:00
if err := utils . TranslationsPreInit ( ) ; err != nil {
return nil , errors . Wrapf ( err , "unable to load Mattermost translation files" )
2018-12-17 08:51:46 -08:00
}
2018-11-28 10:56:21 -08:00
err := s . RunOldAppInitalization ( )
if err != nil {
return nil , err
}
2018-12-17 08:51:46 -08:00
model . AppErrorInit ( utils . T )
2019-02-25 12:07:45 -04:00
s . timezones = timezones . New ( )
2018-12-17 08:51:46 -08:00
2018-11-28 10:56:21 -08:00
// Start email batching because it's not like the other jobs
s . InitEmailBatching ( )
s . AddConfigListener ( func ( _ , _ * model . Config ) {
s . InitEmailBatching ( )
} )
mlog . Info ( fmt . Sprintf ( "Current version is %v (%v/%v/%v/%v)" , model . CurrentVersion , model . BuildNumber , model . BuildDate , model . BuildHash , model . BuildHashEnterprise ) )
mlog . Info ( fmt . Sprintf ( "Enterprise Enabled: %v" , model . BuildEnterpriseReady ) )
pwd , _ := os . Getwd ( )
mlog . Info ( fmt . Sprintf ( "Current working directory is %v" , pwd ) )
2019-02-12 14:19:01 -04:00
mlog . Info ( "Loaded config" , mlog . String ( "source" , s . configStore . String ( ) ) )
2018-11-28 10:56:21 -08:00
license := s . License ( )
if license == nil && len ( s . Config ( ) . SqlSettings . DataSourceReplicas ) > 1 {
mlog . Warn ( "More than 1 read replica functionality disabled by current license. Please contact your system administrator about upgrading your enterprise license." )
s . UpdateConfig ( func ( cfg * model . Config ) {
cfg . SqlSettings . DataSourceReplicas = cfg . SqlSettings . DataSourceReplicas [ : 1 ]
} )
}
if license == nil {
s . UpdateConfig ( func ( cfg * model . Config ) {
cfg . TeamSettings . MaxNotificationsPerChannel = & MaxNotificationsPerChannelDefault
} )
}
s . ReloadConfig ( )
// Enable developer settings if this is a "dev" build
if model . BuildNumber == "dev" {
s . UpdateConfig ( func ( cfg * model . Config ) { * cfg . ServiceSettings . EnableDeveloper = true } )
}
if result := <- s . Store . Status ( ) . ResetAll ( ) ; result . Err != nil {
mlog . Error ( fmt . Sprint ( "Error to reset the server status." , result . Err . Error ( ) ) )
}
2019-01-15 09:09:25 -08:00
if s . joinCluster && s . Cluster != nil {
2018-12-17 08:51:46 -08:00
s . FakeApp ( ) . RegisterAllClusterMessageHandlers ( )
s . Cluster . StartInterNodeCommunication ( )
}
2019-01-15 09:09:25 -08:00
if s . startMetrics && s . Metrics != nil {
2018-12-17 08:51:46 -08:00
s . Metrics . StartServer ( )
}
2019-01-15 09:09:25 -08:00
if s . startElasticsearch && s . Elasticsearch != nil {
2018-12-17 08:51:46 -08:00
s . StartElasticsearch ( )
}
s . initJobs ( )
if s . runjobs {
s . Go ( func ( ) {
runSecurityJob ( s )
} )
s . Go ( func ( ) {
runDiagnosticsJob ( s )
} )
s . Go ( func ( ) {
runSessionCleanupJob ( s )
} )
s . Go ( func ( ) {
runTokenCleanupJob ( s )
} )
s . Go ( func ( ) {
runCommandWebhookCleanupJob ( s )
} )
if complianceI := s . Compliance ; complianceI != nil {
complianceI . StartComplianceDailyJob ( )
}
if * s . Config ( ) . JobSettings . RunJobs && s . Jobs != nil {
s . Jobs . StartWorkers ( )
}
if * s . Config ( ) . JobSettings . RunScheduler && s . Jobs != nil {
s . Jobs . StartSchedulers ( )
}
}
2018-11-28 10:56:21 -08:00
return s , nil
}
// Global app opptions that should be applied to apps created by this server
func ( s * Server ) AppOptions ( ) [ ] AppOption {
return [ ] AppOption {
ServerConnector ( s ) ,
}
}
const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time . Second
func ( s * Server ) StopHTTPServer ( ) {
if s . Server != nil {
ctx , cancel := context . WithTimeout ( context . Background ( ) , TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN )
defer cancel ( )
didShutdown := false
for s . didFinishListen != nil && ! didShutdown {
if err := s . Server . Shutdown ( ctx ) ; err != nil {
mlog . Warn ( err . Error ( ) )
}
timer := time . NewTimer ( time . Millisecond * 50 )
select {
case <- s . didFinishListen :
didShutdown = true
case <- timer . C :
}
timer . Stop ( )
}
s . Server . Close ( )
s . Server = nil
}
}
func ( s * Server ) Shutdown ( ) error {
mlog . Info ( "Stopping Server..." )
s . RunOldAppShutdown ( )
s . StopHTTPServer ( )
s . WaitForGoroutines ( )
if s . Store != nil {
s . Store . Close ( )
}
if s . htmlTemplateWatcher != nil {
s . htmlTemplateWatcher . Close ( )
}
s . RemoveConfigListener ( s . configListenerId )
s . RemoveConfigListener ( s . logListenerId )
2019-02-12 14:19:01 -04:00
s . configStore . Close ( )
2018-11-28 10:56:21 -08:00
2018-12-17 08:51:46 -08:00
if s . Cluster != nil {
s . Cluster . StopInterNodeCommunication ( )
}
if s . Metrics != nil {
s . Metrics . StopServer ( )
}
if s . Jobs != nil && s . runjobs {
s . Jobs . StopWorkers ( )
s . Jobs . StopSchedulers ( )
}
2018-11-28 10:56:21 -08:00
mlog . Info ( "Server stopped" )
return nil
}
2018-11-07 10:20:07 -08:00
// Go creates a goroutine, but maintains a record of it to ensure that execution completes before
2018-11-28 10:56:21 -08:00
// the server is shutdown.
2018-11-07 10:20:07 -08:00
func ( s * Server ) Go ( f func ( ) ) {
atomic . AddInt32 ( & s . goroutineCount , 1 )
go func ( ) {
f ( )
atomic . AddInt32 ( & s . goroutineCount , - 1 )
select {
case s . goroutineExitSignal <- struct { } { } :
default :
}
} ( )
}
// WaitForGoroutines blocks until all goroutines created by App.Go exit.
func ( s * Server ) WaitForGoroutines ( ) {
for atomic . LoadInt32 ( & s . goroutineCount ) != 0 {
<- s . goroutineExitSignal
}
2017-01-13 13:53:37 -05:00
}
2018-10-16 16:51:46 +02:00
var corsAllowedMethods = [ ] string {
2017-01-13 13:53:37 -05:00
"POST" ,
"GET" ,
"OPTIONS" ,
"PUT" ,
"PATCH" ,
"DELETE" ,
2015-06-14 23:53:32 -08:00
}
2018-06-29 17:17:35 -04:00
// golang.org/x/crypto/acme/autocert/autocert.go
func handleHTTPRedirect ( w http . ResponseWriter , r * http . Request ) {
if r . Method != "GET" && r . Method != "HEAD" {
http . Error ( w , "Use HTTPS" , http . StatusBadRequest )
return
2016-10-03 16:03:15 -04:00
}
2018-06-29 17:17:35 -04:00
target := "https://" + stripPort ( r . Host ) + r . URL . RequestURI ( )
http . Redirect ( w , r , target , http . StatusFound )
}
2016-10-03 16:03:15 -04:00
2018-06-29 17:17:35 -04:00
// golang.org/x/crypto/acme/autocert/autocert.go
func stripPort ( hostport string ) string {
host , _ , err := net . SplitHostPort ( hostport )
if err != nil {
return hostport
}
return net . JoinHostPort ( host , "443" )
2016-10-03 16:03:15 -04:00
}
2018-12-17 08:51:46 -08:00
func ( s * Server ) Start ( ) error {
2018-04-27 12:49:45 -07:00
mlog . Info ( "Starting Server..." )
2015-07-29 01:26:10 -08:00
2018-12-17 08:51:46 -08:00
var handler http . Handler = s . RootRouter
if allowedOrigins := * s . Config ( ) . ServiceSettings . AllowCorsFrom ; allowedOrigins != "" {
exposedCorsHeaders := * s . Config ( ) . ServiceSettings . CorsExposedHeaders
allowCredentials := * s . Config ( ) . ServiceSettings . CorsAllowCredentials
debug := * s . Config ( ) . ServiceSettings . CorsDebug
2018-07-26 08:31:22 -07:00
corsWrapper := cors . New ( cors . Options {
AllowedOrigins : strings . Fields ( allowedOrigins ) ,
AllowedMethods : corsAllowedMethods ,
AllowedHeaders : [ ] string { "*" } ,
ExposedHeaders : strings . Fields ( exposedCorsHeaders ) ,
MaxAge : 86400 ,
AllowCredentials : allowCredentials ,
Debug : debug ,
} )
// If we have debugging of CORS turned on then forward messages to logs
if debug {
2018-12-17 08:51:46 -08:00
corsWrapper . Log = s . Log . StdLog ( mlog . String ( "source" , "cors" ) )
2018-07-26 08:31:22 -07:00
}
handler = corsWrapper . Handler ( handler )
}
2015-07-29 01:26:10 -08:00
2018-12-17 08:51:46 -08:00
if * s . Config ( ) . RateLimitSettings . Enable {
2018-04-27 12:49:45 -07:00
mlog . Info ( "RateLimiter is enabled" )
2015-07-29 01:26:10 -08:00
2018-12-17 08:51:46 -08:00
rateLimiter , err := NewRateLimiter ( & s . Config ( ) . RateLimitSettings )
2018-02-06 10:57:34 +05:30
if err != nil {
2018-02-07 13:41:15 +05:30
return err
2018-02-06 10:57:34 +05:30
}
2015-07-29 01:26:10 -08:00
2018-12-17 08:51:46 -08:00
s . RateLimiter = rateLimiter
2018-02-06 10:57:34 +05:30
handler = rateLimiter . RateLimitHandler ( handler )
2015-07-29 01:26:10 -08:00
}
2019-03-11 22:32:34 +05:30
// Creating a logger for logging errors from http.Server at error level
errStdLog , err := s . Log . StdLogAt ( mlog . LevelError , mlog . String ( "source" , "httpserver" ) )
if err != nil {
return err
}
2018-12-17 08:51:46 -08:00
s . Server = & http . Server {
2019-03-11 22:32:34 +05:30
Handler : handler ,
2018-12-17 08:51:46 -08:00
ReadTimeout : time . Duration ( * s . Config ( ) . ServiceSettings . ReadTimeout ) * time . Second ,
WriteTimeout : time . Duration ( * s . Config ( ) . ServiceSettings . WriteTimeout ) * time . Second ,
2019-03-11 22:32:34 +05:30
ErrorLog : errStdLog ,
2016-10-03 16:03:15 -04:00
}
2017-10-16 08:09:43 -07:00
2018-12-17 08:51:46 -08:00
addr := * s . Config ( ) . ServiceSettings . ListenAddress
2017-10-16 08:09:43 -07:00
if addr == "" {
2018-12-17 08:51:46 -08:00
if * s . Config ( ) . ServiceSettings . ConnectionSecurity == model . CONN_SECURITY_TLS {
2017-10-16 08:09:43 -07:00
addr = ":https"
} else {
addr = ":http"
}
}
listener , err := net . Listen ( "tcp" , addr )
if err != nil {
2018-02-07 13:41:15 +05:30
errors . Wrapf ( err , utils . T ( "api.server.start_server.starting.critical" ) , err )
return err
2017-10-16 08:09:43 -07:00
}
2018-12-17 08:51:46 -08:00
s . ListenAddr = listener . Addr ( ) . ( * net . TCPAddr )
2017-10-16 08:09:43 -07:00
2018-04-27 12:49:45 -07:00
mlog . Info ( fmt . Sprintf ( "Server is listening on %v" , listener . Addr ( ) . String ( ) ) )
2016-10-03 16:03:15 -04:00
2018-01-30 10:12:42 -08:00
// Migration from old let's encrypt library
2018-12-17 08:51:46 -08:00
if * s . Config ( ) . ServiceSettings . UseLetsEncrypt {
if stat , err := os . Stat ( * s . Config ( ) . ServiceSettings . LetsEncryptCertificateCacheFile ) ; err == nil && ! stat . IsDir ( ) {
os . Remove ( * s . Config ( ) . ServiceSettings . LetsEncryptCertificateCacheFile )
2018-01-30 10:12:42 -08:00
}
}
2016-10-03 16:03:15 -04:00
2018-01-30 10:12:42 -08:00
m := & autocert . Manager {
2018-12-17 08:51:46 -08:00
Cache : autocert . DirCache ( * s . Config ( ) . ServiceSettings . LetsEncryptCertificateCacheFile ) ,
2018-01-30 10:12:42 -08:00
Prompt : autocert . AcceptTOS ,
}
2018-12-17 08:51:46 -08:00
if * s . Config ( ) . ServiceSettings . Forward80To443 {
2018-03-23 12:33:50 -04:00
if host , port , err := net . SplitHostPort ( addr ) ; err != nil {
2018-04-27 12:49:45 -07:00
mlog . Error ( "Unable to setup forwarding: " + err . Error ( ) )
2018-03-23 12:33:50 -04:00
} else if port != "443" {
return fmt . Errorf ( utils . T ( "api.server.start_server.forward80to443.enabled_but_listening_on_wrong_port" ) , port )
2018-01-30 10:12:42 -08:00
} else {
2018-02-23 15:33:36 -08:00
httpListenAddress := net . JoinHostPort ( host , "http" )
2018-12-17 08:51:46 -08:00
if * s . Config ( ) . ServiceSettings . UseLetsEncrypt {
2018-04-27 12:49:45 -07:00
server := & http . Server {
Addr : httpListenAddress ,
Handler : m . HTTPHandler ( nil ) ,
2018-12-17 08:51:46 -08:00
ErrorLog : s . Log . StdLog ( mlog . String ( "source" , "le_forwarder_server" ) ) ,
2018-04-27 12:49:45 -07:00
}
go server . ListenAndServe ( )
2018-02-23 15:33:36 -08:00
} else {
go func ( ) {
redirectListener , err := net . Listen ( "tcp" , httpListenAddress )
if err != nil {
2018-04-27 12:49:45 -07:00
mlog . Error ( "Unable to setup forwarding: " + err . Error ( ) )
2018-02-23 15:33:36 -08:00
return
}
defer redirectListener . Close ( )
2018-04-27 12:49:45 -07:00
server := & http . Server {
2018-06-29 17:17:35 -04:00
Handler : http . HandlerFunc ( handleHTTPRedirect ) ,
2018-12-17 08:51:46 -08:00
ErrorLog : s . Log . StdLog ( mlog . String ( "source" , "forwarder_server" ) ) ,
2018-04-27 12:49:45 -07:00
}
server . Serve ( redirectListener )
2018-02-23 15:33:36 -08:00
} ( )
}
2018-01-30 10:12:42 -08:00
}
2018-12-17 08:51:46 -08:00
} else if * s . Config ( ) . ServiceSettings . UseLetsEncrypt {
2018-03-23 12:33:50 -04:00
return errors . New ( utils . T ( "api.server.start_server.forward80to443.disabled_while_using_lets_encrypt" ) )
2016-10-03 16:03:15 -04:00
}
2018-12-17 08:51:46 -08:00
s . didFinishListen = make ( chan struct { } )
2015-06-14 23:53:32 -08:00
go func ( ) {
2016-10-03 16:03:15 -04:00
var err error
2018-12-17 08:51:46 -08:00
if * s . Config ( ) . ServiceSettings . ConnectionSecurity == model . CONN_SECURITY_TLS {
2016-10-03 16:03:15 -04:00
2018-10-16 16:51:46 +02:00
tlsConfig := & tls . Config {
PreferServerCipherSuites : true ,
CurvePreferences : [ ] tls . CurveID { tls . CurveP521 , tls . CurveP384 , tls . CurveP256 } ,
}
2018-12-17 08:51:46 -08:00
switch * s . Config ( ) . ServiceSettings . TLSMinVer {
2018-10-16 16:51:46 +02:00
case "1.0" :
tlsConfig . MinVersion = tls . VersionTLS10
case "1.1" :
tlsConfig . MinVersion = tls . VersionTLS11
default :
tlsConfig . MinVersion = tls . VersionTLS12
}
defaultCiphers := [ ] uint16 {
tls . TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ,
tls . TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 ,
tls . TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ,
tls . TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,
tls . TLS_RSA_WITH_AES_128_GCM_SHA256 ,
tls . TLS_RSA_WITH_AES_256_GCM_SHA384 ,
}
2018-12-17 08:51:46 -08:00
if len ( s . Config ( ) . ServiceSettings . TLSOverwriteCiphers ) == 0 {
2018-10-16 16:51:46 +02:00
tlsConfig . CipherSuites = defaultCiphers
} else {
var cipherSuites [ ] uint16
2018-12-17 08:51:46 -08:00
for _ , cipher := range s . Config ( ) . ServiceSettings . TLSOverwriteCiphers {
2018-10-16 16:51:46 +02:00
value , ok := model . ServerTLSSupportedCiphers [ cipher ]
if ! ok {
mlog . Warn ( "Unsupported cipher passed" , mlog . String ( "cipher" , cipher ) )
continue
}
cipherSuites = append ( cipherSuites , value )
2016-10-03 16:03:15 -04:00
}
2018-10-16 16:51:46 +02:00
if len ( cipherSuites ) == 0 {
mlog . Warn ( "No supported ciphers passed, fallback to default cipher suite" )
cipherSuites = defaultCiphers
}
tlsConfig . CipherSuites = cipherSuites
}
certFile := ""
keyFile := ""
2016-10-03 16:03:15 -04:00
2018-12-17 08:51:46 -08:00
if * s . Config ( ) . ServiceSettings . UseLetsEncrypt {
2018-10-16 16:51:46 +02:00
tlsConfig . GetCertificate = m . GetCertificate
tlsConfig . NextProtos = append ( tlsConfig . NextProtos , "h2" )
2016-10-03 16:03:15 -04:00
} else {
2018-12-17 08:51:46 -08:00
certFile = * s . Config ( ) . ServiceSettings . TLSCertFile
keyFile = * s . Config ( ) . ServiceSettings . TLSKeyFile
2016-10-03 16:03:15 -04:00
}
2018-10-16 16:51:46 +02:00
2018-12-17 08:51:46 -08:00
s . Server . TLSConfig = tlsConfig
err = s . Server . ServeTLS ( listener , certFile , keyFile )
2016-10-03 16:03:15 -04:00
} else {
2018-12-17 08:51:46 -08:00
err = s . Server . Serve ( listener )
2016-10-03 16:03:15 -04:00
}
2018-10-16 16:51:46 +02:00
2017-10-16 08:09:43 -07:00
if err != nil && err != http . ErrServerClosed {
2018-04-27 12:49:45 -07:00
mlog . Critical ( fmt . Sprintf ( "Error starting server, err:%v" , err ) )
2015-06-14 23:53:32 -08:00
time . Sleep ( time . Second )
}
2018-10-16 16:51:46 +02:00
2018-12-17 08:51:46 -08:00
close ( s . didFinishListen )
2015-06-14 23:53:32 -08:00
} ( )
2018-02-07 13:41:15 +05:30
return nil
2015-06-14 23:53:32 -08:00
}
2017-10-09 14:59:48 -07:00
2017-11-22 15:58:03 -06:00
func ( a * App ) OriginChecker ( ) func ( * http . Request ) bool {
if allowed := * a . Config ( ) . ServiceSettings . AllowCorsFrom ; allowed != "" {
2018-07-30 17:21:57 -04:00
if allowed != "*" {
siteURL , err := url . Parse ( * a . Config ( ) . ServiceSettings . SiteURL )
if err == nil {
siteURL . Path = ""
allowed += " " + siteURL . String ( )
}
}
2017-11-22 15:58:03 -06:00
return utils . OriginChecker ( allowed )
}
return nil
}
2018-12-17 08:51:46 -08:00
func runSecurityJob ( s * Server ) {
doSecurity ( s )
model . CreateRecurringTask ( "Security" , func ( ) {
doSecurity ( s )
} , time . Hour * 4 )
}
func runDiagnosticsJob ( s * Server ) {
doDiagnostics ( s )
model . CreateRecurringTask ( "Diagnostics" , func ( ) {
doDiagnostics ( s )
} , time . Hour * 24 )
}
func runTokenCleanupJob ( s * Server ) {
doTokenCleanup ( s )
model . CreateRecurringTask ( "Token Cleanup" , func ( ) {
doTokenCleanup ( s )
} , time . Hour * 1 )
}
func runCommandWebhookCleanupJob ( s * Server ) {
doCommandWebhookCleanup ( s )
model . CreateRecurringTask ( "Command Hook Cleanup" , func ( ) {
doCommandWebhookCleanup ( s )
} , time . Hour * 1 )
}
func runSessionCleanupJob ( s * Server ) {
doSessionCleanup ( s )
model . CreateRecurringTask ( "Session Cleanup" , func ( ) {
doSessionCleanup ( s )
} , time . Hour * 24 )
}
func doSecurity ( s * Server ) {
s . DoSecurityUpdateCheck ( )
}
func doDiagnostics ( s * Server ) {
if * s . Config ( ) . LogSettings . EnableDiagnostics {
s . FakeApp ( ) . SendDailyDiagnostics ( )
}
}
func doTokenCleanup ( s * Server ) {
s . Store . Token ( ) . Cleanup ( )
}
func doCommandWebhookCleanup ( s * Server ) {
s . Store . CommandWebhook ( ) . Cleanup ( )
}
const (
SESSIONS_CLEANUP_BATCH_SIZE = 1000
)
func doSessionCleanup ( s * Server ) {
s . Store . Session ( ) . Cleanup ( model . GetMillis ( ) , SESSIONS_CLEANUP_BATCH_SIZE )
}
func ( s * Server ) StartElasticsearch ( ) {
s . Go ( func ( ) {
if err := s . Elasticsearch . Start ( ) ; err != nil {
s . Log . Error ( err . Error ( ) )
}
} )
s . AddConfigListener ( func ( oldConfig * model . Config , newConfig * model . Config ) {
if ! * oldConfig . ElasticsearchSettings . EnableIndexing && * newConfig . ElasticsearchSettings . EnableIndexing {
s . Go ( func ( ) {
if err := s . Elasticsearch . Start ( ) ; err != nil {
mlog . Error ( err . Error ( ) )
}
} )
} else if * oldConfig . ElasticsearchSettings . EnableIndexing && ! * newConfig . ElasticsearchSettings . EnableIndexing {
s . Go ( func ( ) {
if err := s . Elasticsearch . Stop ( ) ; err != nil {
mlog . Error ( err . Error ( ) )
}
} )
} else if * oldConfig . ElasticsearchSettings . Password != * newConfig . ElasticsearchSettings . Password || * oldConfig . ElasticsearchSettings . Username != * newConfig . ElasticsearchSettings . Username || * oldConfig . ElasticsearchSettings . ConnectionUrl != * newConfig . ElasticsearchSettings . ConnectionUrl || * oldConfig . ElasticsearchSettings . Sniff != * newConfig . ElasticsearchSettings . Sniff {
s . Go ( func ( ) {
if * oldConfig . ElasticsearchSettings . EnableIndexing {
if err := s . Elasticsearch . Stop ( ) ; err != nil {
mlog . Error ( err . Error ( ) )
}
if err := s . Elasticsearch . Start ( ) ; err != nil {
mlog . Error ( err . Error ( ) )
}
}
} )
}
} )
s . AddLicenseListener ( func ( ) {
if s . License ( ) != nil {
s . Go ( func ( ) {
if err := s . Elasticsearch . Start ( ) ; err != nil {
mlog . Error ( err . Error ( ) )
}
} )
} else {
s . Go ( func ( ) {
if err := s . Elasticsearch . Stop ( ) ; err != nil {
mlog . Error ( err . Error ( ) )
}
} )
}
} )
}