setting.go 17 KB

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