setting.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. // Copyright 2014 Unknwon
  2. // Copyright 2014 Torkel Ödegaard
  3. package setting
  4. import (
  5. "bytes"
  6. "fmt"
  7. "net/url"
  8. "os"
  9. "path"
  10. "path/filepath"
  11. "regexp"
  12. "runtime"
  13. "strings"
  14. "gopkg.in/ini.v1"
  15. "github.com/go-macaron/session"
  16. "github.com/grafana/grafana/pkg/log"
  17. "github.com/grafana/grafana/pkg/util"
  18. )
  19. type Scheme string
  20. const (
  21. HTTP Scheme = "http"
  22. HTTPS Scheme = "https"
  23. DEFAULT_HTTP_ADDR string = "0.0.0.0"
  24. )
  25. const (
  26. DEV string = "development"
  27. PROD string = "production"
  28. TEST string = "test"
  29. )
  30. var (
  31. // App settings.
  32. Env string = DEV
  33. AppUrl string
  34. AppSubUrl string
  35. InstanceName string
  36. // build
  37. BuildVersion string
  38. BuildCommit string
  39. BuildStamp int64
  40. // Paths
  41. LogsPath string
  42. HomePath string
  43. DataPath string
  44. PluginsPath string
  45. CustomInitPath = "conf/custom.ini"
  46. // Log settings.
  47. LogModes []string
  48. LogConfigs []util.DynMap
  49. // Http server options
  50. Protocol Scheme
  51. Domain string
  52. HttpAddr, HttpPort string
  53. SshPort int
  54. CertFile, KeyFile string
  55. RouterLogging bool
  56. DataProxyLogging bool
  57. StaticRootPath string
  58. EnableGzip bool
  59. EnforceDomain bool
  60. // Security settings.
  61. SecretKey string
  62. LogInRememberDays int
  63. CookieUserName string
  64. CookieRememberName string
  65. DisableGravatar bool
  66. EmailCodeValidMinutes int
  67. DataProxyWhiteList map[string]bool
  68. // Snapshots
  69. ExternalSnapshotUrl string
  70. ExternalSnapshotName string
  71. ExternalEnabled bool
  72. SnapShotTTLDays int
  73. SnapShotRemoveExpired bool
  74. // User settings
  75. AllowUserSignUp bool
  76. AllowUserOrgCreate bool
  77. AutoAssignOrg bool
  78. AutoAssignOrgRole string
  79. VerifyEmailEnabled bool
  80. LoginHint string
  81. DefaultTheme string
  82. DisableLoginForm bool
  83. // Http auth
  84. AdminUser string
  85. AdminPassword string
  86. AnonymousEnabled bool
  87. AnonymousOrgName string
  88. AnonymousOrgRole string
  89. // Auth proxy settings
  90. AuthProxyEnabled bool
  91. AuthProxyHeaderName string
  92. AuthProxyHeaderProperty string
  93. AuthProxyAutoSignUp bool
  94. AuthProxyLdapSyncTtl int
  95. AuthProxyWhitelist string
  96. // Basic Auth
  97. BasicAuthEnabled bool
  98. // Session settings.
  99. SessionOptions session.Options
  100. // Global setting objects.
  101. Cfg *ini.File
  102. ConfRootPath string
  103. IsWindows bool
  104. // PhantomJs Rendering
  105. ImagesDir string
  106. PhantomDir string
  107. // for logging purposes
  108. configFiles []string
  109. appliedCommandLineProperties []string
  110. appliedEnvOverrides []string
  111. ReportingEnabled bool
  112. CheckForUpdates bool
  113. GoogleAnalyticsId string
  114. GoogleTagManagerId string
  115. // LDAP
  116. LdapEnabled bool
  117. LdapConfigFile string
  118. LdapAllowSignup bool = true
  119. // SMTP email settings
  120. Smtp SmtpSettings
  121. // QUOTA
  122. Quota QuotaSettings
  123. // Alerting
  124. AlertingEnabled bool
  125. ExecuteAlerts bool
  126. // logger
  127. logger log.Logger
  128. // Grafana.NET URL
  129. GrafanaNetUrl string
  130. // S3 temp image store
  131. S3TempImageStoreBucketUrl string
  132. S3TempImageStoreAccessKey string
  133. S3TempImageStoreSecretKey string
  134. ImageUploadProvider string
  135. )
  136. type CommandLineArgs struct {
  137. Config string
  138. HomePath string
  139. Args []string
  140. }
  141. func init() {
  142. IsWindows = runtime.GOOS == "windows"
  143. logger = log.New("settings")
  144. }
  145. func parseAppUrlAndSubUrl(section *ini.Section) (string, string) {
  146. appUrl := section.Key("root_url").MustString("http://localhost:3000/")
  147. if appUrl[len(appUrl)-1] != '/' {
  148. appUrl += "/"
  149. }
  150. // Check if has app suburl.
  151. url, err := url.Parse(appUrl)
  152. if err != nil {
  153. log.Fatal(4, "Invalid root_url(%s): %s", appUrl, err)
  154. }
  155. appSubUrl := strings.TrimSuffix(url.Path, "/")
  156. return appUrl, appSubUrl
  157. }
  158. func ToAbsUrl(relativeUrl string) string {
  159. return AppUrl + relativeUrl
  160. }
  161. func shouldRedactKey(s string) bool {
  162. uppercased := strings.ToUpper(s)
  163. return strings.Contains(uppercased, "PASSWORD") || strings.Contains(uppercased, "SECRET") || strings.Contains(uppercased, "PROVIDER_CONFIG")
  164. }
  165. func shouldRedactURLKey(s string) bool {
  166. uppercased := strings.ToUpper(s)
  167. return strings.Contains(uppercased, "DATABASE_URL")
  168. }
  169. func applyEnvVariableOverrides() {
  170. appliedEnvOverrides = make([]string, 0)
  171. for _, section := range Cfg.Sections() {
  172. for _, key := range section.Keys() {
  173. sectionName := strings.ToUpper(strings.Replace(section.Name(), ".", "_", -1))
  174. keyName := strings.ToUpper(strings.Replace(key.Name(), ".", "_", -1))
  175. envKey := fmt.Sprintf("GF_%s_%s", sectionName, keyName)
  176. envValue := os.Getenv(envKey)
  177. if len(envValue) > 0 {
  178. key.SetValue(envValue)
  179. if shouldRedactKey(envKey) {
  180. envValue = "*********"
  181. }
  182. if shouldRedactURLKey(envKey) {
  183. u, _ := url.Parse(envValue)
  184. ui := u.User
  185. if ui != nil {
  186. _, exists := ui.Password()
  187. if exists {
  188. u.User = url.UserPassword(ui.Username(), "-redacted-")
  189. envValue = u.String()
  190. }
  191. }
  192. }
  193. appliedEnvOverrides = append(appliedEnvOverrides, fmt.Sprintf("%s=%s", envKey, envValue))
  194. }
  195. }
  196. }
  197. }
  198. func applyCommandLineDefaultProperties(props map[string]string) {
  199. appliedCommandLineProperties = make([]string, 0)
  200. for _, section := range Cfg.Sections() {
  201. for _, key := range section.Keys() {
  202. keyString := fmt.Sprintf("default.%s.%s", section.Name(), key.Name())
  203. value, exists := props[keyString]
  204. if exists {
  205. key.SetValue(value)
  206. if shouldRedactKey(keyString) {
  207. value = "*********"
  208. }
  209. appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
  210. }
  211. }
  212. }
  213. }
  214. func applyCommandLineProperties(props map[string]string) {
  215. for _, section := range Cfg.Sections() {
  216. for _, key := range section.Keys() {
  217. keyString := fmt.Sprintf("%s.%s", section.Name(), key.Name())
  218. value, exists := props[keyString]
  219. if exists {
  220. key.SetValue(value)
  221. appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
  222. }
  223. }
  224. }
  225. }
  226. func getCommandLineProperties(args []string) map[string]string {
  227. props := make(map[string]string)
  228. for _, arg := range args {
  229. if !strings.HasPrefix(arg, "cfg:") {
  230. continue
  231. }
  232. trimmed := strings.TrimPrefix(arg, "cfg:")
  233. parts := strings.Split(trimmed, "=")
  234. if len(parts) != 2 {
  235. log.Fatal(3, "Invalid command line argument", arg)
  236. return nil
  237. }
  238. props[parts[0]] = parts[1]
  239. }
  240. return props
  241. }
  242. func makeAbsolute(path string, root string) string {
  243. if filepath.IsAbs(path) {
  244. return path
  245. }
  246. return filepath.Join(root, path)
  247. }
  248. func evalEnvVarExpression(value string) string {
  249. regex := regexp.MustCompile(`\${(\w+)}`)
  250. return regex.ReplaceAllStringFunc(value, func(envVar string) string {
  251. envVar = strings.TrimPrefix(envVar, "${")
  252. envVar = strings.TrimSuffix(envVar, "}")
  253. envValue := os.Getenv(envVar)
  254. // if env variable is hostname and it is emtpy use os.Hostname as default
  255. if envVar == "HOSTNAME" && envValue == "" {
  256. envValue, _ = os.Hostname()
  257. }
  258. return envValue
  259. })
  260. }
  261. func evalConfigValues() {
  262. for _, section := range Cfg.Sections() {
  263. for _, key := range section.Keys() {
  264. key.SetValue(evalEnvVarExpression(key.Value()))
  265. }
  266. }
  267. }
  268. func loadSpecifedConfigFile(configFile string) error {
  269. if configFile == "" {
  270. configFile = filepath.Join(HomePath, CustomInitPath)
  271. // return without error if custom file does not exist
  272. if !pathExists(configFile) {
  273. return nil
  274. }
  275. }
  276. userConfig, err := ini.Load(configFile)
  277. if err != nil {
  278. return fmt.Errorf("Failed to parse %v, %v", configFile, err)
  279. }
  280. userConfig.BlockMode = false
  281. for _, section := range userConfig.Sections() {
  282. for _, key := range section.Keys() {
  283. if key.Value() == "" {
  284. continue
  285. }
  286. defaultSec, err := Cfg.GetSection(section.Name())
  287. if err != nil {
  288. defaultSec, _ = Cfg.NewSection(section.Name())
  289. }
  290. defaultKey, err := defaultSec.GetKey(key.Name())
  291. if err != nil {
  292. defaultKey, _ = defaultSec.NewKey(key.Name(), key.Value())
  293. }
  294. defaultKey.SetValue(key.Value())
  295. }
  296. }
  297. configFiles = append(configFiles, configFile)
  298. return nil
  299. }
  300. func loadConfiguration(args *CommandLineArgs) {
  301. var err error
  302. // load config defaults
  303. defaultConfigFile := path.Join(HomePath, "conf/defaults.ini")
  304. configFiles = append(configFiles, defaultConfigFile)
  305. // check if config file exists
  306. if _, err := os.Stat(defaultConfigFile); os.IsNotExist(err) {
  307. fmt.Println("Grafana-server Init Failed: Could not find config defaults, make sure homepath command line parameter is set or working directory is homepath")
  308. os.Exit(1)
  309. }
  310. // load defaults
  311. Cfg, err = ini.Load(defaultConfigFile)
  312. if err != nil {
  313. fmt.Println(fmt.Sprintf("Failed to parse defaults.ini, %v", err))
  314. os.Exit(1)
  315. return
  316. }
  317. Cfg.BlockMode = false
  318. // command line props
  319. commandLineProps := getCommandLineProperties(args.Args)
  320. // load default overrides
  321. applyCommandLineDefaultProperties(commandLineProps)
  322. // load specified config file
  323. err = loadSpecifedConfigFile(args.Config)
  324. if err != nil {
  325. initLogging()
  326. log.Fatal(3, err.Error())
  327. }
  328. // apply environment overrides
  329. applyEnvVariableOverrides()
  330. // apply command line overrides
  331. applyCommandLineProperties(commandLineProps)
  332. // evaluate config values containing environment variables
  333. evalConfigValues()
  334. // update data path and logging config
  335. DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath)
  336. initLogging()
  337. }
  338. func pathExists(path string) bool {
  339. _, err := os.Stat(path)
  340. if err == nil {
  341. return true
  342. }
  343. if os.IsNotExist(err) {
  344. return false
  345. }
  346. return false
  347. }
  348. func setHomePath(args *CommandLineArgs) {
  349. if args.HomePath != "" {
  350. HomePath = args.HomePath
  351. return
  352. }
  353. HomePath, _ = filepath.Abs(".")
  354. // check if homepath is correct
  355. if pathExists(filepath.Join(HomePath, "conf/defaults.ini")) {
  356. return
  357. }
  358. // try down one path
  359. if pathExists(filepath.Join(HomePath, "../conf/defaults.ini")) {
  360. HomePath = filepath.Join(HomePath, "../")
  361. }
  362. }
  363. var skipStaticRootValidation bool = false
  364. func validateStaticRootPath() error {
  365. if skipStaticRootValidation {
  366. return nil
  367. }
  368. if _, err := os.Stat(path.Join(StaticRootPath, "css")); err == nil {
  369. return nil
  370. }
  371. if _, err := os.Stat(StaticRootPath + "_gen/css"); err == nil {
  372. StaticRootPath = StaticRootPath + "_gen"
  373. return nil
  374. }
  375. return fmt.Errorf("Failed to detect generated css or javascript files in static root (%s), have you executed default grunt task?", StaticRootPath)
  376. }
  377. func NewConfigContext(args *CommandLineArgs) error {
  378. setHomePath(args)
  379. loadConfiguration(args)
  380. Env = Cfg.Section("").Key("app_mode").MustString("development")
  381. InstanceName = Cfg.Section("").Key("instance_name").MustString("unknown_instance_name")
  382. PluginsPath = makeAbsolute(Cfg.Section("paths").Key("plugins").String(), HomePath)
  383. server := Cfg.Section("server")
  384. AppUrl, AppSubUrl = parseAppUrlAndSubUrl(server)
  385. Protocol = HTTP
  386. if server.Key("protocol").MustString("http") == "https" {
  387. Protocol = HTTPS
  388. CertFile = server.Key("cert_file").String()
  389. KeyFile = server.Key("cert_key").String()
  390. }
  391. Domain = server.Key("domain").MustString("localhost")
  392. HttpAddr = server.Key("http_addr").MustString(DEFAULT_HTTP_ADDR)
  393. HttpPort = server.Key("http_port").MustString("3000")
  394. RouterLogging = server.Key("router_logging").MustBool(false)
  395. EnableGzip = server.Key("enable_gzip").MustBool(false)
  396. EnforceDomain = server.Key("enforce_domain").MustBool(false)
  397. StaticRootPath = makeAbsolute(server.Key("static_root_path").String(), HomePath)
  398. if err := validateStaticRootPath(); err != nil {
  399. return err
  400. }
  401. // read data proxy settings
  402. dataproxy := Cfg.Section("dataproxy")
  403. DataProxyLogging = dataproxy.Key("logging").MustBool(false)
  404. // read security settings
  405. security := Cfg.Section("security")
  406. SecretKey = security.Key("secret_key").String()
  407. LogInRememberDays = security.Key("login_remember_days").MustInt()
  408. CookieUserName = security.Key("cookie_username").String()
  409. CookieRememberName = security.Key("cookie_remember_name").String()
  410. DisableGravatar = security.Key("disable_gravatar").MustBool(true)
  411. // read snapshots settings
  412. snapshots := Cfg.Section("snapshots")
  413. ExternalSnapshotUrl = snapshots.Key("external_snapshot_url").String()
  414. ExternalSnapshotName = snapshots.Key("external_snapshot_name").String()
  415. ExternalEnabled = snapshots.Key("external_enabled").MustBool(true)
  416. SnapShotRemoveExpired = snapshots.Key("snapshot_remove_expired").MustBool(true)
  417. SnapShotTTLDays = snapshots.Key("snapshot_TTL_days").MustInt(90)
  418. // read data source proxy white list
  419. DataProxyWhiteList = make(map[string]bool)
  420. for _, hostAndIp := range security.Key("data_source_proxy_whitelist").Strings(" ") {
  421. DataProxyWhiteList[hostAndIp] = true
  422. }
  423. // admin
  424. AdminUser = security.Key("admin_user").String()
  425. AdminPassword = security.Key("admin_password").String()
  426. users := Cfg.Section("users")
  427. AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
  428. AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
  429. AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
  430. AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
  431. VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
  432. LoginHint = users.Key("login_hint").String()
  433. DefaultTheme = users.Key("default_theme").String()
  434. // auth
  435. auth := Cfg.Section("auth")
  436. DisableLoginForm = auth.Key("disable_login_form").MustBool(false)
  437. // anonymous access
  438. AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
  439. AnonymousOrgName = Cfg.Section("auth.anonymous").Key("org_name").String()
  440. AnonymousOrgRole = Cfg.Section("auth.anonymous").Key("org_role").String()
  441. // auth proxy
  442. authProxy := Cfg.Section("auth.proxy")
  443. AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)
  444. AuthProxyHeaderName = authProxy.Key("header_name").String()
  445. AuthProxyHeaderProperty = authProxy.Key("header_property").String()
  446. AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
  447. AuthProxyLdapSyncTtl = authProxy.Key("ldap_sync_ttl").MustInt()
  448. AuthProxyWhitelist = authProxy.Key("whitelist").String()
  449. // basic auth
  450. authBasic := Cfg.Section("auth.basic")
  451. BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
  452. // PhantomJS rendering
  453. ImagesDir = filepath.Join(DataPath, "png")
  454. PhantomDir = filepath.Join(HomePath, "vendor/phantomjs")
  455. analytics := Cfg.Section("analytics")
  456. ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
  457. CheckForUpdates = analytics.Key("check_for_updates").MustBool(true)
  458. GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
  459. GoogleTagManagerId = analytics.Key("google_tag_manager_id").String()
  460. ldapSec := Cfg.Section("auth.ldap")
  461. LdapEnabled = ldapSec.Key("enabled").MustBool(false)
  462. LdapConfigFile = ldapSec.Key("config_file").String()
  463. LdapAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true)
  464. alerting := Cfg.Section("alerting")
  465. AlertingEnabled = alerting.Key("enabled").MustBool(true)
  466. ExecuteAlerts = alerting.Key("execute_alerts").MustBool(true)
  467. readSessionConfig()
  468. readSmtpSettings()
  469. readQuotaSettings()
  470. if VerifyEmailEnabled && !Smtp.Enabled {
  471. log.Warn("require_email_validation is enabled but smpt is disabled")
  472. }
  473. GrafanaNetUrl = Cfg.Section("grafana_net").Key("url").MustString("https://grafana.com")
  474. imageUploadingSection := Cfg.Section("external_image_storage")
  475. ImageUploadProvider = imageUploadingSection.Key("provider").MustString("internal")
  476. return nil
  477. }
  478. func readSessionConfig() {
  479. sec := Cfg.Section("session")
  480. SessionOptions = session.Options{}
  481. SessionOptions.Provider = sec.Key("provider").In("memory", []string{"memory", "file", "redis", "mysql", "postgres", "memcache"})
  482. SessionOptions.ProviderConfig = strings.Trim(sec.Key("provider_config").String(), "\" ")
  483. SessionOptions.CookieName = sec.Key("cookie_name").MustString("grafana_sess")
  484. SessionOptions.CookiePath = AppSubUrl
  485. SessionOptions.Secure = sec.Key("cookie_secure").MustBool()
  486. SessionOptions.Gclifetime = Cfg.Section("session").Key("gc_interval_time").MustInt64(86400)
  487. SessionOptions.Maxlifetime = Cfg.Section("session").Key("session_life_time").MustInt64(86400)
  488. SessionOptions.IDLength = 16
  489. if SessionOptions.Provider == "file" {
  490. SessionOptions.ProviderConfig = makeAbsolute(SessionOptions.ProviderConfig, DataPath)
  491. os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm)
  492. }
  493. if SessionOptions.CookiePath == "" {
  494. SessionOptions.CookiePath = "/"
  495. }
  496. }
  497. func initLogging() {
  498. // split on comma
  499. LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",")
  500. // also try space
  501. if len(LogModes) == 1 {
  502. LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), " ")
  503. }
  504. LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath)
  505. log.ReadLoggingConfig(LogModes, LogsPath, Cfg)
  506. }
  507. func LogConfigurationInfo() {
  508. var text bytes.Buffer
  509. for _, file := range configFiles {
  510. logger.Info("Config loaded from", "file", file)
  511. }
  512. if len(appliedCommandLineProperties) > 0 {
  513. for _, prop := range appliedCommandLineProperties {
  514. logger.Info("Config overriden from command line", "arg", prop)
  515. }
  516. }
  517. if len(appliedEnvOverrides) > 0 {
  518. text.WriteString("\tEnvironment variables used:\n")
  519. for _, prop := range appliedEnvOverrides {
  520. logger.Info("Config overriden from Environment variable", "var", prop)
  521. }
  522. }
  523. logger.Info("Path Home", "path", HomePath)
  524. logger.Info("Path Data", "path", DataPath)
  525. logger.Info("Path Logs", "path", LogsPath)
  526. logger.Info("Path Plugins", "path", PluginsPath)
  527. }