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