setting.go 17 KB

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