setting.go 16 KB

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