github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/app/config.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 "crypto/ecdsa" 8 "crypto/elliptic" 9 "crypto/md5" 10 "crypto/rand" 11 "crypto/x509" 12 "encoding/base64" 13 "encoding/json" 14 "fmt" 15 "net/http" 16 "net/url" 17 "runtime/debug" 18 "strconv" 19 "time" 20 21 "github.com/pkg/errors" 22 23 "github.com/vnforks/kid/v5/config" 24 "github.com/vnforks/kid/v5/mlog" 25 "github.com/vnforks/kid/v5/model" 26 "github.com/vnforks/kid/v5/utils" 27 ) 28 29 const ( 30 ERROR_TERMS_OF_SERVICE_NO_ROWS_FOUND = "store.sql_terms_of_service_store.get.no_rows.app_error" 31 ) 32 33 func (s *Server) Config() *model.Config { 34 return s.configStore.Get() 35 } 36 37 func (a *App) Config() *model.Config { 38 return a.Srv().Config() 39 } 40 41 func (s *Server) EnvironmentConfig() map[string]interface{} { 42 return s.configStore.GetEnvironmentOverrides() 43 } 44 45 func (a *App) EnvironmentConfig() map[string]interface{} { 46 return a.Srv().EnvironmentConfig() 47 } 48 49 func (s *Server) UpdateConfig(f func(*model.Config)) { 50 old := s.Config() 51 updated := old.Clone() 52 f(updated) 53 if _, err := s.configStore.Set(updated); err != nil { 54 mlog.Error("Failed to update config", mlog.Err(err)) 55 } 56 } 57 58 func (a *App) UpdateConfig(f func(*model.Config)) { 59 a.Srv().UpdateConfig(f) 60 } 61 62 func (s *Server) ReloadConfig() error { 63 debug.FreeOSMemory() 64 if err := s.configStore.Load(); err != nil { 65 return err 66 } 67 return nil 68 } 69 70 func (a *App) ReloadConfig() error { 71 return a.Srv().ReloadConfig() 72 } 73 74 func (a *App) ClientConfig() map[string]string { 75 return a.Srv().clientConfig 76 } 77 78 func (a *App) ClientConfigHash() string { 79 return a.Srv().clientConfigHash 80 } 81 82 func (a *App) LimitedClientConfig() map[string]string { 83 return a.Srv().limitedClientConfig 84 } 85 86 // Registers a function with a given listener to be called when the config is reloaded and may have changed. The function 87 // will be called with two arguments: the old config and the new config. AddConfigListener returns a unique ID 88 // for the listener that can later be used to remove it. 89 func (s *Server) AddConfigListener(listener func(*model.Config, *model.Config)) string { 90 return s.configStore.AddListener(listener) 91 } 92 93 func (a *App) AddConfigListener(listener func(*model.Config, *model.Config)) string { 94 return a.Srv().AddConfigListener(listener) 95 } 96 97 // Removes a listener function by the unique ID returned when AddConfigListener was called 98 func (s *Server) RemoveConfigListener(id string) { 99 s.configStore.RemoveListener(id) 100 } 101 102 func (a *App) RemoveConfigListener(id string) { 103 a.Srv().RemoveConfigListener(id) 104 } 105 106 // ensurePostActionCookieSecret ensures that the key for encrypting PostActionCookie exists 107 // and future calls to PostActionCookieSecret will always return a valid key, same on all 108 // servers in the cluster 109 func (a *App) ensurePostActionCookieSecret() error { 110 if a.Srv().postActionCookieSecret != nil { 111 return nil 112 } 113 114 var secret *model.SystemPostActionCookieSecret 115 116 value, err := a.Srv().Store.System().GetByName(model.SYSTEM_POST_ACTION_COOKIE_SECRET) 117 if err == nil { 118 if err := json.Unmarshal([]byte(value.Value), &secret); err != nil { 119 return err 120 } 121 } 122 123 // If we don't already have a key, try to generate one. 124 if secret == nil { 125 newSecret := &model.SystemPostActionCookieSecret{ 126 Secret: make([]byte, 32), 127 } 128 _, err := rand.Reader.Read(newSecret.Secret) 129 if err != nil { 130 return err 131 } 132 133 system := &model.System{ 134 Name: model.SYSTEM_POST_ACTION_COOKIE_SECRET, 135 } 136 v, err := json.Marshal(newSecret) 137 if err != nil { 138 return err 139 } 140 system.Value = string(v) 141 // If we were able to save the key, use it, otherwise log the error. 142 if appErr := a.Srv().Store.System().Save(system); appErr != nil { 143 mlog.Error("Failed to save PostActionCookieSecret", mlog.Err(appErr)) 144 } else { 145 secret = newSecret 146 } 147 } 148 149 // If we weren't able to save a new key above, another server must have beat us to it. Get the 150 // key from the database, and if that fails, error out. 151 if secret == nil { 152 value, err := a.Srv().Store.System().GetByName(model.SYSTEM_POST_ACTION_COOKIE_SECRET) 153 if err != nil { 154 return err 155 } 156 157 if err := json.Unmarshal([]byte(value.Value), &secret); err != nil { 158 return err 159 } 160 } 161 162 a.Srv().postActionCookieSecret = secret.Secret 163 return nil 164 } 165 166 // EnsureAsymmetricSigningKey ensures that an asymmetric signing key exists and future calls to 167 // AsymmetricSigningKey will always return a valid signing key. 168 func (a *App) ensureAsymmetricSigningKey() error { 169 if a.Srv().asymmetricSigningKey != nil { 170 return nil 171 } 172 173 var key *model.SystemAsymmetricSigningKey 174 175 value, err := a.Srv().Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY) 176 if err == nil { 177 if err := json.Unmarshal([]byte(value.Value), &key); err != nil { 178 return err 179 } 180 } 181 182 // If we don't already have a key, try to generate one. 183 if key == nil { 184 newECDSAKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 185 if err != nil { 186 return err 187 } 188 newKey := &model.SystemAsymmetricSigningKey{ 189 ECDSAKey: &model.SystemECDSAKey{ 190 Curve: "P-256", 191 X: newECDSAKey.X, 192 Y: newECDSAKey.Y, 193 D: newECDSAKey.D, 194 }, 195 } 196 system := &model.System{ 197 Name: model.SYSTEM_ASYMMETRIC_SIGNING_KEY, 198 } 199 v, err := json.Marshal(newKey) 200 if err != nil { 201 return err 202 } 203 system.Value = string(v) 204 // If we were able to save the key, use it, otherwise log the error. 205 if appErr := a.Srv().Store.System().Save(system); appErr != nil { 206 mlog.Error("Failed to save AsymmetricSigningKey", mlog.Err(appErr)) 207 } else { 208 key = newKey 209 } 210 } 211 212 // If we weren't able to save a new key above, another server must have beat us to it. Get the 213 // key from the database, and if that fails, error out. 214 if key == nil { 215 value, err := a.Srv().Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY) 216 if err != nil { 217 return err 218 } 219 220 if err := json.Unmarshal([]byte(value.Value), &key); err != nil { 221 return err 222 } 223 } 224 225 var curve elliptic.Curve 226 switch key.ECDSAKey.Curve { 227 case "P-256": 228 curve = elliptic.P256() 229 default: 230 return fmt.Errorf("unknown curve: " + key.ECDSAKey.Curve) 231 } 232 a.Srv().asymmetricSigningKey = &ecdsa.PrivateKey{ 233 PublicKey: ecdsa.PublicKey{ 234 Curve: curve, 235 X: key.ECDSAKey.X, 236 Y: key.ECDSAKey.Y, 237 }, 238 D: key.ECDSAKey.D, 239 } 240 a.regenerateClientConfig() 241 return nil 242 } 243 244 func (a *App) ensureInstallationDate() error { 245 _, err := a.getSystemInstallDate() 246 if err == nil { 247 return nil 248 } 249 250 installDate, err := a.Srv().Store.User().InferSystemInstallDate() 251 var installationDate int64 252 if err == nil && installDate > 0 { 253 installationDate = installDate 254 } else { 255 installationDate = utils.MillisFromTime(time.Now()) 256 } 257 258 err = a.Srv().Store.System().SaveOrUpdate(&model.System{ 259 Name: model.SYSTEM_INSTALLATION_DATE_KEY, 260 Value: strconv.FormatInt(installationDate, 10), 261 }) 262 if err != nil { 263 return err 264 } 265 return nil 266 } 267 268 // AsymmetricSigningKey will return a private key that can be used for asymmetric signing. 269 func (s *Server) AsymmetricSigningKey() *ecdsa.PrivateKey { 270 return s.asymmetricSigningKey 271 } 272 273 func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey { 274 return a.Srv().AsymmetricSigningKey() 275 } 276 277 func (s *Server) PostActionCookieSecret() []byte { 278 return s.postActionCookieSecret 279 } 280 281 func (a *App) PostActionCookieSecret() []byte { 282 return a.Srv().PostActionCookieSecret() 283 } 284 285 func (a *App) regenerateClientConfig() { 286 clientConfig := config.GenerateClientConfig(a.Config(), a.DiagnosticId(), a.License()) 287 limitedClientConfig := config.GenerateLimitedClientConfig(a.Config(), a.DiagnosticId(), a.License()) 288 289 if clientConfig["EnableCustomTermsOfService"] == "true" { 290 termsOfService, err := a.GetLatestTermsOfService() 291 if err != nil { 292 mlog.Err(err) 293 } else { 294 clientConfig["CustomTermsOfServiceId"] = termsOfService.Id 295 limitedClientConfig["CustomTermsOfServiceId"] = termsOfService.Id 296 } 297 } 298 299 if key := a.AsymmetricSigningKey(); key != nil { 300 der, _ := x509.MarshalPKIXPublicKey(&key.PublicKey) 301 clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der) 302 limitedClientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der) 303 } 304 305 clientConfigJSON, _ := json.Marshal(clientConfig) 306 a.Srv().clientConfig = clientConfig 307 a.Srv().limitedClientConfig = limitedClientConfig 308 a.Srv().clientConfigHash = fmt.Sprintf("%x", md5.Sum(clientConfigJSON)) 309 } 310 311 func (a *App) GetCookieDomain() string { 312 if *a.Config().ServiceSettings.AllowCookiesForSubdomains { 313 if siteURL, err := url.Parse(*a.Config().ServiceSettings.SiteURL); err == nil { 314 return siteURL.Hostname() 315 } 316 } 317 return "" 318 } 319 320 func (a *App) GetSiteURL() string { 321 return *a.Config().ServiceSettings.SiteURL 322 } 323 324 // ClientConfigWithComputed gets the configuration in a format suitable for sending to the client. 325 func (a *App) ClientConfigWithComputed() map[string]string { 326 respCfg := map[string]string{} 327 for k, v := range a.ClientConfig() { 328 respCfg[k] = v 329 } 330 331 // These properties are not configurable, but nevertheless represent configuration expected 332 // by the client. 333 respCfg["NoAccounts"] = strconv.FormatBool(a.IsFirstUserAccount()) 334 respCfg["MaxPostSize"] = strconv.Itoa(a.MaxPostSize()) 335 respCfg["InstallationDate"] = "" 336 if installationDate, err := a.getSystemInstallDate(); err == nil { 337 respCfg["InstallationDate"] = strconv.FormatInt(installationDate, 10) 338 } 339 340 return respCfg 341 } 342 343 // LimitedClientConfigWithComputed gets the configuration in a format suitable for sending to the client. 344 func (a *App) LimitedClientConfigWithComputed() map[string]string { 345 respCfg := map[string]string{} 346 for k, v := range a.LimitedClientConfig() { 347 respCfg[k] = v 348 } 349 350 // These properties are not configurable, but nevertheless represent configuration expected 351 // by the client. 352 respCfg["NoAccounts"] = strconv.FormatBool(a.IsFirstUserAccount()) 353 354 return respCfg 355 } 356 357 // GetConfigFile proxies access to the given configuration file to the underlying config store. 358 func (a *App) GetConfigFile(name string) ([]byte, error) { 359 data, err := a.Srv().configStore.GetFile(name) 360 if err != nil { 361 return nil, errors.Wrapf(err, "failed to get config file %s", name) 362 } 363 364 return data, nil 365 } 366 367 // GetSanitizedConfig gets the configuration for a system admin without any secrets. 368 func (a *App) GetSanitizedConfig() *model.Config { 369 cfg := a.Config().Clone() 370 cfg.Sanitize() 371 372 return cfg 373 } 374 375 // GetEnvironmentConfig returns a map of configuration keys whose values have been overridden by an environment variable. 376 func (a *App) GetEnvironmentConfig() map[string]interface{} { 377 return a.EnvironmentConfig() 378 } 379 380 // SaveConfig replaces the active configuration, optionally notifying cluster peers. 381 func (a *App) SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) *model.AppError { 382 oldCfg, err := a.Srv().configStore.Set(newCfg) 383 if errors.Cause(err) == config.ErrReadOnlyConfiguration { 384 return model.NewAppError("saveConfig", "ent.cluster.save_config.error", nil, err.Error(), http.StatusForbidden) 385 } else if err != nil { 386 return model.NewAppError("saveConfig", "app.save_config.app_error", nil, err.Error(), http.StatusInternalServerError) 387 } 388 389 if a.Metrics() != nil { 390 if *a.Config().MetricsSettings.Enable { 391 a.Metrics().StartServer() 392 } else { 393 a.Metrics().StopServer() 394 } 395 } 396 397 if a.Cluster() != nil { 398 newCfg = a.Srv().configStore.RemoveEnvironmentOverrides(newCfg) 399 oldCfg = a.Srv().configStore.RemoveEnvironmentOverrides(oldCfg) 400 err := a.Cluster().ConfigChanged(oldCfg, newCfg, sendConfigChangeClusterMessage) 401 if err != nil { 402 return err 403 } 404 } 405 406 return nil 407 } 408 409 func (a *App) HandleMessageExportConfig(cfg *model.Config, appCfg *model.Config) { 410 // If the Message Export feature has been toggled in the System Console, rewrite the ExportFromTimestamp field to an 411 // appropriate value. The rewriting occurs here to ensure it doesn't affect values written to the config file 412 // directly and not through the System Console UI. 413 if *cfg.MessageExportSettings.EnableExport != *appCfg.MessageExportSettings.EnableExport { 414 if *cfg.MessageExportSettings.EnableExport && *cfg.MessageExportSettings.ExportFromTimestamp == int64(0) { 415 // When the feature is toggled on, use the current timestamp as the start time for future exports. 416 cfg.MessageExportSettings.ExportFromTimestamp = model.NewInt64(model.GetMillis()) 417 } else if !*cfg.MessageExportSettings.EnableExport { 418 // When the feature is disabled, reset the timestamp so that the timestamp will be set if 419 // the feature is re-enabled from the System Console in future. 420 cfg.MessageExportSettings.ExportFromTimestamp = model.NewInt64(0) 421 } 422 } 423 }