setting.go 17 KB

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