setting.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  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. AuditLogging 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. ExecuteAlerts 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. AuditLogging = server.Key("audit_logging").MustBool(false)
  410. EnableGzip = server.Key("enable_gzip").MustBool(false)
  411. EnforceDomain = server.Key("enforce_domain").MustBool(false)
  412. StaticRootPath = makeAbsolute(server.Key("static_root_path").String(), HomePath)
  413. if err := validateStaticRootPath(); err != nil {
  414. return err
  415. }
  416. // read security settings
  417. security := Cfg.Section("security")
  418. SecretKey = security.Key("secret_key").String()
  419. LogInRememberDays = security.Key("login_remember_days").MustInt()
  420. CookieUserName = security.Key("cookie_username").String()
  421. CookieRememberName = security.Key("cookie_remember_name").String()
  422. DisableGravatar = security.Key("disable_gravatar").MustBool(true)
  423. // read snapshots settings
  424. snapshots := Cfg.Section("snapshots")
  425. ExternalSnapshotUrl = snapshots.Key("external_snapshot_url").String()
  426. ExternalSnapshotName = snapshots.Key("external_snapshot_name").String()
  427. ExternalEnabled = snapshots.Key("external_enabled").MustBool(true)
  428. SnapShotRemoveExpired = snapshots.Key("snapshot_remove_expired").MustBool(true)
  429. SnapShotTTLDays = snapshots.Key("snapshot_TTL_days").MustInt(90)
  430. // read data source proxy white list
  431. DataProxyWhiteList = make(map[string]bool)
  432. for _, hostAndIp := range security.Key("data_source_proxy_whitelist").Strings(" ") {
  433. DataProxyWhiteList[hostAndIp] = true
  434. }
  435. // admin
  436. AdminUser = security.Key("admin_user").String()
  437. AdminPassword = security.Key("admin_password").String()
  438. users := Cfg.Section("users")
  439. AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
  440. AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
  441. AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
  442. AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
  443. VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
  444. LoginHint = users.Key("login_hint").String()
  445. DefaultTheme = users.Key("default_theme").String()
  446. // auth
  447. auth := Cfg.Section("auth")
  448. DisableLoginForm = auth.Key("disable_login_form").MustBool(false)
  449. // anonymous access
  450. AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
  451. AnonymousOrgName = Cfg.Section("auth.anonymous").Key("org_name").String()
  452. AnonymousOrgRole = Cfg.Section("auth.anonymous").Key("org_role").String()
  453. // auth proxy
  454. authProxy := Cfg.Section("auth.proxy")
  455. AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)
  456. AuthProxyHeaderName = authProxy.Key("header_name").String()
  457. AuthProxyHeaderProperty = authProxy.Key("header_property").String()
  458. AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
  459. AuthProxyLdapSyncTtl = authProxy.Key("ldap_sync_ttl").MustInt()
  460. AuthProxyWhitelist = authProxy.Key("whitelist").String()
  461. // basic auth
  462. authBasic := Cfg.Section("auth.basic")
  463. BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
  464. // PhantomJS rendering
  465. ImagesDir = filepath.Join(DataPath, "png")
  466. PhantomDir = filepath.Join(HomePath, "vendor/phantomjs")
  467. analytics := Cfg.Section("analytics")
  468. ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
  469. CheckForUpdates = analytics.Key("check_for_updates").MustBool(true)
  470. GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
  471. GoogleTagManagerId = analytics.Key("google_tag_manager_id").String()
  472. ldapSec := Cfg.Section("auth.ldap")
  473. LdapEnabled = ldapSec.Key("enabled").MustBool(false)
  474. LdapConfigFile = ldapSec.Key("config_file").String()
  475. LdapAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true)
  476. alerting := Cfg.Section("alerting")
  477. ExecuteAlerts = alerting.Key("execute_alerts").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. }