grafana/pkg/setting/setting.go

1178 lines
32 KiB
Go
Raw Normal View History

2014-10-05 09:50:04 -05:00
// Copyright 2014 Unknwon
// Copyright 2014 Torkel Ödegaard
2014-10-04 06:33:20 -05:00
package setting
import (
"bytes"
"errors"
"fmt"
"net/http"
2014-10-04 06:33:20 -05:00
"net/url"
"os"
"path"
"path/filepath"
"regexp"
2014-10-04 06:33:20 -05:00
"runtime"
"strings"
"time"
"github.com/go-macaron/session"
ini "gopkg.in/ini.v1"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/util"
2014-10-04 06:33:20 -05:00
)
type Scheme string
const (
HTTP Scheme = "http"
HTTPS Scheme = "https"
HTTP2 Scheme = "h2"
SOCKET Scheme = "socket"
DEFAULT_HTTP_ADDR string = "0.0.0.0"
REDACTED_PASSWORD string = "*********"
2014-10-04 06:33:20 -05:00
)
2014-12-16 05:04:08 -06:00
const (
DEV = "development"
PROD = "production"
TEST = "test"
APP_NAME = "Grafana"
2014-12-16 05:04:08 -06:00
)
2018-11-02 04:49:46 -05:00
var (
ERR_TEMPLATE_NAME = "error"
)
// This constant corresponds to the default value for ldap_sync_ttl in .ini files
// it is used for comparison and has to be kept in sync
const (
AUTH_PROXY_SYNC_TTL = 60
)
2014-10-04 06:33:20 -05:00
var (
// App settings.
Env = DEV
AppUrl string
AppSubUrl string
ServeFromSubPath bool
InstanceName string
2014-10-04 06:33:20 -05:00
// build
BuildVersion string
BuildCommit string
BuildBranch string
BuildStamp int64
IsEnterprise bool
ApplicationName string
2018-11-15 07:42:09 -06:00
// packaging
Packaging = "unknown"
// Paths
HomePath string
PluginsPath string
CustomInitPath = "conf/custom.ini"
2014-10-04 06:33:20 -05:00
// Log settings.
2015-04-19 02:29:08 -05:00
LogConfigs []util.DynMap
2014-10-04 06:33:20 -05:00
// Http server options
Protocol Scheme
Domain string
HttpAddr, HttpPort string
SshPort int
CertFile, KeyFile string
SocketPath string
2014-10-05 09:50:04 -05:00
RouterLogging bool
2017-01-11 09:51:46 -06:00
DataProxyLogging bool
DataProxyTimeout int
2014-10-05 09:50:04 -05:00
StaticRootPath string
EnableGzip bool
EnforceDomain bool
2014-10-05 09:50:04 -05:00
// Security settings.
SecretKey string
DisableGravatar bool
EmailCodeValidMinutes int
DataProxyWhiteList map[string]bool
DisableBruteForceLoginProtection bool
CookieSecure bool
CookieSameSiteDisabled bool
CookieSameSiteMode http.SameSite
AllowEmbedding bool
XSSProtectionHeader bool
ContentTypeProtectionHeader bool
StrictTransportSecurity bool
StrictTransportSecurityMaxAge int
StrictTransportSecurityPreload bool
StrictTransportSecuritySubDomains bool
// Snapshots
ExternalSnapshotUrl string
ExternalSnapshotName string
ExternalEnabled bool
SnapShotRemoveExpired bool
SnapshotPublicMode bool
// Dashboard history
DashboardVersionsToKeep int
MinRefreshInterval string
// User settings
2017-07-31 07:39:33 -05:00
AllowUserSignUp bool
AllowUserOrgCreate bool
AutoAssignOrg bool
AutoAssignOrgId int
2017-07-31 07:39:33 -05:00
AutoAssignOrgRole string
VerifyEmailEnabled bool
LoginHint string
PasswordHint string
2017-07-31 07:39:33 -05:00
DefaultTheme string
DisableLoginForm bool
DisableSignoutMenu bool
SignoutRedirectUrl string
2017-07-31 07:39:33 -05:00
ExternalUserMngLinkUrl string
ExternalUserMngLinkName string
ExternalUserMngInfo string
OAuthAutoLogin bool
ViewersCanEdit bool
// Http auth
AdminUser string
AdminPassword string
LoginCookieName string
LoginMaxLifetimeDays int
2015-01-27 08:45:27 -06:00
AnonymousEnabled bool
AnonymousOrgName string
AnonymousOrgRole string
// Auth proxy settings
AuthProxyEnabled bool
AuthProxyHeaderName string
AuthProxyHeaderProperty string
AuthProxyAutoSignUp bool
AuthProxyEnableLoginToken bool
AuthProxySyncTtl int
AuthProxyWhitelist string
AuthProxyHeaders map[string]string
// Basic Auth
BasicAuthEnabled bool
2014-10-05 09:50:04 -05:00
// Session settings.
SessionOptions session.Options
SessionConnMaxLifetime int64
2014-10-04 06:33:20 -05:00
// Global setting objects.
Raw *ini.File
2014-10-04 06:33:20 -05:00
ConfRootPath string
IsWindows bool
2014-10-06 14:31:54 -05:00
// for logging purposes
configFiles []string
appliedCommandLineProperties []string
appliedEnvOverrides []string
2015-03-22 14:14:00 -05:00
ReportingEnabled bool
CheckForUpdates bool
GoogleAnalyticsId string
GoogleTagManagerId string
2015-06-04 02:34:42 -05:00
// LDAP
LDAPEnabled bool
LDAPConfigFile string
LDAPSyncCron string
LDAPAllowSignup bool
LDAPActiveSyncEnabled bool
2015-07-10 04:10:48 -05:00
2015-09-10 12:47:33 -05:00
// QUOTA
Quota QuotaSettings
// Alerting
AlertingEnabled bool
ExecuteAlerts bool
AlertingRenderLimit int
AlertingErrorOrTimeout string
AlertingNoDataOrNullValues string
AlertingEvaluationTimeout time.Duration
AlertingNotificationTimeout time.Duration
AlertingMaxAttempts int
AlertingMinInterval int64
2018-04-27 04:39:14 -05:00
// Explore UI
ExploreEnabled bool
// Grafana.NET URL
GrafanaComUrl string
// S3 temp image store
S3TempImageStoreBucketUrl string
S3TempImageStoreAccessKey string
S3TempImageStoreSecretKey string
ImageUploadProvider string
FeatureToggles map[string]bool
2014-10-04 06:33:20 -05:00
)
2018-10-12 00:55:36 -05:00
// TODO move all global vars to this struct
type Cfg struct {
Raw *ini.File
Logger log.Logger
2018-10-12 00:55:36 -05:00
// HTTP Server Settings
AppUrl string
AppSubUrl string
ServeFromSubPath bool
2018-10-12 00:55:36 -05:00
// Paths
ProvisioningPath string
2018-10-12 00:55:36 -05:00
DataPath string
LogsPath string
// SMTP email settings
Smtp SmtpSettings
// Rendering
ImagesDir string
RendererUrl string
RendererCallbackUrl string
RendererConcurrentRequestLimit int
// Security
DisableInitAdminCreation bool
DisableBruteForceLoginProtection bool
CookieSecure bool
CookieSameSiteDisabled bool
CookieSameSiteMode http.SameSite
TempDataLifetime time.Duration
MetricsEndpointEnabled bool
MetricsEndpointBasicAuthUsername string
MetricsEndpointBasicAuthPassword string
MetricsEndpointDisableTotalStats bool
PluginsEnableAlpha bool
PluginsAppsSkipVerifyTLS bool
PluginSettings PluginSettings
DisableSanitizeHtml bool
EnterpriseLicensePath string
// Auth
LoginCookieName string
LoginMaxInactiveLifetimeDays int
LoginMaxLifetimeDays int
TokenRotationIntervalMinutes int
// OAuth
OAuthCookieMaxAge int
2019-07-05 09:39:52 -05:00
// SAML Auth
SAMLEnabled bool
// Dataproxy
SendUserHeader bool
2019-03-03 14:48:00 -06:00
// DistributedCache
2019-03-08 13:49:16 -06:00
RemoteCacheOptions *RemoteCacheOptions
2019-03-12 01:32:47 -05:00
EditorsCanAdmin bool
Auth: Allow expiration of API keys (#17678) * Modify backend to allow expiration of API Keys * Add middleware test for expired api keys * Modify frontend to enable expiration of API Keys * Fix frontend tests * Fix migration and add index for `expires` field * Add api key tests for database access * Substitude time.Now() by a mock for test usage * Front-end modifications * Change input label to `Time to live` * Change input behavior to comply with the other similar * Add tooltip * Modify AddApiKey api call response Expiration should be *time.Time instead of string * Present expiration date in the selected timezone * Use kbn for transforming intervals to seconds * Use `assert` library for tests * Frontend fixes Add checks for empty/undefined/null values * Change expires column from datetime to integer * Restrict api key duration input It should be interval not number * AddApiKey must complain if SecondsToLive is negative * Declare ErrInvalidApiKeyExpiration * Move configuration to auth section * Update docs * Eliminate alias for models in modified files * Omit expiration from api response if empty * Eliminate Goconvey from test file * Fix test Do not sleep, use mocked timeNow() instead * Remove index for expires from api_key table The index should be anyway on both org_id and expires fields. However this commit eliminates completely the index for now since not many rows are expected to be in this table. * Use getTimeZone function * Minor change in api key listing The frontend should display a message instead of empty string if the key does not expire.
2019-06-26 01:47:03 -05:00
ApiKeyMaxSecondsToLive int64
// Use to enable new features which may still be in alpha/beta stage.
FeatureToggles map[string]bool
}
type CommandLineArgs struct {
Config string
HomePath string
Args []string
}
2014-10-04 06:33:20 -05:00
func init() {
IsWindows = runtime.GOOS == "windows"
2015-01-01 08:29:10 -06:00
}
2014-10-04 06:33:20 -05:00
func parseAppUrlAndSubUrl(section *ini.Section) (string, string, error) {
appUrl, err := valueAsString(section, "root_url", "http://localhost:3000/")
if err != nil {
return "", "", err
}
if appUrl[len(appUrl)-1] != '/' {
appUrl += "/"
}
// Check if has app suburl.
2015-01-30 07:21:32 -06:00
url, err := url.Parse(appUrl)
if err != nil {
log.Fatal(4, "Invalid root_url(%s): %s", appUrl, err)
}
appSubUrl := strings.TrimSuffix(url.Path, "/")
return appUrl, appSubUrl, nil
}
func ToAbsUrl(relativeUrl string) string {
return AppUrl + relativeUrl
}
func shouldRedactKey(s string) bool {
uppercased := strings.ToUpper(s)
2016-08-27 02:50:35 -05:00
return strings.Contains(uppercased, "PASSWORD") || strings.Contains(uppercased, "SECRET") || strings.Contains(uppercased, "PROVIDER_CONFIG")
}
func shouldRedactURLKey(s string) bool {
uppercased := strings.ToUpper(s)
return strings.Contains(uppercased, "DATABASE_URL")
}
func applyEnvVariableOverrides(file *ini.File) error {
appliedEnvOverrides = make([]string, 0)
for _, section := range file.Sections() {
for _, key := range section.Keys() {
envKey := envKey(section.Name(), key.Name())
envValue := os.Getenv(envKey)
if len(envValue) > 0 {
key.SetValue(envValue)
if shouldRedactKey(envKey) {
envValue = REDACTED_PASSWORD
}
if shouldRedactURLKey(envKey) {
u, err := url.Parse(envValue)
if err != nil {
return fmt.Errorf("could not parse environment variable. key: %s, value: %s. error: %v", envKey, envValue, err)
}
ui := u.User
if ui != nil {
_, exists := ui.Password()
if exists {
u.User = url.UserPassword(ui.Username(), "-redacted-")
envValue = u.String()
}
}
}
appliedEnvOverrides = append(appliedEnvOverrides, fmt.Sprintf("%s=%s", envKey, envValue))
}
}
}
return nil
}
func envKey(sectionName string, keyName string) string {
sN := strings.ToUpper(strings.Replace(sectionName, ".", "_", -1))
kN := strings.ToUpper(strings.Replace(keyName, ".", "_", -1))
envKey := fmt.Sprintf("GF_%s_%s", sN, kN)
return envKey
}
func applyCommandLineDefaultProperties(props map[string]string, file *ini.File) {
appliedCommandLineProperties = make([]string, 0)
for _, section := range file.Sections() {
for _, key := range section.Keys() {
keyString := fmt.Sprintf("default.%s.%s", section.Name(), key.Name())
value, exists := props[keyString]
if exists {
key.SetValue(value)
if shouldRedactKey(keyString) {
value = REDACTED_PASSWORD
}
appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
}
}
}
}
func applyCommandLineProperties(props map[string]string, file *ini.File) {
for _, section := range file.Sections() {
sectionName := section.Name() + "."
if section.Name() == ini.DefaultSection {
sectionName = ""
}
for _, key := range section.Keys() {
keyString := sectionName + key.Name()
value, exists := props[keyString]
if exists {
appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
key.SetValue(value)
}
}
}
}
func getCommandLineProperties(args []string) map[string]string {
props := make(map[string]string)
2014-10-04 06:33:20 -05:00
for _, arg := range args {
if !strings.HasPrefix(arg, "cfg:") {
continue
}
trimmed := strings.TrimPrefix(arg, "cfg:")
parts := strings.Split(trimmed, "=")
if len(parts) != 2 {
2018-08-28 15:26:47 -05:00
log.Fatal(3, "Invalid command line argument. argument: %v", arg)
return nil
}
props[parts[0]] = parts[1]
}
return props
}
func makeAbsolute(path string, root string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.Join(root, path)
}
func EvalEnvVarExpression(value string) string {
regex := regexp.MustCompile(`\${(\w+)}`)
return regex.ReplaceAllStringFunc(value, func(envVar string) string {
envVar = strings.TrimPrefix(envVar, "${")
envVar = strings.TrimSuffix(envVar, "}")
envValue := os.Getenv(envVar)
// if env variable is hostname and it is empty use os.Hostname as default
if envVar == "HOSTNAME" && envValue == "" {
envValue, _ = os.Hostname()
}
return envValue
})
}
func evalConfigValues(file *ini.File) {
for _, section := range file.Sections() {
for _, key := range section.Keys() {
key.SetValue(EvalEnvVarExpression(key.Value()))
}
}
}
func loadSpecifiedConfigFile(configFile string, masterFile *ini.File) error {
if configFile == "" {
configFile = filepath.Join(HomePath, CustomInitPath)
// return without error if custom file does not exist
if !pathExists(configFile) {
return nil
}
}
userConfig, err := ini.Load(configFile)
if err != nil {
return fmt.Errorf("Failed to parse %v, %v", configFile, err)
}
userConfig.BlockMode = false
for _, section := range userConfig.Sections() {
for _, key := range section.Keys() {
if key.Value() == "" {
continue
}
defaultSec, err := masterFile.GetSection(section.Name())
if err != nil {
defaultSec, _ = masterFile.NewSection(section.Name())
}
defaultKey, err := defaultSec.GetKey(key.Name())
if err != nil {
defaultKey, _ = defaultSec.NewKey(key.Name(), key.Value())
}
defaultKey.SetValue(key.Value())
}
}
configFiles = append(configFiles, configFile)
return nil
}
2018-10-12 00:55:36 -05:00
func (cfg *Cfg) loadConfiguration(args *CommandLineArgs) (*ini.File, error) {
var err error
// load config defaults
defaultConfigFile := path.Join(HomePath, "conf/defaults.ini")
configFiles = append(configFiles, defaultConfigFile)
// check if config file exists
if _, err := os.Stat(defaultConfigFile); os.IsNotExist(err) {
fmt.Println("Grafana-server Init Failed: Could not find config defaults, make sure homepath command line parameter is set or working directory is homepath")
os.Exit(1)
}
// load defaults
parsedFile, err := ini.Load(defaultConfigFile)
if err != nil {
fmt.Printf("Failed to parse defaults.ini, %v\n", err)
os.Exit(1)
return nil, err
}
parsedFile.BlockMode = false
// command line props
commandLineProps := getCommandLineProperties(args.Args)
// load default overrides
applyCommandLineDefaultProperties(commandLineProps, parsedFile)
// load specified config file
err = loadSpecifiedConfigFile(args.Config, parsedFile)
if err != nil {
err2 := cfg.initLogging(parsedFile)
if err2 != nil {
return nil, err2
}
log.Fatal(3, err.Error())
}
2014-10-04 06:33:20 -05:00
// apply environment overrides
err = applyEnvVariableOverrides(parsedFile)
if err != nil {
return nil, err
}
// apply command line overrides
applyCommandLineProperties(commandLineProps, parsedFile)
// evaluate config values containing environment variables
evalConfigValues(parsedFile)
// update data path and logging config
dataPath, err := valueAsString(parsedFile.Section("paths"), "data", "")
if err != nil {
return nil, err
}
cfg.DataPath = makeAbsolute(dataPath, HomePath)
err = cfg.initLogging(parsedFile)
if err != nil {
return nil, err
}
return parsedFile, err
}
func pathExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
func setHomePath(args *CommandLineArgs) {
if args.HomePath != "" {
HomePath = args.HomePath
return
}
HomePath, _ = filepath.Abs(".")
// check if homepath is correct
if pathExists(filepath.Join(HomePath, "conf/defaults.ini")) {
return
}
// try down one path
if pathExists(filepath.Join(HomePath, "../conf/defaults.ini")) {
HomePath = filepath.Join(HomePath, "../")
}
}
Remove redundancy in variable declarations (golint) This commit fixes the following golint warnings: pkg/api/avatar/avatar.go:229:12: should omit type *http.Client from declaration of var client; it will be inferred from the right-hand side pkg/login/brute_force_login_protection.go:13:26: should omit type time.Duration from declaration of var loginAttemptsWindow; it will be inferred from the right-hand side pkg/metrics/graphitebridge/graphite.go:58:26: should omit type []string from declaration of var metricCategoryPrefix; it will be inferred from the right-hand side pkg/metrics/graphitebridge/graphite.go:69:22: should omit type []string from declaration of var trimMetricPrefix; it will be inferred from the right-hand side pkg/models/alert.go:37:36: should omit type error from declaration of var ErrCannotChangeStateOnPausedAlert; it will be inferred from the right-hand side pkg/models/alert.go:38:36: should omit type error from declaration of var ErrRequiresNewState; it will be inferred from the right-hand side pkg/models/datasource.go:61:28: should omit type map[string]bool from declaration of var knownDatasourcePlugins; it will be inferred from the right-hand side pkg/plugins/update_checker.go:16:13: should omit type http.Client from declaration of var httpClient; it will be inferred from the right-hand side pkg/services/alerting/engine.go:103:24: should omit type time.Duration from declaration of var unfinishedWorkTimeout; it will be inferred from the right-hand side pkg/services/alerting/engine.go:105:19: should omit type time.Duration from declaration of var alertTimeout; it will be inferred from the right-hand side pkg/services/alerting/engine.go:106:19: should omit type int from declaration of var alertMaxAttempts; it will be inferred from the right-hand side pkg/services/alerting/notifier.go:143:23: should omit type map[string]*NotifierPlugin from declaration of var notifierFactories; it will be inferred from the right-hand side pkg/services/alerting/rule.go:136:24: should omit type map[string]ConditionFactory from declaration of var conditionFactories; it will be inferred from the right-hand side pkg/services/alerting/conditions/evaluator.go:12:15: should omit type []string from declaration of var defaultTypes; it will be inferred from the right-hand side pkg/services/alerting/conditions/evaluator.go:13:15: should omit type []string from declaration of var rangedTypes; it will be inferred from the right-hand side pkg/services/alerting/notifiers/opsgenie.go:44:19: should omit type string from declaration of var opsgenieAlertURL; it will be inferred from the right-hand side pkg/services/alerting/notifiers/pagerduty.go:43:23: should omit type string from declaration of var pagerdutyEventApiUrl; it will be inferred from the right-hand side pkg/services/alerting/notifiers/telegram.go:21:17: should omit type string from declaration of var telegramApiUrl; it will be inferred from the right-hand side pkg/services/provisioning/dashboards/config_reader_test.go:11:24: should omit type string from declaration of var simpleDashboardConfig; it will be inferred from the right-hand side pkg/services/provisioning/dashboards/config_reader_test.go:12:24: should omit type string from declaration of var oldVersion; it will be inferred from the right-hand side pkg/services/provisioning/dashboards/config_reader_test.go:13:24: should omit type string from declaration of var brokenConfigs; it will be inferred from the right-hand side pkg/services/provisioning/dashboards/file_reader.go:22:30: should omit type time.Duration from declaration of var checkDiskForChangesInterval; it will be inferred from the right-hand side pkg/services/provisioning/dashboards/file_reader.go:24:23: should omit type error from declaration of var ErrFolderNameMissing; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:15:34: should omit type string from declaration of var twoDatasourcesConfig; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:16:34: should omit type string from declaration of var twoDatasourcesConfigPurgeOthers; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:17:34: should omit type string from declaration of var doubleDatasourcesConfig; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:18:34: should omit type string from declaration of var allProperties; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:19:34: should omit type string from declaration of var versionZero; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:20:34: should omit type string from declaration of var brokenYaml; it will be inferred from the right-hand side pkg/services/sqlstore/stats.go:16:25: should omit type time.Duration from declaration of var activeUserTimeLimit; it will be inferred from the right-hand side pkg/services/sqlstore/migrator/mysql_dialect.go:69:14: should omit type bool from declaration of var hasLen1; it will be inferred from the right-hand side pkg/services/sqlstore/migrator/mysql_dialect.go:70:14: should omit type bool from declaration of var hasLen2; it will be inferred from the right-hand side pkg/services/sqlstore/migrator/postgres_dialect.go:95:14: should omit type bool from declaration of var hasLen1; it will be inferred from the right-hand side pkg/services/sqlstore/migrator/postgres_dialect.go:96:14: should omit type bool from declaration of var hasLen2; it will be inferred from the right-hand side pkg/setting/setting.go:42:15: should omit type string from declaration of var Env; it will be inferred from the right-hand side pkg/setting/setting.go:161:18: should omit type bool from declaration of var LdapAllowSignup; it will be inferred from the right-hand side pkg/setting/setting.go:473:30: should omit type bool from declaration of var skipStaticRootValidation; it will be inferred from the right-hand side pkg/tsdb/interval.go:14:21: should omit type time.Duration from declaration of var defaultMinInterval; it will be inferred from the right-hand side pkg/tsdb/interval.go:15:21: should omit type time.Duration from declaration of var year; it will be inferred from the right-hand side pkg/tsdb/interval.go:16:21: should omit type time.Duration from declaration of var day; it will be inferred from the right-hand side pkg/tsdb/cloudwatch/credentials.go:26:24: should omit type map[string]cache from declaration of var awsCredentialCache; it will be inferred from the right-hand side pkg/tsdb/influxdb/query.go:15:27: should omit type *regexp.Regexp from declaration of var regexpOperatorPattern; it will be inferred from the right-hand side pkg/tsdb/influxdb/query.go:16:27: should omit type *regexp.Regexp from declaration of var regexpMeasurementPattern; it will be inferred from the right-hand side pkg/tsdb/mssql/mssql_test.go:25:14: should omit type string from declaration of var serverIP; it will be inferred from the right-hand side
2018-04-27 15:14:36 -05:00
var skipStaticRootValidation = false
func NewCfg() *Cfg {
return &Cfg{
Logger: log.New("settings"),
Raw: ini.Empty(),
}
}
func (cfg *Cfg) validateStaticRootPath() error {
if skipStaticRootValidation {
return nil
}
if _, err := os.Stat(path.Join(StaticRootPath, "build")); err != nil {
cfg.Logger.Error("Failed to detect generated javascript files in public/build")
}
return nil
}
func (cfg *Cfg) Load(args *CommandLineArgs) error {
setHomePath(args)
2018-10-12 00:55:36 -05:00
iniFile, err := cfg.loadConfiguration(args)
if err != nil {
return err
}
cfg.Raw = iniFile
// Temporary keep global, to make refactor in steps
Raw = cfg.Raw
ApplicationName = APP_NAME
Env, err = valueAsString(iniFile.Section(""), "app_mode", "development")
if err != nil {
return err
}
InstanceName, err = valueAsString(iniFile.Section(""), "instance_name", "unknown_instance_name")
if err != nil {
return err
}
plugins, err := valueAsString(iniFile.Section("paths"), "plugins", "")
if err != nil {
return err
}
PluginsPath = makeAbsolute(plugins, HomePath)
provisioning, err := valueAsString(iniFile.Section("paths"), "provisioning", "")
if err != nil {
return err
}
cfg.ProvisioningPath = makeAbsolute(provisioning, HomePath)
server := iniFile.Section("server")
AppUrl, AppSubUrl, err = parseAppUrlAndSubUrl(server)
if err != nil {
return err
}
ServeFromSubPath = server.Key("serve_from_sub_path").MustBool(false)
2018-10-12 00:55:36 -05:00
cfg.AppUrl = AppUrl
cfg.AppSubUrl = AppSubUrl
cfg.ServeFromSubPath = ServeFromSubPath
2014-10-04 06:33:20 -05:00
Protocol = HTTP
protocolStr, err := valueAsString(server, "protocol", "http")
if err != nil {
return err
}
if protocolStr == "https" {
2014-10-04 06:33:20 -05:00
Protocol = HTTPS
CertFile = server.Key("cert_file").String()
KeyFile = server.Key("cert_key").String()
2014-10-04 06:33:20 -05:00
}
if protocolStr == "h2" {
Protocol = HTTP2
CertFile = server.Key("cert_file").String()
KeyFile = server.Key("cert_key").String()
}
if protocolStr == "socket" {
Protocol = SOCKET
SocketPath = server.Key("socket").String()
}
Domain, err = valueAsString(server, "domain", "localhost")
if err != nil {
return err
}
HttpAddr, err = valueAsString(server, "http_addr", DEFAULT_HTTP_ADDR)
if err != nil {
return err
}
HttpPort, err = valueAsString(server, "http_port", "3000")
if err != nil {
return err
}
RouterLogging = server.Key("router_logging").MustBool(false)
EnableGzip = server.Key("enable_gzip").MustBool(false)
EnforceDomain = server.Key("enforce_domain").MustBool(false)
staticRoot, err := valueAsString(server, "static_root_path", "")
if err != nil {
return err
}
StaticRootPath = makeAbsolute(staticRoot, HomePath)
if err := cfg.validateStaticRootPath(); err != nil {
return err
}
// read data proxy settings
dataproxy := iniFile.Section("dataproxy")
DataProxyLogging = dataproxy.Key("logging").MustBool(false)
DataProxyTimeout = dataproxy.Key("timeout").MustInt(30)
cfg.SendUserHeader = dataproxy.Key("send_user_header").MustBool(false)
// read security settings
security := iniFile.Section("security")
SecretKey, err = valueAsString(security, "secret_key", "")
if err != nil {
return err
}
DisableGravatar = security.Key("disable_gravatar").MustBool(true)
cfg.DisableBruteForceLoginProtection = security.Key("disable_brute_force_login_protection").MustBool(false)
DisableBruteForceLoginProtection = cfg.DisableBruteForceLoginProtection
CookieSecure = security.Key("cookie_secure").MustBool(false)
cfg.CookieSecure = CookieSecure
samesiteString, err := valueAsString(security, "cookie_samesite", "lax")
if err != nil {
return err
}
if samesiteString == "disabled" {
CookieSameSiteDisabled = true
cfg.CookieSameSiteDisabled = CookieSameSiteDisabled
} else {
validSameSiteValues := map[string]http.SameSite{
"lax": http.SameSiteLaxMode,
"strict": http.SameSiteStrictMode,
"none": http.SameSiteNoneMode,
}
if samesite, ok := validSameSiteValues[samesiteString]; ok {
CookieSameSiteMode = samesite
cfg.CookieSameSiteMode = CookieSameSiteMode
} else {
CookieSameSiteMode = http.SameSiteLaxMode
cfg.CookieSameSiteMode = CookieSameSiteMode
}
}
AllowEmbedding = security.Key("allow_embedding").MustBool(false)
ContentTypeProtectionHeader = security.Key("x_content_type_options").MustBool(false)
XSSProtectionHeader = security.Key("x_xss_protection").MustBool(false)
StrictTransportSecurity = security.Key("strict_transport_security").MustBool(false)
StrictTransportSecurityMaxAge = security.Key("strict_transport_security_max_age_seconds").MustInt(86400)
StrictTransportSecurityPreload = security.Key("strict_transport_security_preload").MustBool(false)
StrictTransportSecuritySubDomains = security.Key("strict_transport_security_subdomains").MustBool(false)
// read snapshots settings
snapshots := iniFile.Section("snapshots")
ExternalSnapshotUrl, err = valueAsString(snapshots, "external_snapshot_url", "")
if err != nil {
return err
}
ExternalSnapshotName, err = valueAsString(snapshots, "external_snapshot_name", "")
if err != nil {
return err
}
ExternalEnabled = snapshots.Key("external_enabled").MustBool(true)
SnapShotRemoveExpired = snapshots.Key("snapshot_remove_expired").MustBool(true)
SnapshotPublicMode = snapshots.Key("public_mode").MustBool(false)
// read dashboard settings
dashboards := iniFile.Section("dashboards")
DashboardVersionsToKeep = dashboards.Key("versions_to_keep").MustInt(20)
MinRefreshInterval, err = valueAsString(dashboards, "min_refresh_interval", "5s")
if err != nil {
return err
}
// read data source proxy white list
DataProxyWhiteList = make(map[string]bool)
securityStr, err := valueAsString(security, "data_source_proxy_whitelist", "")
if err != nil {
return err
}
for _, hostAndIp := range util.SplitString(securityStr) {
DataProxyWhiteList[hostAndIp] = true
}
2015-01-27 08:45:27 -06:00
// admin
cfg.DisableInitAdminCreation = security.Key("disable_initial_admin_creation").MustBool(false)
AdminUser, err = valueAsString(security, "admin_user", "")
if err != nil {
return err
}
AdminPassword, err = valueAsString(security, "admin_password", "")
if err != nil {
return err
}
2015-01-27 08:45:27 -06:00
// users
users := iniFile.Section("users")
AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
AutoAssignOrgId = users.Key("auto_assign_org_id").MustInt(1)
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
LoginHint, err = valueAsString(users, "login_hint", "")
if err != nil {
return err
}
PasswordHint, err = valueAsString(users, "password_hint", "")
if err != nil {
return err
}
DefaultTheme, err = valueAsString(users, "default_theme", "")
if err != nil {
return err
}
ExternalUserMngLinkUrl, err = valueAsString(users, "external_manage_link_url", "")
if err != nil {
return err
}
ExternalUserMngLinkName, err = valueAsString(users, "external_manage_link_name", "")
if err != nil {
return err
}
ExternalUserMngInfo, err = valueAsString(users, "external_manage_info", "")
if err != nil {
return err
}
ViewersCanEdit = users.Key("viewers_can_edit").MustBool(false)
2019-03-12 01:32:47 -05:00
cfg.EditorsCanAdmin = users.Key("editors_can_admin").MustBool(false)
// auth
auth := iniFile.Section("auth")
LoginCookieName, err = valueAsString(auth, "login_cookie_name", "grafana_session")
cfg.LoginCookieName = LoginCookieName
if err != nil {
return err
}
cfg.LoginMaxInactiveLifetimeDays = auth.Key("login_maximum_inactive_lifetime_days").MustInt(7)
LoginMaxLifetimeDays = auth.Key("login_maximum_lifetime_days").MustInt(30)
cfg.LoginMaxLifetimeDays = LoginMaxLifetimeDays
Auth: Allow expiration of API keys (#17678) * Modify backend to allow expiration of API Keys * Add middleware test for expired api keys * Modify frontend to enable expiration of API Keys * Fix frontend tests * Fix migration and add index for `expires` field * Add api key tests for database access * Substitude time.Now() by a mock for test usage * Front-end modifications * Change input label to `Time to live` * Change input behavior to comply with the other similar * Add tooltip * Modify AddApiKey api call response Expiration should be *time.Time instead of string * Present expiration date in the selected timezone * Use kbn for transforming intervals to seconds * Use `assert` library for tests * Frontend fixes Add checks for empty/undefined/null values * Change expires column from datetime to integer * Restrict api key duration input It should be interval not number * AddApiKey must complain if SecondsToLive is negative * Declare ErrInvalidApiKeyExpiration * Move configuration to auth section * Update docs * Eliminate alias for models in modified files * Omit expiration from api response if empty * Eliminate Goconvey from test file * Fix test Do not sleep, use mocked timeNow() instead * Remove index for expires from api_key table The index should be anyway on both org_id and expires fields. However this commit eliminates completely the index for now since not many rows are expected to be in this table. * Use getTimeZone function * Minor change in api key listing The frontend should display a message instead of empty string if the key does not expire.
2019-06-26 01:47:03 -05:00
cfg.ApiKeyMaxSecondsToLive = auth.Key("api_key_max_seconds_to_live").MustInt64(-1)
cfg.TokenRotationIntervalMinutes = auth.Key("token_rotation_interval_minutes").MustInt(10)
if cfg.TokenRotationIntervalMinutes < 2 {
cfg.TokenRotationIntervalMinutes = 2
}
DisableLoginForm = auth.Key("disable_login_form").MustBool(false)
DisableSignoutMenu = auth.Key("disable_signout_menu").MustBool(false)
OAuthAutoLogin = auth.Key("oauth_auto_login").MustBool(false)
cfg.OAuthCookieMaxAge = auth.Key("oauth_state_cookie_max_age").MustInt(60)
SignoutRedirectUrl, err = valueAsString(auth, "signout_redirect_url", "")
if err != nil {
return err
}
2019-07-05 09:39:52 -05:00
// SAML auth
cfg.SAMLEnabled = iniFile.Section("auth.saml").Key("enabled").MustBool(false)
2015-01-27 08:45:27 -06:00
// anonymous access
AnonymousEnabled = iniFile.Section("auth.anonymous").Key("enabled").MustBool(false)
AnonymousOrgName, err = valueAsString(iniFile.Section("auth.anonymous"), "org_name", "")
if err != nil {
return err
}
AnonymousOrgRole, err = valueAsString(iniFile.Section("auth.anonymous"), "org_role", "")
if err != nil {
return err
}
// auth proxy
authProxy := iniFile.Section("auth.proxy")
AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)
AuthProxyHeaderName, err = valueAsString(authProxy, "header_name", "")
if err != nil {
return err
}
AuthProxyHeaderProperty, err = valueAsString(authProxy, "header_property", "")
if err != nil {
return err
}
AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
AuthProxyEnableLoginToken = authProxy.Key("enable_login_token").MustBool(false)
ldapSyncVal := authProxy.Key("ldap_sync_ttl").MustInt()
syncVal := authProxy.Key("sync_ttl").MustInt()
if ldapSyncVal != AUTH_PROXY_SYNC_TTL {
AuthProxySyncTtl = ldapSyncVal
cfg.Logger.Warn("[Deprecated] the configuration setting 'ldap_sync_ttl' is deprecated, please use 'sync_ttl' instead")
} else {
AuthProxySyncTtl = syncVal
}
AuthProxyWhitelist, err = valueAsString(authProxy, "whitelist", "")
if err != nil {
return err
}
AuthProxyHeaders = make(map[string]string)
headers, err := valueAsString(authProxy, "headers", "")
if err != nil {
return err
}
for _, propertyAndHeader := range util.SplitString(headers) {
split := strings.SplitN(propertyAndHeader, ":", 2)
if len(split) == 2 {
AuthProxyHeaders[split[0]] = split[1]
}
}
// basic auth
authBasic := iniFile.Section("auth.basic")
2015-06-30 05:14:13 -05:00
BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
// Rendering
renderSec := iniFile.Section("rendering")
cfg.RendererUrl, err = valueAsString(renderSec, "server_url", "")
if err != nil {
return err
}
cfg.RendererCallbackUrl, err = valueAsString(renderSec, "callback_url", "")
if err != nil {
return err
}
improve remote image rendering (#13102) * improve remote image rendering - determine "domain" during Init() so we are not re-parsing settings on every request - if using http-mode via a rednererUrl, then use the AppUrl for the page that the renderer loads. When in http-mode the renderer is likely running on another server so trying to use the localhost or even the specific IP:PORT grafana is listening on wont work. - apply the request timeout via a context rather then directly on the http client. - use a global http client so we can take advantage of connection re-use - log and handle errors better. * ensure imagesDir exists * allow users to define callback_url for remote rendering - allow users to define the url that a remote rendering service should use for connecting back to the grafana instance. By default the "root_url" is used. * improve remote image rendering - determine "domain" during Init() so we are not re-parsing settings on every request - if using http-mode via a rednererUrl, then use the AppUrl for the page that the renderer loads. When in http-mode the renderer is likely running on another server so trying to use the localhost or even the specific IP:PORT grafana is listening on wont work. - apply the request timeout via a context rather then directly on the http client. - use a global http client so we can take advantage of connection re-use - log and handle errors better. * ensure imagesDir exists * allow users to define callback_url for remote rendering - allow users to define the url that a remote rendering service should use for connecting back to the grafana instance. By default the "root_url" is used. * rendering: fixed issue with renderKey where userId and orgId was in mixed up, added test for RenderCallbackUrl reading logic
2018-09-04 06:42:55 -05:00
if cfg.RendererCallbackUrl == "" {
cfg.RendererCallbackUrl = AppUrl
} else {
if cfg.RendererCallbackUrl[len(cfg.RendererCallbackUrl)-1] != '/' {
cfg.RendererCallbackUrl += "/"
}
_, err := url.Parse(cfg.RendererCallbackUrl)
if err != nil {
log.Fatal(4, "Invalid callback_url(%s): %s", cfg.RendererCallbackUrl, err)
}
}
cfg.RendererConcurrentRequestLimit = renderSec.Key("concurrent_render_request_limit").MustInt(30)
2018-10-12 00:55:36 -05:00
cfg.ImagesDir = filepath.Join(cfg.DataPath, "png")
cfg.TempDataLifetime = iniFile.Section("paths").Key("temp_data_lifetime").MustDuration(time.Second * 3600 * 24)
cfg.MetricsEndpointEnabled = iniFile.Section("metrics").Key("enabled").MustBool(true)
cfg.MetricsEndpointBasicAuthUsername, err = valueAsString(iniFile.Section("metrics"), "basic_auth_username", "")
if err != nil {
return err
}
cfg.MetricsEndpointBasicAuthPassword, err = valueAsString(iniFile.Section("metrics"), "basic_auth_password", "")
if err != nil {
return err
}
cfg.MetricsEndpointDisableTotalStats = iniFile.Section("metrics").Key("disable_total_stats").MustBool(false)
2014-11-14 10:13:33 -06:00
analytics := iniFile.Section("analytics")
2015-03-27 11:13:44 -05:00
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
CheckForUpdates = analytics.Key("check_for_updates").MustBool(true)
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
GoogleTagManagerId = analytics.Key("google_tag_manager_id").String()
2015-03-22 14:14:00 -05:00
alerting := iniFile.Section("alerting")
AlertingEnabled = alerting.Key("enabled").MustBool(true)
ExecuteAlerts = alerting.Key("execute_alerts").MustBool(true)
AlertingRenderLimit = alerting.Key("concurrent_render_limit").MustInt(5)
AlertingErrorOrTimeout, err = valueAsString(alerting, "error_or_timeout", "alerting")
if err != nil {
return err
}
AlertingNoDataOrNullValues, err = valueAsString(alerting, "nodata_or_nullvalues", "no_data")
if err != nil {
return err
}
evaluationTimeoutSeconds := alerting.Key("evaluation_timeout_seconds").MustInt64(30)
AlertingEvaluationTimeout = time.Second * time.Duration(evaluationTimeoutSeconds)
notificationTimeoutSeconds := alerting.Key("notification_timeout_seconds").MustInt64(30)
AlertingNotificationTimeout = time.Second * time.Duration(notificationTimeoutSeconds)
AlertingMaxAttempts = alerting.Key("max_attempts").MustInt(3)
AlertingMinInterval = alerting.Key("min_interval_seconds").MustInt64(1)
explore := iniFile.Section("explore")
ExploreEnabled = explore.Key("enabled").MustBool(true)
2018-04-27 04:39:14 -05:00
panelsSection := iniFile.Section("panels")
cfg.DisableSanitizeHtml = panelsSection.Key("disable_sanitize_html").MustBool(false)
pluginsSection := iniFile.Section("plugins")
cfg.PluginsEnableAlpha = pluginsSection.Key("enable_alpha").MustBool(false)
cfg.PluginsAppsSkipVerifyTLS = pluginsSection.Key("app_tls_skip_verify_insecure").MustBool(false)
cfg.PluginSettings = extractPluginSettings(iniFile.Sections())
// Read and populate feature toggles list
featureTogglesSection := iniFile.Section("feature_toggles")
cfg.FeatureToggles = make(map[string]bool)
featuresTogglesStr, err := valueAsString(featureTogglesSection, "enable", "")
if err != nil {
return err
}
for _, feature := range util.SplitString(featuresTogglesStr) {
cfg.FeatureToggles[feature] = true
}
FeatureToggles = cfg.FeatureToggles
// check old location for this option
if panelsSection.Key("enable_alpha").MustBool(false) {
cfg.PluginsEnableAlpha = true
}
cfg.readLDAPConfig()
cfg.readSessionConfig()
cfg.readSmtpSettings()
cfg.readQuotaSettings()
if VerifyEmailEnabled && !cfg.Smtp.Enabled {
2017-12-28 08:51:15 -06:00
log.Warn("require_email_validation is enabled but smtp is disabled")
}
// check old key name
GrafanaComUrl, err = valueAsString(iniFile.Section("grafana_net"), "url", "")
if err != nil {
return err
}
if GrafanaComUrl == "" {
GrafanaComUrl, err = valueAsString(iniFile.Section("grafana_com"), "url", "https://grafana.com")
if err != nil {
return err
}
}
imageUploadingSection := iniFile.Section("external_image_storage")
ImageUploadProvider, err = valueAsString(imageUploadingSection, "provider", "")
if err != nil {
return err
}
enterprise := iniFile.Section("enterprise")
cfg.EnterpriseLicensePath, err = valueAsString(enterprise, "license_path", filepath.Join(cfg.DataPath, "license.jwt"))
if err != nil {
return err
}
2019-03-08 13:49:16 -06:00
cacheServer := iniFile.Section("remote_cache")
dbName, err := valueAsString(cacheServer, "type", "database")
if err != nil {
return err
}
connStr, err := valueAsString(cacheServer, "connstr", "")
if err != nil {
return err
}
2019-03-08 13:49:16 -06:00
cfg.RemoteCacheOptions = &RemoteCacheOptions{
Name: dbName,
ConnStr: connStr,
2019-03-03 14:48:00 -06:00
}
return nil
}
2014-12-30 03:28:27 -06:00
func valueAsString(section *ini.Section, keyName string, defaultValue string) (value string, err error) {
defer func() {
if err_ := recover(); err_ != nil {
err = errors.New("Invalid value for key '" + keyName + "' in configuration file")
}
}()
return section.Key(keyName).MustString(defaultValue), nil
}
2019-03-08 13:49:16 -06:00
type RemoteCacheOptions struct {
2019-03-03 14:48:00 -06:00
Name string
ConnStr string
}
func (cfg *Cfg) readLDAPConfig() {
ldapSec := cfg.Raw.Section("auth.ldap")
LDAPConfigFile = ldapSec.Key("config_file").String()
LDAPSyncCron = ldapSec.Key("sync_cron").String()
LDAPEnabled = ldapSec.Key("enabled").MustBool(false)
LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false)
LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true)
}
func (cfg *Cfg) readSessionConfig() {
sec, _ := cfg.Raw.GetSection("session")
if sec != nil {
cfg.Logger.Warn(
"[Removed] Session setting was removed in v6.2, use remote_cache option instead",
)
}
2014-10-05 09:50:04 -05:00
}
func (cfg *Cfg) initLogging(file *ini.File) error {
logModeStr, err := valueAsString(file.Section("log"), "mode", "console")
if err != nil {
return err
}
2016-06-07 05:11:41 -05:00
// split on comma
logModes := strings.Split(logModeStr, ",")
2016-06-07 05:11:41 -05:00
// also try space
2018-10-12 00:55:36 -05:00
if len(logModes) == 1 {
logModes = strings.Split(logModeStr, " ")
}
logsPath, err := valueAsString(file.Section("paths"), "logs", "")
if err != nil {
return err
}
cfg.LogsPath = makeAbsolute(logsPath, HomePath)
return log.ReadLoggingConfig(logModes, cfg.LogsPath, file)
}
func (cfg *Cfg) LogConfigSources() {
var text bytes.Buffer
for _, file := range configFiles {
cfg.Logger.Info("Config loaded from", "file", file)
}
if len(appliedCommandLineProperties) > 0 {
for _, prop := range appliedCommandLineProperties {
cfg.Logger.Info("Config overridden from command line", "arg", prop)
}
}
if len(appliedEnvOverrides) > 0 {
text.WriteString("\tEnvironment variables used:\n")
for _, prop := range appliedEnvOverrides {
cfg.Logger.Info("Config overridden from Environment variable", "var", prop)
}
}
cfg.Logger.Info("Path Home", "path", HomePath)
cfg.Logger.Info("Path Data", "path", cfg.DataPath)
cfg.Logger.Info("Path Logs", "path", cfg.LogsPath)
cfg.Logger.Info("Path Plugins", "path", PluginsPath)
cfg.Logger.Info("Path Provisioning", "path", cfg.ProvisioningPath)
cfg.Logger.Info("App mode " + Env)
}
type DynamicSection struct {
section *ini.Section
Logger log.Logger
}
// Key dynamically overrides keys with environment variables.
// As a side effect, the value of the setting key will be updated if an environment variable is present.
func (s *DynamicSection) Key(k string) *ini.Key {
envKey := envKey(s.section.Name(), k)
envValue := os.Getenv(envKey)
key := s.section.Key(k)
if len(envValue) == 0 {
return key
}
key.SetValue(envValue)
if shouldRedactKey(envKey) {
envValue = REDACTED_PASSWORD
}
s.Logger.Info("Config overridden from Environment variable", "var", fmt.Sprintf("%s=%s", envKey, envValue))
return key
}
// SectionWithEnvOverrides dynamically overrides keys with environment variables.
// As a side effect, the value of the setting key will be updated if an environment variable is present.
func (cfg *Cfg) SectionWithEnvOverrides(s string) *DynamicSection {
return &DynamicSection{cfg.Raw.Section(s), cfg.Logger}
}
func IsExpressionsEnabled() bool {
v, ok := FeatureToggles["expressions"]
if !ok {
return false
}
return v
}