setting.go 17 KB

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