grafana/pkg/setting/setting.go

1420 lines
43 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"
"runtime"
"strconv"
2014-10-04 06:33:20 -05:00
"strings"
"time"
"github.com/prometheus/common/model"
ini "gopkg.in/ini.v1"
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
"github.com/grafana/grafana/pkg/components/gtime"
"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 (
HTTPScheme Scheme = "http"
HTTPSScheme Scheme = "https"
HTTP2Scheme Scheme = "h2"
SocketScheme Scheme = "socket"
2014-10-04 06:33:20 -05:00
)
2014-12-16 05:04:08 -06:00
const (
redactedPassword = "*********"
DefaultHTTPAddr = "0.0.0.0"
Dev = "development"
Prod = "production"
Test = "test"
2014-12-16 05:04:08 -06:00
)
// 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 (
authProxySyncTTL = 60
)
// zoneInfo names environment variable for setting the path to look for the timezone database in go
const zoneInfo = "ZONEINFO"
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
CustomInitPath = "conf/custom.ini"
// HTTP server options
Fix: Add additional settings for dataproxy to help with network proxy timeouts (#27841) * adding additional settings for datasource cache transport * added documentation for the new changes * fixing small typo in defaults.ini comment * fixing small typo in configuration.md comment * Update conf/defaults.ini keepalive comment per review Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update conf/defaults.ini idle conn comment per review Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update conf/defaults.ini anon user comment per review Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/administration/configuration.md idle conn comment per review Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * adding suggestions from papagian * fixing configuration.md * fixing configuration.md typo * Apply suggestions from code review aknuds1 Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * updating sample.ini * Apply suggestions for docs from code review papagian Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com> * Update docs/sources/administration/configuration.md fix typo Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com> Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com>
2020-10-12 03:36:47 -05:00
DataProxyLogging bool
DataProxyTimeout int
DataProxyTLSHandshakeTimeout int
DataProxyExpectContinueTimeout int
DataProxyMaxIdleConns int
DataProxyKeepAlive int
DataProxyIdleConnTimeout int
StaticRootPath string
2014-10-05 09:50:04 -05:00
// Security settings.
SecretKey string
DisableGravatar bool
EmailCodeValidMinutes int
DataProxyWhiteList map[string]bool
CookieSecure bool
CookieSameSiteDisabled bool
CookieSameSiteMode http.SameSite
// Snapshots
ExternalSnapshotUrl string
ExternalSnapshotName string
ExternalEnabled bool
SnapShotRemoveExpired 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
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
SigV4AuthEnabled bool
2015-01-27 08:45:27 -06:00
AnonymousEnabled bool
// Auth proxy settings
AuthProxyEnabled bool
AuthProxyHeaderProperty string
// Basic Auth
BasicAuthEnabled bool
2014-10-04 06:33:20 -05:00
// Global setting objects.
Raw *ini.File
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
// analytics
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
// Quota
2015-09-10 12:47:33 -05:00
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
ImageUploadProvider string
2014-10-04 06:33:20 -05:00
)
// AddChangePasswordLink returns if login form is disabled or not since
// the same intention can be used to hide both features.
func AddChangePasswordLink() bool {
return !DisableLoginForm
}
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
CertFile string
KeyFile string
HTTPAddr string
HTTPPort string
AppURL string
AppSubURL string
ServeFromSubPath bool
StaticRootPath string
Protocol Scheme
SocketPath string
RouterLogging bool
Domain string
CDNRootURL *url.URL
ReadTimeout time.Duration
EnableGzip bool
EnforceDomain bool
2018-10-12 00:55:36 -05:00
// build
BuildVersion string
BuildCommit string
BuildBranch string
BuildStamp int64
IsEnterprise bool
// packaging
Packaging string
// Paths
ProvisioningPath string
DataPath string
LogsPath string
PluginsPath string
BundledPluginsPath 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
AllowEmbedding bool
XSSProtectionHeader bool
ContentTypeProtectionHeader bool
StrictTransportSecurity bool
StrictTransportSecurityMaxAge int
StrictTransportSecurityPreload bool
StrictTransportSecuritySubDomains bool
// CSPEnabled toggles Content Security Policy support.
CSPEnabled bool
// CSPTemplate contains the Content Security Policy template.
CSPTemplate string
TempDataLifetime time.Duration
PluginsEnableAlpha bool
PluginsAppsSkipVerifyTLS bool
PluginSettings PluginSettings
PluginsAllowUnsigned []string
MarketplaceURL string
DisableSanitizeHtml bool
EnterpriseLicensePath string
// Metrics
MetricsEndpointEnabled bool
MetricsEndpointBasicAuthUsername string
MetricsEndpointBasicAuthPassword string
MetricsEndpointDisableTotalStats bool
MetricsGrafanaEnvironmentInfo map[string]string
// Dashboards
DefaultHomeDashboardPath string
// Auth
LoginCookieName string
LoginMaxInactiveLifetime time.Duration
LoginMaxLifetime time.Duration
TokenRotationIntervalMinutes int
SigV4AuthEnabled bool
BasicAuthEnabled bool
AdminUser string
AdminPassword string
AWS: Add aws plugin configuration (#31312) * add new conf and make sure its passed to frontend * change auth provider name * goimports * fixed after feedback * more updates after feedback * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update conf/sample.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update docs/sources/administration/configuration.md Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/setting/setting.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * updates after pr feedback * Update conf/defaults.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update conf/defaults.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update conf/sample.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
2021-02-24 11:08:13 -06:00
// AWS Plugin Auth
AWSAllowedAuthProviders []string
AWSAssumeRoleEnabled bool
AWSListMetricsPageLimit int
AWS: Add aws plugin configuration (#31312) * add new conf and make sure its passed to frontend * change auth provider name * goimports * fixed after feedback * more updates after feedback * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update conf/sample.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update docs/sources/administration/configuration.md Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/setting/setting.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * updates after pr feedback * Update conf/defaults.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update conf/defaults.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update conf/sample.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
2021-02-24 11:08:13 -06:00
// Auth proxy settings
AuthProxyEnabled bool
AuthProxyHeaderName string
AuthProxyHeaderProperty string
AuthProxyAutoSignUp bool
AuthProxyEnableLoginToken bool
AuthProxyWhitelist string
AuthProxyHeaders map[string]string
AuthProxySyncTTL int
// OAuth
OAuthCookieMaxAge int
// JWT Auth
JWTAuthEnabled bool
JWTAuthHeaderName string
JWTAuthEmailClaim string
JWTAuthUsernameClaim string
JWTAuthExpectClaims string
JWTAuthJWKSetURL string
JWTAuthCacheTTL time.Duration
JWTAuthKeyFile string
JWTAuthJWKSetFile string
// 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
AnonymousEnabled bool
AnonymousOrgName string
AnonymousOrgRole string
AnonymousHideVersion bool
DateFormats DateFormats
// User
UserInviteMaxLifetime time.Duration
HiddenUsers map[string]struct{}
// Annotations
AnnotationCleanupJobBatchSize int64
AlertingAnnotationCleanupSetting AnnotationCleanupSettings
DashboardAnnotationCleanupSettings AnnotationCleanupSettings
APIAnnotationCleanupSettings AnnotationCleanupSettings
// Sentry config
Sentry Sentry
// Data sources
DataSourceLimit int
// Snapshots
SnapshotPublicMode bool
ErrTemplateName string
Env string
// Analytics
CheckForUpdates bool
ReportingDistributor string
ReportingEnabled bool
// LDAP
LDAPEnabled bool
LDAPAllowSignup bool
Quota QuotaSettings
DefaultTheme string
HomePage string
AutoAssignOrg bool
AutoAssignOrgId int
AutoAssignOrgRole string
// ExpressionsEnabled specifies whether expressions are enabled.
ExpressionsEnabled bool
ImageUploadProvider string
}
// IsLiveEnabled returns if grafana live should be enabled
func (cfg Cfg) IsLiveEnabled() bool {
return cfg.FeatureToggles["live"]
}
// IsNgAlertEnabled returns whether the standalone alerts feature is enabled.
func (cfg Cfg) IsNgAlertEnabled() bool {
return cfg.FeatureToggles["ngalert"]
}
// IsTrimDefaultsEnabled returns whether the standalone trim dashboard default feature is enabled.
func (cfg Cfg) IsTrimDefaultsEnabled() bool {
return cfg.FeatureToggles["trimDefaults"]
}
// IsDatabaseMetricsEnabled returns whether the database instrumentation feature is enabled.
func (cfg Cfg) IsDatabaseMetricsEnabled() bool {
return cfg.FeatureToggles["database_metrics"]
}
// IsHTTPRequestHistogramEnabled returns whether the http_request_histogram feature is enabled.
func (cfg Cfg) IsHTTPRequestHistogramEnabled() bool {
return cfg.FeatureToggles["http_request_histogram"]
}
// IsPanelLibraryEnabled returns whether the panel library feature is enabled.
func (cfg Cfg) IsPanelLibraryEnabled() bool {
return cfg.FeatureToggles["panelLibrary"]
}
type CommandLineArgs struct {
Config string
HomePath string
Args []string
}
func parseAppUrlAndSubUrl(section *ini.Section) (string, string, error) {
appUrl := valueAsString(section, "root_url", "http://localhost:3000/")
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.Fatalf(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 = redactedPassword
}
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 (cfg *Cfg) readGrafanaEnvironmentMetrics() error {
environmentMetricsSection := cfg.Raw.Section("metrics.environment_info")
keys := environmentMetricsSection.Keys()
cfg.MetricsGrafanaEnvironmentInfo = make(map[string]string, len(keys))
for _, key := range keys {
labelName := model.LabelName(key.Name())
labelValue := model.LabelValue(key.Value())
if !labelName.IsValid() {
return fmt.Errorf("invalid label name in [metrics.environment_info] configuration. name %q", labelName)
}
if !labelValue.IsValid() {
return fmt.Errorf("invalid label value in [metrics.environment_info] configuration. name %q value %q", labelName, labelValue)
}
cfg.MetricsGrafanaEnvironmentInfo[string(labelName)] = string(labelValue)
}
return nil
}
func (cfg *Cfg) readAnnotationSettings() {
section := cfg.Raw.Section("annotations")
cfg.AnnotationCleanupJobBatchSize = section.Key("cleanupjob_batchsize").MustInt64(100)
dashboardAnnotation := cfg.Raw.Section("annotations.dashboard")
apiIAnnotation := cfg.Raw.Section("annotations.api")
alertingSection := cfg.Raw.Section("alerting")
var newAnnotationCleanupSettings = func(section *ini.Section, maxAgeField string) AnnotationCleanupSettings {
maxAge, err := gtime.ParseDuration(section.Key(maxAgeField).MustString(""))
if err != nil {
maxAge = 0
}
return AnnotationCleanupSettings{
MaxAge: maxAge,
MaxCount: section.Key("max_annotations_to_keep").MustInt64(0),
}
}
cfg.AlertingAnnotationCleanupSetting = newAnnotationCleanupSettings(alertingSection, "max_annotation_age")
cfg.DashboardAnnotationCleanupSettings = newAnnotationCleanupSettings(dashboardAnnotation, "max_age")
cfg.APIAnnotationCleanupSettings = newAnnotationCleanupSettings(apiIAnnotation, "max_age")
}
func (cfg *Cfg) readExpressionsSettings() {
expressions := cfg.Raw.Section("expressions")
cfg.ExpressionsEnabled = expressions.Key("enabled").MustBool(true)
}
type AnnotationCleanupSettings struct {
MaxAge time.Duration
MaxCount int64
}
func envKey(sectionName string, keyName string) string {
sN := strings.ToUpper(strings.ReplaceAll(sectionName, ".", "_"))
sN = strings.ReplaceAll(sN, "-", "_")
kN := strings.ToUpper(strings.ReplaceAll(keyName, ".", "_"))
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 = redactedPassword
}
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 {
log.Fatalf(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 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 %q: %w", 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) {
// 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.Fatalf(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
err = expandConfig(parsedFile)
if err != nil {
return nil, err
}
// update data path and logging config
dataPath := valueAsString(parsedFile.Section("paths"), "data", "")
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
}
var err error
HomePath, err = filepath.Abs(".")
if err != nil {
panic(err)
}
// 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(),
}
}
var theCfg *Cfg
// GetCfg gets the Cfg singleton.
// XXX: This is only required for integration tests so that the configuration can be reset for each test,
// as due to how the current DI framework functions, we can't create a new Cfg object every time (the services
// constituting the DI graph, and referring to a Cfg instance, get created only once).
func GetCfg() *Cfg {
if theCfg != nil {
return theCfg
}
theCfg = NewCfg()
return theCfg
}
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)
// Fix for missing IANA db on Windows
_, zoneInfoSet := os.LookupEnv(zoneInfo)
if runtime.GOOS == "windows" && !zoneInfoSet {
if err := os.Setenv(zoneInfo, filepath.Join(HomePath, "tools", "zoneinfo.zip")); err != nil {
cfg.Logger.Error("Can't set ZONEINFO environment variable", "err", err)
}
}
2018-10-12 00:55:36 -05:00
iniFile, err := cfg.loadConfiguration(args)
if err != nil {
return err
}
cfg.Raw = iniFile
// Temporarily keep global, to make refactor in steps
Raw = cfg.Raw
cfg.BuildVersion = BuildVersion
cfg.BuildCommit = BuildCommit
cfg.BuildStamp = BuildStamp
cfg.BuildBranch = BuildBranch
cfg.IsEnterprise = IsEnterprise
cfg.Packaging = Packaging
cfg.ErrTemplateName = "error"
ApplicationName = "Grafana"
Env = valueAsString(iniFile.Section(""), "app_mode", "development")
cfg.Env = Env
InstanceName = valueAsString(iniFile.Section(""), "instance_name", "unknown_instance_name")
plugins := valueAsString(iniFile.Section("paths"), "plugins", "")
cfg.PluginsPath = makeAbsolute(plugins, HomePath)
cfg.BundledPluginsPath = makeAbsolute("plugins-bundled", HomePath)
provisioning := valueAsString(iniFile.Section("paths"), "provisioning", "")
cfg.ProvisioningPath = makeAbsolute(provisioning, HomePath)
if err := cfg.readServerSettings(iniFile); err != nil {
return err
}
// read data proxy settings
dataproxy := iniFile.Section("dataproxy")
DataProxyLogging = dataproxy.Key("logging").MustBool(false)
DataProxyTimeout = dataproxy.Key("timeout").MustInt(30)
Fix: Add additional settings for dataproxy to help with network proxy timeouts (#27841) * adding additional settings for datasource cache transport * added documentation for the new changes * fixing small typo in defaults.ini comment * fixing small typo in configuration.md comment * Update conf/defaults.ini keepalive comment per review Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update conf/defaults.ini idle conn comment per review Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update conf/defaults.ini anon user comment per review Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/administration/configuration.md idle conn comment per review Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * adding suggestions from papagian * fixing configuration.md * fixing configuration.md typo * Apply suggestions from code review aknuds1 Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * updating sample.ini * Apply suggestions for docs from code review papagian Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com> * Update docs/sources/administration/configuration.md fix typo Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com> Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com>
2020-10-12 03:36:47 -05:00
DataProxyKeepAlive = dataproxy.Key("keep_alive_seconds").MustInt(30)
DataProxyTLSHandshakeTimeout = dataproxy.Key("tls_handshake_timeout_seconds").MustInt(10)
DataProxyExpectContinueTimeout = dataproxy.Key("expect_continue_timeout_seconds").MustInt(1)
DataProxyMaxIdleConns = dataproxy.Key("max_idle_connections").MustInt(100)
DataProxyIdleConnTimeout = dataproxy.Key("idle_conn_timeout_seconds").MustInt(90)
cfg.SendUserHeader = dataproxy.Key("send_user_header").MustBool(false)
if err := readSecuritySettings(iniFile, cfg); err != nil {
return err
}
if err := readSnapshotsSettings(cfg, iniFile); err != nil {
return err
}
// read dashboard settings
dashboards := iniFile.Section("dashboards")
DashboardVersionsToKeep = dashboards.Key("versions_to_keep").MustInt(20)
MinRefreshInterval = valueAsString(dashboards, "min_refresh_interval", "5s")
cfg.DefaultHomeDashboardPath = dashboards.Key("default_home_dashboard_path").MustString("")
2014-10-04 06:33:20 -05:00
if err := readUserSettings(iniFile, cfg); err != nil {
return err
}
if err := readAuthSettings(iniFile, cfg); err != nil {
return err
}
if err := readRenderingSettings(iniFile, cfg); err != nil {
return err
}
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 = valueAsString(iniFile.Section("metrics"), "basic_auth_username", "")
cfg.MetricsEndpointBasicAuthPassword = valueAsString(iniFile.Section("metrics"), "basic_auth_password", "")
cfg.MetricsEndpointDisableTotalStats = iniFile.Section("metrics").Key("disable_total_stats").MustBool(false)
analytics := iniFile.Section("analytics")
cfg.CheckForUpdates = analytics.Key("check_for_updates").MustBool(true)
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
GoogleTagManagerId = analytics.Key("google_tag_manager_id").String()
cfg.ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
cfg.ReportingDistributor = analytics.Key("reporting_distributor").MustString("grafana-labs")
if len(cfg.ReportingDistributor) >= 100 {
cfg.ReportingDistributor = cfg.ReportingDistributor[:100]
}
if err := readAlertingSettings(iniFile); err != nil {
return err
}
explore := iniFile.Section("explore")
ExploreEnabled = explore.Key("enabled").MustBool(true)
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())
pluginsAllowUnsigned := pluginsSection.Key("allow_loading_unsigned_plugins").MustString("")
for _, plug := range strings.Split(pluginsAllowUnsigned, ",") {
plug = strings.TrimSpace(plug)
cfg.PluginsAllowUnsigned = append(cfg.PluginsAllowUnsigned, plug)
}
cfg.MarketplaceURL = pluginsSection.Key("marketplace_url").MustString("https://grafana.com/grafana/plugins/")
// Read and populate feature toggles list
featureTogglesSection := iniFile.Section("feature_toggles")
cfg.FeatureToggles = make(map[string]bool)
featuresTogglesStr := valueAsString(featureTogglesSection, "enable", "")
for _, feature := range util.SplitString(featuresTogglesStr) {
cfg.FeatureToggles[feature] = true
}
// check old location for this option
if panelsSection.Key("enable_alpha").MustBool(false) {
cfg.PluginsEnableAlpha = true
}
cfg.readLDAPConfig()
cfg.handleAWSConfig()
cfg.readSessionConfig()
cfg.readSmtpSettings()
cfg.readQuotaSettings()
cfg.readAnnotationSettings()
cfg.readExpressionsSettings()
if err := cfg.readGrafanaEnvironmentMetrics(); err != nil {
return err
}
cfg.readDataSourcesSettings()
if VerifyEmailEnabled && !cfg.Smtp.Enabled {
log.Warnf("require_email_validation is enabled but smtp is disabled")
}
// check old key name
GrafanaComUrl = valueAsString(iniFile.Section("grafana_net"), "url", "")
if GrafanaComUrl == "" {
GrafanaComUrl = valueAsString(iniFile.Section("grafana_com"), "url", "https://grafana.com")
}
imageUploadingSection := iniFile.Section("external_image_storage")
cfg.ImageUploadProvider = valueAsString(imageUploadingSection, "provider", "")
ImageUploadProvider = cfg.ImageUploadProvider
enterprise := iniFile.Section("enterprise")
cfg.EnterpriseLicensePath = valueAsString(enterprise, "license_path", filepath.Join(cfg.DataPath, "license.jwt"))
cacheServer := iniFile.Section("remote_cache")
dbName := valueAsString(cacheServer, "type", "database")
connStr := valueAsString(cacheServer, "connstr", "")
cfg.RemoteCacheOptions = &RemoteCacheOptions{
Name: dbName,
ConnStr: connStr,
}
cfg.readDateFormats()
cfg.readSentryConfig()
return nil
}
func valueAsString(section *ini.Section, keyName string, defaultValue string) string {
return section.Key(keyName).MustString(defaultValue)
}
type RemoteCacheOptions struct {
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)
cfg.LDAPEnabled = LDAPEnabled
LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false)
LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true)
cfg.LDAPAllowSignup = LDAPAllowSignup
}
func (cfg *Cfg) handleAWSConfig() {
AWS: Add aws plugin configuration (#31312) * add new conf and make sure its passed to frontend * change auth provider name * goimports * fixed after feedback * more updates after feedback * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update conf/sample.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update docs/sources/administration/configuration.md Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/setting/setting.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * updates after pr feedback * Update conf/defaults.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update conf/defaults.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update conf/sample.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
2021-02-24 11:08:13 -06:00
awsPluginSec := cfg.Raw.Section("aws")
cfg.AWSAssumeRoleEnabled = awsPluginSec.Key("assume_role_enabled").MustBool(true)
allowedAuthProviders := awsPluginSec.Key("allowed_auth_providers").MustString("default,keys,credentials")
AWS: Add aws plugin configuration (#31312) * add new conf and make sure its passed to frontend * change auth provider name * goimports * fixed after feedback * more updates after feedback * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update conf/sample.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update docs/sources/administration/configuration.md Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/setting/setting.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * updates after pr feedback * Update conf/defaults.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update conf/defaults.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update conf/sample.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
2021-02-24 11:08:13 -06:00
for _, authProvider := range strings.Split(allowedAuthProviders, ",") {
authProvider = strings.TrimSpace(authProvider)
if authProvider != "" {
cfg.AWSAllowedAuthProviders = append(cfg.AWSAllowedAuthProviders, authProvider)
}
}
cfg.AWSListMetricsPageLimit = awsPluginSec.Key("list_metrics_page_limit").MustInt(500)
// Also set environment variables that can be used by core plugins
err := os.Setenv(awsds.AssumeRoleEnabledEnvVarKeyName, strconv.FormatBool(cfg.AWSAssumeRoleEnabled))
if err != nil {
cfg.Logger.Error(fmt.Sprintf("could not set environment variable '%s'", awsds.AssumeRoleEnabledEnvVarKeyName), err)
}
err = os.Setenv(awsds.AllowedAuthProvidersEnvVarKeyName, allowedAuthProviders)
if err != nil {
cfg.Logger.Error(fmt.Sprintf("could not set environment variable '%s'", awsds.AllowedAuthProvidersEnvVarKeyName), err)
}
AWS: Add aws plugin configuration (#31312) * add new conf and make sure its passed to frontend * change auth provider name * goimports * fixed after feedback * more updates after feedback * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update conf/sample.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update docs/sources/administration/configuration.md Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/setting/setting.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * updates after pr feedback * Update conf/defaults.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update conf/defaults.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update conf/sample.ini Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
2021-02-24 11:08:13 -06:00
}
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",
)
}
}
func (cfg *Cfg) initLogging(file *ini.File) error {
logModeStr := valueAsString(file.Section("log"), "mode", "console")
// split on comma
logModes := strings.Split(logModeStr, ",")
// also try space
if len(logModes) == 1 {
logModes = strings.Split(logModeStr, " ")
}
logsPath := valueAsString(file.Section("paths"), "logs", "")
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", cfg.PluginsPath)
cfg.Logger.Info("Path Provisioning", "path", cfg.ProvisioningPath)
cfg.Logger.Info("App mode " + cfg.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 = redactedPassword
}
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 readSecuritySettings(iniFile *ini.File, cfg *Cfg) error {
security := iniFile.Section("security")
SecretKey = valueAsString(security, "secret_key", "")
DisableGravatar = security.Key("disable_gravatar").MustBool(true)
cfg.DisableBruteForceLoginProtection = security.Key("disable_brute_force_login_protection").MustBool(false)
CookieSecure = security.Key("cookie_secure").MustBool(false)
cfg.CookieSecure = CookieSecure
samesiteString := valueAsString(security, "cookie_samesite", "lax")
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
}
}
cfg.AllowEmbedding = security.Key("allow_embedding").MustBool(false)
cfg.ContentTypeProtectionHeader = security.Key("x_content_type_options").MustBool(true)
cfg.XSSProtectionHeader = security.Key("x_xss_protection").MustBool(true)
cfg.StrictTransportSecurity = security.Key("strict_transport_security").MustBool(false)
cfg.StrictTransportSecurityMaxAge = security.Key("strict_transport_security_max_age_seconds").MustInt(86400)
cfg.StrictTransportSecurityPreload = security.Key("strict_transport_security_preload").MustBool(false)
cfg.StrictTransportSecuritySubDomains = security.Key("strict_transport_security_subdomains").MustBool(false)
cfg.CSPEnabled = security.Key("content_security_policy").MustBool(false)
cfg.CSPTemplate = security.Key("content_security_policy_template").MustString("")
// read data source proxy whitelist
DataProxyWhiteList = make(map[string]bool)
securityStr := valueAsString(security, "data_source_proxy_whitelist", "")
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)
cfg.AdminUser = valueAsString(security, "admin_user", "")
cfg.AdminPassword = valueAsString(security, "admin_password", "")
2015-01-27 08:45:27 -06:00
return nil
}
func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
auth := iniFile.Section("auth")
cfg.LoginCookieName = valueAsString(auth, "login_cookie_name", "grafana_session")
maxInactiveDaysVal := auth.Key("login_maximum_inactive_lifetime_days").MustString("")
if maxInactiveDaysVal != "" {
maxInactiveDaysVal = fmt.Sprintf("%sd", maxInactiveDaysVal)
cfg.Logger.Warn("[Deprecated] the configuration setting 'login_maximum_inactive_lifetime_days' is deprecated, please use 'login_maximum_inactive_lifetime_duration' instead")
} else {
maxInactiveDaysVal = "7d"
}
maxInactiveDurationVal := valueAsString(auth, "login_maximum_inactive_lifetime_duration", maxInactiveDaysVal)
cfg.LoginMaxInactiveLifetime, err = gtime.ParseDuration(maxInactiveDurationVal)
if err != nil {
return err
}
maxLifetimeDaysVal := auth.Key("login_maximum_lifetime_days").MustString("")
if maxLifetimeDaysVal != "" {
maxLifetimeDaysVal = fmt.Sprintf("%sd", maxLifetimeDaysVal)
cfg.Logger.Warn("[Deprecated] the configuration setting 'login_maximum_lifetime_days' is deprecated, please use 'login_maximum_lifetime_duration' instead")
} else {
maxLifetimeDaysVal = "30d"
}
maxLifetimeDurationVal := valueAsString(auth, "login_maximum_lifetime_duration", maxLifetimeDaysVal)
cfg.LoginMaxLifetime, err = gtime.ParseDuration(maxLifetimeDurationVal)
if err != nil {
return err
}
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(600)
SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "")
// SigV4
SigV4AuthEnabled = auth.Key("sigv4_auth_enabled").MustBool(false)
cfg.SigV4AuthEnabled = SigV4AuthEnabled
2015-01-27 08:45:27 -06:00
// anonymous access
AnonymousEnabled = iniFile.Section("auth.anonymous").Key("enabled").MustBool(false)
cfg.AnonymousEnabled = AnonymousEnabled
cfg.AnonymousOrgName = valueAsString(iniFile.Section("auth.anonymous"), "org_name", "")
cfg.AnonymousOrgRole = valueAsString(iniFile.Section("auth.anonymous"), "org_role", "")
cfg.AnonymousHideVersion = iniFile.Section("auth.anonymous").Key("hide_version").MustBool(false)
// basic auth
authBasic := iniFile.Section("auth.basic")
BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
cfg.BasicAuthEnabled = BasicAuthEnabled
// JWT auth
authJWT := iniFile.Section("auth.jwt")
cfg.JWTAuthEnabled = authJWT.Key("enabled").MustBool(false)
cfg.JWTAuthHeaderName = valueAsString(authJWT, "header_name", "")
cfg.JWTAuthEmailClaim = valueAsString(authJWT, "email_claim", "")
cfg.JWTAuthUsernameClaim = valueAsString(authJWT, "username_claim", "")
cfg.JWTAuthExpectClaims = valueAsString(authJWT, "expect_claims", "{}")
cfg.JWTAuthJWKSetURL = valueAsString(authJWT, "jwk_set_url", "")
cfg.JWTAuthCacheTTL = authJWT.Key("cache_ttl").MustDuration(time.Minute * 60)
cfg.JWTAuthKeyFile = valueAsString(authJWT, "key_file", "")
cfg.JWTAuthJWKSetFile = valueAsString(authJWT, "jwk_set_file", "")
authProxy := iniFile.Section("auth.proxy")
AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)
cfg.AuthProxyEnabled = AuthProxyEnabled
cfg.AuthProxyHeaderName = valueAsString(authProxy, "header_name", "")
AuthProxyHeaderProperty = valueAsString(authProxy, "header_property", "")
cfg.AuthProxyHeaderProperty = AuthProxyHeaderProperty
cfg.AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
cfg.AuthProxyEnableLoginToken = authProxy.Key("enable_login_token").MustBool(false)
ldapSyncVal := authProxy.Key("ldap_sync_ttl").MustInt()
syncVal := authProxy.Key("sync_ttl").MustInt()
if ldapSyncVal != authProxySyncTTL {
cfg.AuthProxySyncTTL = ldapSyncVal
cfg.Logger.Warn("[Deprecated] the configuration setting 'ldap_sync_ttl' is deprecated, please use 'sync_ttl' instead")
} else {
cfg.AuthProxySyncTTL = syncVal
}
cfg.AuthProxyWhitelist = valueAsString(authProxy, "whitelist", "")
cfg.AuthProxyHeaders = make(map[string]string)
headers := valueAsString(authProxy, "headers", "")
for _, propertyAndHeader := range util.SplitString(headers) {
split := strings.SplitN(propertyAndHeader, ":", 2)
if len(split) == 2 {
cfg.AuthProxyHeaders[split[0]] = split[1]
}
}
return nil
}
func readUserSettings(iniFile *ini.File, cfg *Cfg) error {
users := iniFile.Section("users")
AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
cfg.AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
AutoAssignOrg = cfg.AutoAssignOrg
cfg.AutoAssignOrgId = users.Key("auto_assign_org_id").MustInt(1)
AutoAssignOrgId = cfg.AutoAssignOrgId
cfg.AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
AutoAssignOrgRole = cfg.AutoAssignOrgRole
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
LoginHint = valueAsString(users, "login_hint", "")
PasswordHint = valueAsString(users, "password_hint", "")
cfg.DefaultTheme = valueAsString(users, "default_theme", "")
cfg.HomePage = valueAsString(users, "home_page", "")
ExternalUserMngLinkUrl = valueAsString(users, "external_manage_link_url", "")
ExternalUserMngLinkName = valueAsString(users, "external_manage_link_name", "")
ExternalUserMngInfo = valueAsString(users, "external_manage_info", "")
ViewersCanEdit = users.Key("viewers_can_edit").MustBool(false)
cfg.EditorsCanAdmin = users.Key("editors_can_admin").MustBool(false)
userInviteMaxLifetimeVal := valueAsString(users, "user_invite_max_lifetime_duration", "24h")
userInviteMaxLifetimeDuration, err := gtime.ParseDuration(userInviteMaxLifetimeVal)
if err != nil {
return err
}
cfg.UserInviteMaxLifetime = userInviteMaxLifetimeDuration
if cfg.UserInviteMaxLifetime < time.Minute*15 {
return errors.New("the minimum supported value for the `user_invite_max_lifetime_duration` configuration is 15m (15 minutes)")
}
cfg.HiddenUsers = make(map[string]struct{})
hiddenUsers := users.Key("hidden_users").MustString("")
for _, user := range strings.Split(hiddenUsers, ",") {
user = strings.TrimSpace(user)
if user != "" {
cfg.HiddenUsers[user] = struct{}{}
}
}
return nil
}
func readRenderingSettings(iniFile *ini.File, cfg *Cfg) error {
renderSec := iniFile.Section("rendering")
cfg.RendererUrl = valueAsString(renderSec, "server_url", "")
cfg.RendererCallbackUrl = valueAsString(renderSec, "callback_url", "")
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 {
// XXX: Should return an error?
log.Fatalf(4, "Invalid callback_url(%s): %s", cfg.RendererCallbackUrl, 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
}
}
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")
2014-11-14 10:13:33 -06:00
return nil
}
2015-03-22 14:14:00 -05:00
func readAlertingSettings(iniFile *ini.File) error {
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 = valueAsString(alerting, "error_or_timeout", "alerting")
AlertingNoDataOrNullValues = valueAsString(alerting, "nodata_or_nullvalues", "no_data")
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)
return nil
}
func readSnapshotsSettings(cfg *Cfg, iniFile *ini.File) error {
snapshots := iniFile.Section("snapshots")
ExternalSnapshotUrl = valueAsString(snapshots, "external_snapshot_url", "")
ExternalSnapshotName = valueAsString(snapshots, "external_snapshot_name", "")
ExternalEnabled = snapshots.Key("external_enabled").MustBool(true)
SnapShotRemoveExpired = snapshots.Key("snapshot_remove_expired").MustBool(true)
cfg.SnapshotPublicMode = snapshots.Key("public_mode").MustBool(false)
return nil
}
func (cfg *Cfg) readServerSettings(iniFile *ini.File) error {
server := iniFile.Section("server")
var err error
AppUrl, AppSubUrl, err = parseAppUrlAndSubUrl(server)
if err != nil {
return err
}
ServeFromSubPath = server.Key("serve_from_sub_path").MustBool(false)
cfg.AppURL = AppUrl
cfg.AppSubURL = AppSubUrl
cfg.ServeFromSubPath = ServeFromSubPath
cfg.Protocol = HTTPScheme
protocolStr := valueAsString(server, "protocol", "http")
if protocolStr == "https" {
cfg.Protocol = HTTPSScheme
cfg.CertFile = server.Key("cert_file").String()
cfg.KeyFile = server.Key("cert_key").String()
}
if protocolStr == "h2" {
cfg.Protocol = HTTP2Scheme
cfg.CertFile = server.Key("cert_file").String()
cfg.KeyFile = server.Key("cert_key").String()
2019-03-03 14:48:00 -06:00
}
if protocolStr == "socket" {
cfg.Protocol = SocketScheme
cfg.SocketPath = server.Key("socket").String()
}
cfg.Domain = valueAsString(server, "domain", "localhost")
cfg.HTTPAddr = valueAsString(server, "http_addr", DefaultHTTPAddr)
cfg.HTTPPort = valueAsString(server, "http_port", "3000")
cfg.RouterLogging = server.Key("router_logging").MustBool(false)
cfg.EnableGzip = server.Key("enable_gzip").MustBool(false)
cfg.EnforceDomain = server.Key("enforce_domain").MustBool(false)
staticRoot := valueAsString(server, "static_root_path", "")
StaticRootPath = makeAbsolute(staticRoot, HomePath)
cfg.StaticRootPath = StaticRootPath
if err := cfg.validateStaticRootPath(); err != nil {
return err
}
cdnURL := valueAsString(server, "cdn_url", "")
if cdnURL != "" {
cfg.CDNRootURL, err = url.Parse(cdnURL)
if err != nil {
return err
}
}
cfg.ReadTimeout = server.Key("read_timeout").MustDuration(0)
return nil
}
// GetContentDeliveryURL returns full content delivery URL with /<edition>/<version> added to URL
func (cfg *Cfg) GetContentDeliveryURL(prefix string) string {
if cfg.CDNRootURL != nil {
url := *cfg.CDNRootURL
preReleaseFolder := ""
if strings.Contains(cfg.BuildVersion, "pre") || strings.Contains(cfg.BuildVersion, "alpha") {
preReleaseFolder = "pre-releases"
}
url.Path = path.Join(url.Path, prefix, preReleaseFolder, cfg.BuildVersion)
return url.String() + "/"
}
return ""
}
func (cfg *Cfg) readDataSourcesSettings() {
datasources := cfg.Raw.Section("datasources")
cfg.DataSourceLimit = datasources.Key("datasource_limit").MustInt(5000)
}