setting.go 17 KB

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