setting.go 17 KB

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