1// Copyright 2014 Unknwon 2// Copyright 2014 Torkel Ödegaard 3 4package setting 5 6import ( 7 "bytes" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "net/http" 12 "net/url" 13 "os" 14 "path" 15 "path/filepath" 16 "regexp" 17 "runtime" 18 "strconv" 19 "strings" 20 "time" 21 22 "github.com/grafana/grafana-aws-sdk/pkg/awsds" 23 "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" 24 25 "github.com/grafana/grafana/pkg/infra/log" 26 "github.com/grafana/grafana/pkg/util" 27 28 "github.com/gobwas/glob" 29 "github.com/prometheus/common/model" 30 "gopkg.in/ini.v1" 31) 32 33type Scheme string 34 35const ( 36 HTTPScheme Scheme = "http" 37 HTTPSScheme Scheme = "https" 38 HTTP2Scheme Scheme = "h2" 39 SocketScheme Scheme = "socket" 40) 41 42const ( 43 RedactedPassword = "*********" 44 DefaultHTTPAddr = "0.0.0.0" 45 Dev = "development" 46 Prod = "production" 47 Test = "test" 48 ApplicationName = "Grafana" 49) 50 51// This constant corresponds to the default value for ldap_sync_ttl in .ini files 52// it is used for comparison and has to be kept in sync 53const ( 54 authProxySyncTTL = 60 55) 56 57// zoneInfo names environment variable for setting the path to look for the timezone database in go 58const zoneInfo = "ZONEINFO" 59 60var ( 61 // App settings. 62 Env = Dev 63 AppUrl string 64 AppSubUrl string 65 ServeFromSubPath bool 66 InstanceName string 67 68 // build 69 BuildVersion string 70 BuildCommit string 71 BuildBranch string 72 BuildStamp int64 73 IsEnterprise bool 74 75 // packaging 76 Packaging = "unknown" 77 78 // Paths 79 HomePath string 80 CustomInitPath = "conf/custom.ini" 81 82 // HTTP server options 83 StaticRootPath string 84 85 // Security settings. 86 SecretKey string 87 DisableGravatar bool 88 DataProxyWhiteList map[string]bool 89 CookieSecure bool 90 CookieSameSiteDisabled bool 91 CookieSameSiteMode http.SameSite 92 93 // Snapshots 94 ExternalSnapshotUrl string 95 ExternalSnapshotName string 96 ExternalEnabled bool 97 SnapShotRemoveExpired bool 98 99 // Dashboard history 100 DashboardVersionsToKeep int 101 MinRefreshInterval string 102 103 // User settings 104 AllowUserSignUp bool 105 AllowUserOrgCreate bool 106 AutoAssignOrg bool 107 AutoAssignOrgId int 108 AutoAssignOrgRole string 109 VerifyEmailEnabled bool 110 LoginHint string 111 PasswordHint string 112 DisableLoginForm bool 113 DisableSignoutMenu bool 114 SignoutRedirectUrl string 115 ExternalUserMngLinkUrl string 116 ExternalUserMngLinkName string 117 ExternalUserMngInfo string 118 OAuthAutoLogin bool 119 ViewersCanEdit bool 120 121 // HTTP auth 122 SigV4AuthEnabled bool 123 124 AnonymousEnabled bool 125 126 // Auth proxy settings 127 AuthProxyEnabled bool 128 AuthProxyHeaderProperty string 129 130 // Basic Auth 131 BasicAuthEnabled bool 132 133 // Global setting objects. 134 Raw *ini.File 135 136 // for logging purposes 137 configFiles []string 138 appliedCommandLineProperties []string 139 appliedEnvOverrides []string 140 141 // analytics 142 GoogleAnalyticsId string 143 GoogleTagManagerId string 144 RudderstackDataPlaneUrl string 145 RudderstackWriteKey string 146 147 // LDAP 148 LDAPEnabled bool 149 LDAPConfigFile string 150 LDAPSyncCron string 151 LDAPAllowSignup bool 152 LDAPActiveSyncEnabled bool 153 154 // Quota 155 Quota QuotaSettings 156 157 // Alerting 158 AlertingEnabled *bool 159 ExecuteAlerts bool 160 AlertingRenderLimit int 161 AlertingErrorOrTimeout string 162 AlertingNoDataOrNullValues string 163 164 AlertingEvaluationTimeout time.Duration 165 AlertingNotificationTimeout time.Duration 166 AlertingMaxAttempts int 167 AlertingMinInterval int64 168 169 // Explore UI 170 ExploreEnabled bool 171 172 // Grafana.NET URL 173 GrafanaComUrl string 174 175 ImageUploadProvider string 176) 177 178// AddChangePasswordLink returns if login form is disabled or not since 179// the same intention can be used to hide both features. 180func AddChangePasswordLink() bool { 181 return !DisableLoginForm 182} 183 184// TODO move all global vars to this struct 185type Cfg struct { 186 Raw *ini.File 187 Logger log.Logger 188 189 // HTTP Server Settings 190 CertFile string 191 KeyFile string 192 HTTPAddr string 193 HTTPPort string 194 AppURL string 195 AppSubURL string 196 ServeFromSubPath bool 197 StaticRootPath string 198 Protocol Scheme 199 SocketPath string 200 RouterLogging bool 201 Domain string 202 CDNRootURL *url.URL 203 ReadTimeout time.Duration 204 EnableGzip bool 205 EnforceDomain bool 206 207 // Security settings 208 SecretKey string 209 EmailCodeValidMinutes int 210 211 // build 212 BuildVersion string 213 BuildCommit string 214 BuildBranch string 215 BuildStamp int64 216 IsEnterprise bool 217 218 // packaging 219 Packaging string 220 221 // Paths 222 HomePath string 223 ProvisioningPath string 224 DataPath string 225 LogsPath string 226 PluginsPath string 227 BundledPluginsPath string 228 229 // SMTP email settings 230 Smtp SmtpSettings 231 232 // Rendering 233 ImagesDir string 234 CSVsDir string 235 RendererUrl string 236 RendererCallbackUrl string 237 RendererConcurrentRequestLimit int 238 239 // Security 240 DisableInitAdminCreation bool 241 DisableBruteForceLoginProtection bool 242 CookieSecure bool 243 CookieSameSiteDisabled bool 244 CookieSameSiteMode http.SameSite 245 AllowEmbedding bool 246 XSSProtectionHeader bool 247 ContentTypeProtectionHeader bool 248 StrictTransportSecurity bool 249 StrictTransportSecurityMaxAge int 250 StrictTransportSecurityPreload bool 251 StrictTransportSecuritySubDomains bool 252 // CSPEnabled toggles Content Security Policy support. 253 CSPEnabled bool 254 // CSPTemplate contains the Content Security Policy template. 255 CSPTemplate string 256 257 TempDataLifetime time.Duration 258 PluginsEnableAlpha bool 259 PluginsAppsSkipVerifyTLS bool 260 PluginSettings PluginSettings 261 PluginsAllowUnsigned []string 262 PluginCatalogURL string 263 PluginCatalogHiddenPlugins []string 264 PluginAdminEnabled bool 265 PluginAdminExternalManageEnabled bool 266 DisableSanitizeHtml bool 267 EnterpriseLicensePath string 268 269 // Metrics 270 MetricsEndpointEnabled bool 271 MetricsEndpointBasicAuthUsername string 272 MetricsEndpointBasicAuthPassword string 273 MetricsEndpointDisableTotalStats bool 274 MetricsGrafanaEnvironmentInfo map[string]string 275 276 // Dashboards 277 DefaultHomeDashboardPath string 278 279 // Auth 280 LoginCookieName string 281 LoginMaxInactiveLifetime time.Duration 282 LoginMaxLifetime time.Duration 283 TokenRotationIntervalMinutes int 284 SigV4AuthEnabled bool 285 BasicAuthEnabled bool 286 AdminUser string 287 AdminPassword string 288 289 // AWS Plugin Auth 290 AWSAllowedAuthProviders []string 291 AWSAssumeRoleEnabled bool 292 AWSListMetricsPageLimit int 293 294 // Azure Cloud settings 295 Azure AzureSettings 296 297 // Auth proxy settings 298 AuthProxyEnabled bool 299 AuthProxyHeaderName string 300 AuthProxyHeaderProperty string 301 AuthProxyAutoSignUp bool 302 AuthProxyEnableLoginToken bool 303 AuthProxyWhitelist string 304 AuthProxyHeaders map[string]string 305 AuthProxySyncTTL int 306 307 // OAuth 308 OAuthCookieMaxAge int 309 310 // JWT Auth 311 JWTAuthEnabled bool 312 JWTAuthHeaderName string 313 JWTAuthEmailClaim string 314 JWTAuthUsernameClaim string 315 JWTAuthExpectClaims string 316 JWTAuthJWKSetURL string 317 JWTAuthCacheTTL time.Duration 318 JWTAuthKeyFile string 319 JWTAuthJWKSetFile string 320 321 // Dataproxy 322 SendUserHeader bool 323 DataProxyLogging bool 324 DataProxyTimeout int 325 DataProxyDialTimeout int 326 DataProxyTLSHandshakeTimeout int 327 DataProxyExpectContinueTimeout int 328 DataProxyMaxConnsPerHost int 329 DataProxyMaxIdleConns int 330 DataProxyKeepAlive int 331 DataProxyIdleConnTimeout int 332 ResponseLimit int64 333 DataProxyRowLimit int64 334 335 // DistributedCache 336 RemoteCacheOptions *RemoteCacheOptions 337 338 EditorsCanAdmin bool 339 340 ApiKeyMaxSecondsToLive int64 341 342 // Use to enable new features which may still be in alpha/beta stage. 343 FeatureToggles map[string]bool 344 AnonymousEnabled bool 345 AnonymousOrgName string 346 AnonymousOrgRole string 347 AnonymousHideVersion bool 348 349 DateFormats DateFormats 350 351 // User 352 UserInviteMaxLifetime time.Duration 353 HiddenUsers map[string]struct{} 354 355 // Annotations 356 AnnotationCleanupJobBatchSize int64 357 AlertingAnnotationCleanupSetting AnnotationCleanupSettings 358 DashboardAnnotationCleanupSettings AnnotationCleanupSettings 359 APIAnnotationCleanupSettings AnnotationCleanupSettings 360 361 // Sentry config 362 Sentry Sentry 363 364 // Data sources 365 DataSourceLimit int 366 367 // Snapshots 368 SnapshotPublicMode bool 369 370 ErrTemplateName string 371 372 Env string 373 374 // Analytics 375 CheckForUpdates bool 376 ReportingDistributor string 377 ReportingEnabled bool 378 ApplicationInsightsConnectionString string 379 ApplicationInsightsEndpointUrl string 380 381 // LDAP 382 LDAPEnabled bool 383 LDAPAllowSignup bool 384 385 Quota QuotaSettings 386 387 DefaultTheme string 388 HomePage string 389 390 AutoAssignOrg bool 391 AutoAssignOrgId int 392 AutoAssignOrgRole string 393 394 // ExpressionsEnabled specifies whether expressions are enabled. 395 ExpressionsEnabled bool 396 397 ImageUploadProvider string 398 399 // LiveMaxConnections is a maximum number of WebSocket connections to 400 // Grafana Live ws endpoint (per Grafana server instance). 0 disables 401 // Live, -1 means unlimited connections. 402 LiveMaxConnections int 403 // LiveHAEngine is a type of engine to use to achieve HA with Grafana Live. 404 // Zero value means in-memory single node setup. 405 LiveHAEngine string 406 // LiveHAEngineAddress is a connection address for Live HA engine. 407 LiveHAEngineAddress string 408 // LiveAllowedOrigins is a set of origins accepted by Live. If not provided 409 // then Live uses AppURL as the only allowed origin. 410 LiveAllowedOrigins []string 411 412 // Grafana.com URL 413 GrafanaComURL string 414 415 // Alerting 416 417 // AlertingBaseInterval controls the alerting base interval in seconds. 418 // Only for internal use and not user configuration. 419 AlertingBaseInterval time.Duration 420 421 // Geomap base layer config 422 GeomapDefaultBaseLayerConfig map[string]interface{} 423 GeomapEnableCustomBaseLayers bool 424 425 // Unified Alerting 426 UnifiedAlerting UnifiedAlertingSettings 427} 428 429// IsLiveConfigEnabled returns true if live should be able to save configs to SQL tables 430func (cfg Cfg) IsLiveConfigEnabled() bool { 431 return cfg.FeatureToggles["live-config"] 432} 433 434// IsTrimDefaultsEnabled returns whether the standalone trim dashboard default feature is enabled. 435func (cfg Cfg) IsTrimDefaultsEnabled() bool { 436 return cfg.FeatureToggles["trimDefaults"] 437} 438 439// IsDatabaseMetricsEnabled returns whether the database instrumentation feature is enabled. 440func (cfg Cfg) IsDatabaseMetricsEnabled() bool { 441 return cfg.FeatureToggles["database_metrics"] 442} 443 444// IsHTTPRequestHistogramDisabled returns whether the request historgrams is disabled. 445// This feature toggle will be removed in Grafana 8.x but gives the operator 446// some graceperiod to update all the monitoring tools. 447func (cfg Cfg) IsHTTPRequestHistogramDisabled() bool { 448 return cfg.FeatureToggles["disable_http_request_histogram"] 449} 450 451func (cfg Cfg) IsNewNavigationEnabled() bool { 452 return cfg.FeatureToggles["newNavigation"] 453} 454 455type CommandLineArgs struct { 456 Config string 457 HomePath string 458 Args []string 459} 460 461func (cfg Cfg) parseAppUrlAndSubUrl(section *ini.Section) (string, string, error) { 462 appUrl := valueAsString(section, "root_url", "http://localhost:3000/") 463 464 if appUrl[len(appUrl)-1] != '/' { 465 appUrl += "/" 466 } 467 468 // Check if has app suburl. 469 url, err := url.Parse(appUrl) 470 if err != nil { 471 cfg.Logger.Error("Invalid root_url.", "url", appUrl, "error", err) 472 os.Exit(1) 473 } 474 475 appSubUrl := strings.TrimSuffix(url.Path, "/") 476 return appUrl, appSubUrl, nil 477} 478 479func ToAbsUrl(relativeUrl string) string { 480 return AppUrl + relativeUrl 481} 482 483func RedactedValue(key, value string) string { 484 uppercased := strings.ToUpper(key) 485 // Sensitive information: password, secrets etc 486 for _, pattern := range []string{ 487 "PASSWORD", 488 "SECRET", 489 "PROVIDER_CONFIG", 490 "PRIVATE_KEY", 491 "SECRET_KEY", 492 "CERTIFICATE", 493 "ACCOUNT_KEY", 494 "ENCRYPTION_KEY", 495 "VAULT_TOKEN", 496 } { 497 if match, err := regexp.MatchString(pattern, uppercased); match && err == nil { 498 return RedactedPassword 499 } 500 } 501 502 for _, exception := range []string{ 503 "RUDDERSTACK", 504 "APPLICATION_INSIGHTS", 505 "SENTRY", 506 } { 507 if strings.Contains(uppercased, exception) { 508 return value 509 } 510 } 511 512 if u, err := RedactedURL(value); err == nil { 513 return u 514 } 515 516 return value 517} 518 519func RedactedURL(value string) (string, error) { 520 // Value could be a list of URLs 521 chunks := util.SplitString(value) 522 523 for i, chunk := range chunks { 524 var hasTmpPrefix bool 525 const tmpPrefix = "http://" 526 527 if !strings.Contains(chunk, "://") { 528 chunk = tmpPrefix + chunk 529 hasTmpPrefix = true 530 } 531 532 u, err := url.Parse(chunk) 533 if err != nil { 534 return "", err 535 } 536 537 redacted := u.Redacted() 538 if hasTmpPrefix { 539 redacted = strings.Replace(redacted, tmpPrefix, "", 1) 540 } 541 542 chunks[i] = redacted 543 } 544 545 if strings.Contains(value, ",") { 546 return strings.Join(chunks, ","), nil 547 } 548 549 return strings.Join(chunks, " "), nil 550} 551 552func applyEnvVariableOverrides(file *ini.File) error { 553 appliedEnvOverrides = make([]string, 0) 554 for _, section := range file.Sections() { 555 for _, key := range section.Keys() { 556 envKey := EnvKey(section.Name(), key.Name()) 557 envValue := os.Getenv(envKey) 558 559 if len(envValue) > 0 { 560 key.SetValue(envValue) 561 appliedEnvOverrides = append(appliedEnvOverrides, fmt.Sprintf("%s=%s", envKey, RedactedValue(envKey, envValue))) 562 } 563 } 564 } 565 566 return nil 567} 568 569func (cfg *Cfg) readGrafanaEnvironmentMetrics() error { 570 environmentMetricsSection := cfg.Raw.Section("metrics.environment_info") 571 keys := environmentMetricsSection.Keys() 572 cfg.MetricsGrafanaEnvironmentInfo = make(map[string]string, len(keys)) 573 574 for _, key := range keys { 575 labelName := model.LabelName(key.Name()) 576 labelValue := model.LabelValue(key.Value()) 577 578 if !labelName.IsValid() { 579 return fmt.Errorf("invalid label name in [metrics.environment_info] configuration. name %q", labelName) 580 } 581 582 if !labelValue.IsValid() { 583 return fmt.Errorf("invalid label value in [metrics.environment_info] configuration. name %q value %q", labelName, labelValue) 584 } 585 586 cfg.MetricsGrafanaEnvironmentInfo[string(labelName)] = string(labelValue) 587 } 588 589 return nil 590} 591 592func (cfg *Cfg) readAnnotationSettings() { 593 section := cfg.Raw.Section("annotations") 594 cfg.AnnotationCleanupJobBatchSize = section.Key("cleanupjob_batchsize").MustInt64(100) 595 596 dashboardAnnotation := cfg.Raw.Section("annotations.dashboard") 597 apiIAnnotation := cfg.Raw.Section("annotations.api") 598 alertingSection := cfg.Raw.Section("alerting") 599 600 var newAnnotationCleanupSettings = func(section *ini.Section, maxAgeField string) AnnotationCleanupSettings { 601 maxAge, err := gtime.ParseDuration(section.Key(maxAgeField).MustString("")) 602 if err != nil { 603 maxAge = 0 604 } 605 606 return AnnotationCleanupSettings{ 607 MaxAge: maxAge, 608 MaxCount: section.Key("max_annotations_to_keep").MustInt64(0), 609 } 610 } 611 612 cfg.AlertingAnnotationCleanupSetting = newAnnotationCleanupSettings(alertingSection, "max_annotation_age") 613 cfg.DashboardAnnotationCleanupSettings = newAnnotationCleanupSettings(dashboardAnnotation, "max_age") 614 cfg.APIAnnotationCleanupSettings = newAnnotationCleanupSettings(apiIAnnotation, "max_age") 615} 616 617func (cfg *Cfg) readExpressionsSettings() { 618 expressions := cfg.Raw.Section("expressions") 619 cfg.ExpressionsEnabled = expressions.Key("enabled").MustBool(true) 620} 621 622type AnnotationCleanupSettings struct { 623 MaxAge time.Duration 624 MaxCount int64 625} 626 627func EnvKey(sectionName string, keyName string) string { 628 sN := strings.ToUpper(strings.ReplaceAll(sectionName, ".", "_")) 629 sN = strings.ReplaceAll(sN, "-", "_") 630 kN := strings.ToUpper(strings.ReplaceAll(keyName, ".", "_")) 631 envKey := fmt.Sprintf("GF_%s_%s", sN, kN) 632 return envKey 633} 634 635func applyCommandLineDefaultProperties(props map[string]string, file *ini.File) { 636 appliedCommandLineProperties = make([]string, 0) 637 for _, section := range file.Sections() { 638 for _, key := range section.Keys() { 639 keyString := fmt.Sprintf("default.%s.%s", section.Name(), key.Name()) 640 value, exists := props[keyString] 641 if exists { 642 key.SetValue(value) 643 appliedCommandLineProperties = append(appliedCommandLineProperties, 644 fmt.Sprintf("%s=%s", keyString, RedactedValue(keyString, value))) 645 } 646 } 647 } 648} 649 650func applyCommandLineProperties(props map[string]string, file *ini.File) { 651 for _, section := range file.Sections() { 652 sectionName := section.Name() + "." 653 if section.Name() == ini.DefaultSection { 654 sectionName = "" 655 } 656 for _, key := range section.Keys() { 657 keyString := sectionName + key.Name() 658 value, exists := props[keyString] 659 if exists { 660 appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value)) 661 key.SetValue(value) 662 } 663 } 664 } 665} 666 667func (cfg Cfg) getCommandLineProperties(args []string) map[string]string { 668 props := make(map[string]string) 669 670 for _, arg := range args { 671 if !strings.HasPrefix(arg, "cfg:") { 672 continue 673 } 674 675 trimmed := strings.TrimPrefix(arg, "cfg:") 676 parts := strings.Split(trimmed, "=") 677 if len(parts) != 2 { 678 cfg.Logger.Error("Invalid command line argument.", "argument", arg) 679 os.Exit(1) 680 } 681 682 props[parts[0]] = parts[1] 683 } 684 return props 685} 686 687func makeAbsolute(path string, root string) string { 688 if filepath.IsAbs(path) { 689 return path 690 } 691 return filepath.Join(root, path) 692} 693 694func (cfg *Cfg) loadSpecifiedConfigFile(configFile string, masterFile *ini.File) error { 695 if configFile == "" { 696 configFile = filepath.Join(cfg.HomePath, CustomInitPath) 697 // return without error if custom file does not exist 698 if !pathExists(configFile) { 699 return nil 700 } 701 } 702 703 userConfig, err := ini.Load(configFile) 704 if err != nil { 705 return fmt.Errorf("failed to parse %q: %w", configFile, err) 706 } 707 708 userConfig.BlockMode = false 709 710 for _, section := range userConfig.Sections() { 711 for _, key := range section.Keys() { 712 if key.Value() == "" { 713 continue 714 } 715 716 defaultSec, err := masterFile.GetSection(section.Name()) 717 if err != nil { 718 defaultSec, _ = masterFile.NewSection(section.Name()) 719 } 720 defaultKey, err := defaultSec.GetKey(key.Name()) 721 if err != nil { 722 defaultKey, _ = defaultSec.NewKey(key.Name(), key.Value()) 723 } 724 defaultKey.SetValue(key.Value()) 725 } 726 } 727 728 configFiles = append(configFiles, configFile) 729 return nil 730} 731 732func (cfg *Cfg) loadConfiguration(args CommandLineArgs) (*ini.File, error) { 733 // load config defaults 734 defaultConfigFile := path.Join(HomePath, "conf/defaults.ini") 735 configFiles = append(configFiles, defaultConfigFile) 736 737 // check if config file exists 738 if _, err := os.Stat(defaultConfigFile); os.IsNotExist(err) { 739 fmt.Println("Grafana-server Init Failed: Could not find config defaults, make sure homepath command line parameter is set or working directory is homepath") 740 os.Exit(1) 741 } 742 743 // load defaults 744 parsedFile, err := ini.Load(defaultConfigFile) 745 if err != nil { 746 fmt.Printf("Failed to parse defaults.ini, %v\n", err) 747 os.Exit(1) 748 return nil, err 749 } 750 751 parsedFile.BlockMode = false 752 753 // command line props 754 commandLineProps := cfg.getCommandLineProperties(args.Args) 755 // load default overrides 756 applyCommandLineDefaultProperties(commandLineProps, parsedFile) 757 758 // load specified config file 759 err = cfg.loadSpecifiedConfigFile(args.Config, parsedFile) 760 if err != nil { 761 err2 := cfg.initLogging(parsedFile) 762 if err2 != nil { 763 return nil, err2 764 } 765 cfg.Logger.Error(err.Error()) 766 os.Exit(1) 767 } 768 769 // apply environment overrides 770 err = applyEnvVariableOverrides(parsedFile) 771 if err != nil { 772 return nil, err 773 } 774 775 // apply command line overrides 776 applyCommandLineProperties(commandLineProps, parsedFile) 777 778 // evaluate config values containing environment variables 779 err = expandConfig(parsedFile) 780 if err != nil { 781 return nil, err 782 } 783 784 // update data path and logging config 785 dataPath := valueAsString(parsedFile.Section("paths"), "data", "") 786 787 cfg.DataPath = makeAbsolute(dataPath, HomePath) 788 err = cfg.initLogging(parsedFile) 789 if err != nil { 790 return nil, err 791 } 792 793 return parsedFile, err 794} 795 796func pathExists(path string) bool { 797 _, err := os.Stat(path) 798 if err == nil { 799 return true 800 } 801 if os.IsNotExist(err) { 802 return false 803 } 804 return false 805} 806 807func (cfg *Cfg) setHomePath(args CommandLineArgs) { 808 if args.HomePath != "" { 809 cfg.HomePath = args.HomePath 810 HomePath = cfg.HomePath 811 return 812 } 813 814 var err error 815 cfg.HomePath, err = filepath.Abs(".") 816 if err != nil { 817 panic(err) 818 } 819 820 HomePath = cfg.HomePath 821 // check if homepath is correct 822 if pathExists(filepath.Join(cfg.HomePath, "conf/defaults.ini")) { 823 return 824 } 825 826 // try down one path 827 if pathExists(filepath.Join(cfg.HomePath, "../conf/defaults.ini")) { 828 cfg.HomePath = filepath.Join(cfg.HomePath, "../") 829 HomePath = cfg.HomePath 830 } 831} 832 833var skipStaticRootValidation = false 834 835func NewCfg() *Cfg { 836 return &Cfg{ 837 Logger: log.New("settings"), 838 Raw: ini.Empty(), 839 } 840} 841 842func NewCfgFromArgs(args CommandLineArgs) (*Cfg, error) { 843 cfg := NewCfg() 844 if err := cfg.Load(args); err != nil { 845 return nil, err 846 } 847 848 return cfg, nil 849} 850 851func (cfg *Cfg) validateStaticRootPath() error { 852 if skipStaticRootValidation { 853 return nil 854 } 855 856 if _, err := os.Stat(path.Join(StaticRootPath, "build")); err != nil { 857 cfg.Logger.Error("Failed to detect generated javascript files in public/build") 858 } 859 860 return nil 861} 862 863func (cfg *Cfg) Load(args CommandLineArgs) error { 864 cfg.setHomePath(args) 865 866 // Fix for missing IANA db on Windows 867 _, zoneInfoSet := os.LookupEnv(zoneInfo) 868 if runtime.GOOS == "windows" && !zoneInfoSet { 869 if err := os.Setenv(zoneInfo, filepath.Join(HomePath, "tools", "zoneinfo.zip")); err != nil { 870 cfg.Logger.Error("Can't set ZONEINFO environment variable", "err", err) 871 } 872 } 873 874 iniFile, err := cfg.loadConfiguration(args) 875 if err != nil { 876 return err 877 } 878 879 cfg.Raw = iniFile 880 881 // Temporarily keep global, to make refactor in steps 882 Raw = cfg.Raw 883 884 cfg.BuildVersion = BuildVersion 885 cfg.BuildCommit = BuildCommit 886 cfg.BuildStamp = BuildStamp 887 cfg.BuildBranch = BuildBranch 888 cfg.IsEnterprise = IsEnterprise 889 cfg.Packaging = Packaging 890 891 cfg.ErrTemplateName = "error" 892 893 Env = valueAsString(iniFile.Section(""), "app_mode", "development") 894 cfg.Env = Env 895 InstanceName = valueAsString(iniFile.Section(""), "instance_name", "unknown_instance_name") 896 plugins := valueAsString(iniFile.Section("paths"), "plugins", "") 897 cfg.PluginsPath = makeAbsolute(plugins, HomePath) 898 cfg.BundledPluginsPath = makeAbsolute("plugins-bundled", HomePath) 899 provisioning := valueAsString(iniFile.Section("paths"), "provisioning", "") 900 cfg.ProvisioningPath = makeAbsolute(provisioning, HomePath) 901 902 if err := cfg.readServerSettings(iniFile); err != nil { 903 return err 904 } 905 906 if err := readDataProxySettings(iniFile, cfg); err != nil { 907 return err 908 } 909 910 if err := readSecuritySettings(iniFile, cfg); err != nil { 911 return err 912 } 913 914 if err := readSnapshotsSettings(cfg, iniFile); err != nil { 915 return err 916 } 917 918 // read dashboard settings 919 dashboards := iniFile.Section("dashboards") 920 DashboardVersionsToKeep = dashboards.Key("versions_to_keep").MustInt(20) 921 MinRefreshInterval = valueAsString(dashboards, "min_refresh_interval", "5s") 922 923 cfg.DefaultHomeDashboardPath = dashboards.Key("default_home_dashboard_path").MustString("") 924 925 if err := readUserSettings(iniFile, cfg); err != nil { 926 return err 927 } 928 if err := readAuthSettings(iniFile, cfg); err != nil { 929 return err 930 } 931 if err := cfg.readRenderingSettings(iniFile); err != nil { 932 return err 933 } 934 935 cfg.TempDataLifetime = iniFile.Section("paths").Key("temp_data_lifetime").MustDuration(time.Second * 3600 * 24) 936 cfg.MetricsEndpointEnabled = iniFile.Section("metrics").Key("enabled").MustBool(true) 937 cfg.MetricsEndpointBasicAuthUsername = valueAsString(iniFile.Section("metrics"), "basic_auth_username", "") 938 cfg.MetricsEndpointBasicAuthPassword = valueAsString(iniFile.Section("metrics"), "basic_auth_password", "") 939 cfg.MetricsEndpointDisableTotalStats = iniFile.Section("metrics").Key("disable_total_stats").MustBool(false) 940 941 analytics := iniFile.Section("analytics") 942 cfg.CheckForUpdates = analytics.Key("check_for_updates").MustBool(true) 943 GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String() 944 GoogleTagManagerId = analytics.Key("google_tag_manager_id").String() 945 RudderstackWriteKey = analytics.Key("rudderstack_write_key").String() 946 RudderstackDataPlaneUrl = analytics.Key("rudderstack_data_plane_url").String() 947 cfg.ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true) 948 cfg.ReportingDistributor = analytics.Key("reporting_distributor").MustString("grafana-labs") 949 if len(cfg.ReportingDistributor) >= 100 { 950 cfg.ReportingDistributor = cfg.ReportingDistributor[:100] 951 } 952 cfg.ApplicationInsightsConnectionString = analytics.Key("application_insights_connection_string").String() 953 cfg.ApplicationInsightsEndpointUrl = analytics.Key("application_insights_endpoint_url").String() 954 955 if err := readAlertingSettings(iniFile); err != nil { 956 return err 957 } 958 959 explore := iniFile.Section("explore") 960 ExploreEnabled = explore.Key("enabled").MustBool(true) 961 962 panelsSection := iniFile.Section("panels") 963 cfg.DisableSanitizeHtml = panelsSection.Key("disable_sanitize_html").MustBool(false) 964 965 if err := cfg.readPluginSettings(iniFile); err != nil { 966 return err 967 } 968 969 if err := cfg.readFeatureToggles(iniFile); err != nil { 970 return err 971 } 972 973 if err := cfg.ReadUnifiedAlertingSettings(iniFile); err != nil { 974 return err 975 } 976 977 // check old location for this option 978 if panelsSection.Key("enable_alpha").MustBool(false) { 979 cfg.PluginsEnableAlpha = true 980 } 981 982 cfg.readLDAPConfig() 983 cfg.handleAWSConfig() 984 cfg.readAzureSettings() 985 cfg.readSessionConfig() 986 cfg.readSmtpSettings() 987 cfg.readQuotaSettings() 988 cfg.readAnnotationSettings() 989 cfg.readExpressionsSettings() 990 if err := cfg.readGrafanaEnvironmentMetrics(); err != nil { 991 return err 992 } 993 994 cfg.readDataSourcesSettings() 995 996 if VerifyEmailEnabled && !cfg.Smtp.Enabled { 997 cfg.Logger.Warn("require_email_validation is enabled but smtp is disabled") 998 } 999 1000 // check old key name 1001 GrafanaComUrl = valueAsString(iniFile.Section("grafana_net"), "url", "") 1002 if GrafanaComUrl == "" { 1003 GrafanaComUrl = valueAsString(iniFile.Section("grafana_com"), "url", "https://grafana.com") 1004 } 1005 cfg.GrafanaComURL = GrafanaComUrl 1006 1007 imageUploadingSection := iniFile.Section("external_image_storage") 1008 cfg.ImageUploadProvider = valueAsString(imageUploadingSection, "provider", "") 1009 ImageUploadProvider = cfg.ImageUploadProvider 1010 1011 enterprise := iniFile.Section("enterprise") 1012 cfg.EnterpriseLicensePath = valueAsString(enterprise, "license_path", filepath.Join(cfg.DataPath, "license.jwt")) 1013 1014 cacheServer := iniFile.Section("remote_cache") 1015 dbName := valueAsString(cacheServer, "type", "database") 1016 connStr := valueAsString(cacheServer, "connstr", "") 1017 1018 cfg.RemoteCacheOptions = &RemoteCacheOptions{ 1019 Name: dbName, 1020 ConnStr: connStr, 1021 } 1022 1023 geomapSection := iniFile.Section("geomap") 1024 basemapJSON := valueAsString(geomapSection, "default_baselayer_config", "") 1025 if basemapJSON != "" { 1026 layer := make(map[string]interface{}) 1027 err = json.Unmarshal([]byte(basemapJSON), &layer) 1028 if err != nil { 1029 cfg.Logger.Error("Error reading json from default_baselayer_config", "error", err) 1030 } else { 1031 cfg.GeomapDefaultBaseLayerConfig = layer 1032 } 1033 } 1034 cfg.GeomapEnableCustomBaseLayers = geomapSection.Key("enable_custom_baselayers").MustBool(true) 1035 1036 cfg.readDateFormats() 1037 cfg.readSentryConfig() 1038 1039 if err := cfg.readLiveSettings(iniFile); err != nil { 1040 return err 1041 } 1042 1043 cfg.LogConfigSources() 1044 1045 return nil 1046} 1047 1048func valueAsString(section *ini.Section, keyName string, defaultValue string) string { 1049 return section.Key(keyName).MustString(defaultValue) 1050} 1051 1052type RemoteCacheOptions struct { 1053 Name string 1054 ConnStr string 1055} 1056 1057func (cfg *Cfg) readLDAPConfig() { 1058 ldapSec := cfg.Raw.Section("auth.ldap") 1059 LDAPConfigFile = ldapSec.Key("config_file").String() 1060 LDAPSyncCron = ldapSec.Key("sync_cron").String() 1061 LDAPEnabled = ldapSec.Key("enabled").MustBool(false) 1062 cfg.LDAPEnabled = LDAPEnabled 1063 LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false) 1064 LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true) 1065 cfg.LDAPAllowSignup = LDAPAllowSignup 1066} 1067 1068func (cfg *Cfg) handleAWSConfig() { 1069 awsPluginSec := cfg.Raw.Section("aws") 1070 cfg.AWSAssumeRoleEnabled = awsPluginSec.Key("assume_role_enabled").MustBool(true) 1071 allowedAuthProviders := awsPluginSec.Key("allowed_auth_providers").MustString("default,keys,credentials") 1072 for _, authProvider := range strings.Split(allowedAuthProviders, ",") { 1073 authProvider = strings.TrimSpace(authProvider) 1074 if authProvider != "" { 1075 cfg.AWSAllowedAuthProviders = append(cfg.AWSAllowedAuthProviders, authProvider) 1076 } 1077 } 1078 cfg.AWSListMetricsPageLimit = awsPluginSec.Key("list_metrics_page_limit").MustInt(500) 1079 // Also set environment variables that can be used by core plugins 1080 err := os.Setenv(awsds.AssumeRoleEnabledEnvVarKeyName, strconv.FormatBool(cfg.AWSAssumeRoleEnabled)) 1081 if err != nil { 1082 cfg.Logger.Error(fmt.Sprintf("could not set environment variable '%s'", awsds.AssumeRoleEnabledEnvVarKeyName), err) 1083 } 1084 1085 err = os.Setenv(awsds.AllowedAuthProvidersEnvVarKeyName, allowedAuthProviders) 1086 if err != nil { 1087 cfg.Logger.Error(fmt.Sprintf("could not set environment variable '%s'", awsds.AllowedAuthProvidersEnvVarKeyName), err) 1088 } 1089} 1090 1091func (cfg *Cfg) readSessionConfig() { 1092 sec, _ := cfg.Raw.GetSection("session") 1093 1094 if sec != nil { 1095 cfg.Logger.Warn( 1096 "[Removed] Session setting was removed in v6.2, use remote_cache option instead", 1097 ) 1098 } 1099} 1100 1101func (cfg *Cfg) initLogging(file *ini.File) error { 1102 logModeStr := valueAsString(file.Section("log"), "mode", "console") 1103 // split on comma 1104 logModes := strings.Split(logModeStr, ",") 1105 // also try space 1106 if len(logModes) == 1 { 1107 logModes = strings.Split(logModeStr, " ") 1108 } 1109 logsPath := valueAsString(file.Section("paths"), "logs", "") 1110 cfg.LogsPath = makeAbsolute(logsPath, HomePath) 1111 return log.ReadLoggingConfig(logModes, cfg.LogsPath, file) 1112} 1113 1114func (cfg *Cfg) LogConfigSources() { 1115 var text bytes.Buffer 1116 1117 for _, file := range configFiles { 1118 cfg.Logger.Info("Config loaded from", "file", file) 1119 } 1120 1121 if len(appliedCommandLineProperties) > 0 { 1122 for _, prop := range appliedCommandLineProperties { 1123 cfg.Logger.Info("Config overridden from command line", "arg", prop) 1124 } 1125 } 1126 1127 if len(appliedEnvOverrides) > 0 { 1128 text.WriteString("\tEnvironment variables used:\n") 1129 for _, prop := range appliedEnvOverrides { 1130 cfg.Logger.Info("Config overridden from Environment variable", "var", prop) 1131 } 1132 } 1133 1134 cfg.Logger.Info("Path Home", "path", HomePath) 1135 cfg.Logger.Info("Path Data", "path", cfg.DataPath) 1136 cfg.Logger.Info("Path Logs", "path", cfg.LogsPath) 1137 cfg.Logger.Info("Path Plugins", "path", cfg.PluginsPath) 1138 cfg.Logger.Info("Path Provisioning", "path", cfg.ProvisioningPath) 1139 cfg.Logger.Info("App mode " + cfg.Env) 1140} 1141 1142type DynamicSection struct { 1143 section *ini.Section 1144 Logger log.Logger 1145} 1146 1147// Key dynamically overrides keys with environment variables. 1148// As a side effect, the value of the setting key will be updated if an environment variable is present. 1149func (s *DynamicSection) Key(k string) *ini.Key { 1150 envKey := EnvKey(s.section.Name(), k) 1151 envValue := os.Getenv(envKey) 1152 key := s.section.Key(k) 1153 1154 if len(envValue) == 0 { 1155 return key 1156 } 1157 1158 key.SetValue(envValue) 1159 s.Logger.Info("Config overridden from Environment variable", "var", fmt.Sprintf("%s=%s", envKey, RedactedValue(envKey, envValue))) 1160 1161 return key 1162} 1163 1164// SectionWithEnvOverrides dynamically overrides keys with environment variables. 1165// As a side effect, the value of the setting key will be updated if an environment variable is present. 1166func (cfg *Cfg) SectionWithEnvOverrides(s string) *DynamicSection { 1167 return &DynamicSection{cfg.Raw.Section(s), cfg.Logger} 1168} 1169 1170func readSecuritySettings(iniFile *ini.File, cfg *Cfg) error { 1171 security := iniFile.Section("security") 1172 SecretKey = valueAsString(security, "secret_key", "") 1173 cfg.SecretKey = SecretKey 1174 DisableGravatar = security.Key("disable_gravatar").MustBool(true) 1175 cfg.DisableBruteForceLoginProtection = security.Key("disable_brute_force_login_protection").MustBool(false) 1176 1177 CookieSecure = security.Key("cookie_secure").MustBool(false) 1178 cfg.CookieSecure = CookieSecure 1179 1180 samesiteString := valueAsString(security, "cookie_samesite", "lax") 1181 1182 if samesiteString == "disabled" { 1183 CookieSameSiteDisabled = true 1184 cfg.CookieSameSiteDisabled = CookieSameSiteDisabled 1185 } else { 1186 validSameSiteValues := map[string]http.SameSite{ 1187 "lax": http.SameSiteLaxMode, 1188 "strict": http.SameSiteStrictMode, 1189 "none": http.SameSiteNoneMode, 1190 } 1191 1192 if samesite, ok := validSameSiteValues[samesiteString]; ok { 1193 CookieSameSiteMode = samesite 1194 cfg.CookieSameSiteMode = CookieSameSiteMode 1195 } else { 1196 CookieSameSiteMode = http.SameSiteLaxMode 1197 cfg.CookieSameSiteMode = CookieSameSiteMode 1198 } 1199 } 1200 cfg.AllowEmbedding = security.Key("allow_embedding").MustBool(false) 1201 1202 cfg.ContentTypeProtectionHeader = security.Key("x_content_type_options").MustBool(true) 1203 cfg.XSSProtectionHeader = security.Key("x_xss_protection").MustBool(true) 1204 cfg.StrictTransportSecurity = security.Key("strict_transport_security").MustBool(false) 1205 cfg.StrictTransportSecurityMaxAge = security.Key("strict_transport_security_max_age_seconds").MustInt(86400) 1206 cfg.StrictTransportSecurityPreload = security.Key("strict_transport_security_preload").MustBool(false) 1207 cfg.StrictTransportSecuritySubDomains = security.Key("strict_transport_security_subdomains").MustBool(false) 1208 cfg.CSPEnabled = security.Key("content_security_policy").MustBool(false) 1209 cfg.CSPTemplate = security.Key("content_security_policy_template").MustString("") 1210 1211 // read data source proxy whitelist 1212 DataProxyWhiteList = make(map[string]bool) 1213 securityStr := valueAsString(security, "data_source_proxy_whitelist", "") 1214 1215 for _, hostAndIP := range util.SplitString(securityStr) { 1216 DataProxyWhiteList[hostAndIP] = true 1217 } 1218 1219 // admin 1220 cfg.DisableInitAdminCreation = security.Key("disable_initial_admin_creation").MustBool(false) 1221 cfg.AdminUser = valueAsString(security, "admin_user", "") 1222 cfg.AdminPassword = valueAsString(security, "admin_password", "") 1223 1224 return nil 1225} 1226 1227func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) { 1228 auth := iniFile.Section("auth") 1229 1230 cfg.LoginCookieName = valueAsString(auth, "login_cookie_name", "grafana_session") 1231 maxInactiveDaysVal := auth.Key("login_maximum_inactive_lifetime_days").MustString("") 1232 if maxInactiveDaysVal != "" { 1233 maxInactiveDaysVal = fmt.Sprintf("%sd", maxInactiveDaysVal) 1234 cfg.Logger.Warn("[Deprecated] the configuration setting 'login_maximum_inactive_lifetime_days' is deprecated, please use 'login_maximum_inactive_lifetime_duration' instead") 1235 } else { 1236 maxInactiveDaysVal = "7d" 1237 } 1238 maxInactiveDurationVal := valueAsString(auth, "login_maximum_inactive_lifetime_duration", maxInactiveDaysVal) 1239 cfg.LoginMaxInactiveLifetime, err = gtime.ParseDuration(maxInactiveDurationVal) 1240 if err != nil { 1241 return err 1242 } 1243 1244 maxLifetimeDaysVal := auth.Key("login_maximum_lifetime_days").MustString("") 1245 if maxLifetimeDaysVal != "" { 1246 maxLifetimeDaysVal = fmt.Sprintf("%sd", maxLifetimeDaysVal) 1247 cfg.Logger.Warn("[Deprecated] the configuration setting 'login_maximum_lifetime_days' is deprecated, please use 'login_maximum_lifetime_duration' instead") 1248 } else { 1249 maxLifetimeDaysVal = "30d" 1250 } 1251 maxLifetimeDurationVal := valueAsString(auth, "login_maximum_lifetime_duration", maxLifetimeDaysVal) 1252 cfg.LoginMaxLifetime, err = gtime.ParseDuration(maxLifetimeDurationVal) 1253 if err != nil { 1254 return err 1255 } 1256 1257 cfg.ApiKeyMaxSecondsToLive = auth.Key("api_key_max_seconds_to_live").MustInt64(-1) 1258 1259 cfg.TokenRotationIntervalMinutes = auth.Key("token_rotation_interval_minutes").MustInt(10) 1260 if cfg.TokenRotationIntervalMinutes < 2 { 1261 cfg.TokenRotationIntervalMinutes = 2 1262 } 1263 1264 DisableLoginForm = auth.Key("disable_login_form").MustBool(false) 1265 DisableSignoutMenu = auth.Key("disable_signout_menu").MustBool(false) 1266 OAuthAutoLogin = auth.Key("oauth_auto_login").MustBool(false) 1267 cfg.OAuthCookieMaxAge = auth.Key("oauth_state_cookie_max_age").MustInt(600) 1268 SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "") 1269 1270 // SigV4 1271 SigV4AuthEnabled = auth.Key("sigv4_auth_enabled").MustBool(false) 1272 cfg.SigV4AuthEnabled = SigV4AuthEnabled 1273 1274 // anonymous access 1275 AnonymousEnabled = iniFile.Section("auth.anonymous").Key("enabled").MustBool(false) 1276 cfg.AnonymousEnabled = AnonymousEnabled 1277 cfg.AnonymousOrgName = valueAsString(iniFile.Section("auth.anonymous"), "org_name", "") 1278 cfg.AnonymousOrgRole = valueAsString(iniFile.Section("auth.anonymous"), "org_role", "") 1279 cfg.AnonymousHideVersion = iniFile.Section("auth.anonymous").Key("hide_version").MustBool(false) 1280 1281 // basic auth 1282 authBasic := iniFile.Section("auth.basic") 1283 BasicAuthEnabled = authBasic.Key("enabled").MustBool(true) 1284 cfg.BasicAuthEnabled = BasicAuthEnabled 1285 1286 // JWT auth 1287 authJWT := iniFile.Section("auth.jwt") 1288 cfg.JWTAuthEnabled = authJWT.Key("enabled").MustBool(false) 1289 cfg.JWTAuthHeaderName = valueAsString(authJWT, "header_name", "") 1290 cfg.JWTAuthEmailClaim = valueAsString(authJWT, "email_claim", "") 1291 cfg.JWTAuthUsernameClaim = valueAsString(authJWT, "username_claim", "") 1292 cfg.JWTAuthExpectClaims = valueAsString(authJWT, "expect_claims", "{}") 1293 cfg.JWTAuthJWKSetURL = valueAsString(authJWT, "jwk_set_url", "") 1294 cfg.JWTAuthCacheTTL = authJWT.Key("cache_ttl").MustDuration(time.Minute * 60) 1295 cfg.JWTAuthKeyFile = valueAsString(authJWT, "key_file", "") 1296 cfg.JWTAuthJWKSetFile = valueAsString(authJWT, "jwk_set_file", "") 1297 1298 authProxy := iniFile.Section("auth.proxy") 1299 AuthProxyEnabled = authProxy.Key("enabled").MustBool(false) 1300 cfg.AuthProxyEnabled = AuthProxyEnabled 1301 1302 cfg.AuthProxyHeaderName = valueAsString(authProxy, "header_name", "") 1303 AuthProxyHeaderProperty = valueAsString(authProxy, "header_property", "") 1304 cfg.AuthProxyHeaderProperty = AuthProxyHeaderProperty 1305 cfg.AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true) 1306 cfg.AuthProxyEnableLoginToken = authProxy.Key("enable_login_token").MustBool(false) 1307 1308 ldapSyncVal := authProxy.Key("ldap_sync_ttl").MustInt() 1309 syncVal := authProxy.Key("sync_ttl").MustInt() 1310 1311 if ldapSyncVal != authProxySyncTTL { 1312 cfg.AuthProxySyncTTL = ldapSyncVal 1313 cfg.Logger.Warn("[Deprecated] the configuration setting 'ldap_sync_ttl' is deprecated, please use 'sync_ttl' instead") 1314 } else { 1315 cfg.AuthProxySyncTTL = syncVal 1316 } 1317 1318 cfg.AuthProxyWhitelist = valueAsString(authProxy, "whitelist", "") 1319 1320 cfg.AuthProxyHeaders = make(map[string]string) 1321 headers := valueAsString(authProxy, "headers", "") 1322 1323 for _, propertyAndHeader := range util.SplitString(headers) { 1324 split := strings.SplitN(propertyAndHeader, ":", 2) 1325 if len(split) == 2 { 1326 cfg.AuthProxyHeaders[split[0]] = split[1] 1327 } 1328 } 1329 1330 return nil 1331} 1332 1333func readUserSettings(iniFile *ini.File, cfg *Cfg) error { 1334 users := iniFile.Section("users") 1335 AllowUserSignUp = users.Key("allow_sign_up").MustBool(true) 1336 AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true) 1337 cfg.AutoAssignOrg = users.Key("auto_assign_org").MustBool(true) 1338 AutoAssignOrg = cfg.AutoAssignOrg 1339 cfg.AutoAssignOrgId = users.Key("auto_assign_org_id").MustInt(1) 1340 AutoAssignOrgId = cfg.AutoAssignOrgId 1341 cfg.AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"}) 1342 AutoAssignOrgRole = cfg.AutoAssignOrgRole 1343 VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false) 1344 1345 LoginHint = valueAsString(users, "login_hint", "") 1346 PasswordHint = valueAsString(users, "password_hint", "") 1347 cfg.DefaultTheme = valueAsString(users, "default_theme", "") 1348 cfg.HomePage = valueAsString(users, "home_page", "") 1349 ExternalUserMngLinkUrl = valueAsString(users, "external_manage_link_url", "") 1350 ExternalUserMngLinkName = valueAsString(users, "external_manage_link_name", "") 1351 ExternalUserMngInfo = valueAsString(users, "external_manage_info", "") 1352 1353 ViewersCanEdit = users.Key("viewers_can_edit").MustBool(false) 1354 cfg.EditorsCanAdmin = users.Key("editors_can_admin").MustBool(false) 1355 1356 userInviteMaxLifetimeVal := valueAsString(users, "user_invite_max_lifetime_duration", "24h") 1357 userInviteMaxLifetimeDuration, err := gtime.ParseDuration(userInviteMaxLifetimeVal) 1358 if err != nil { 1359 return err 1360 } 1361 1362 cfg.UserInviteMaxLifetime = userInviteMaxLifetimeDuration 1363 if cfg.UserInviteMaxLifetime < time.Minute*15 { 1364 return errors.New("the minimum supported value for the `user_invite_max_lifetime_duration` configuration is 15m (15 minutes)") 1365 } 1366 1367 cfg.HiddenUsers = make(map[string]struct{}) 1368 hiddenUsers := users.Key("hidden_users").MustString("") 1369 for _, user := range strings.Split(hiddenUsers, ",") { 1370 user = strings.TrimSpace(user) 1371 if user != "" { 1372 cfg.HiddenUsers[user] = struct{}{} 1373 } 1374 } 1375 1376 return nil 1377} 1378 1379func (cfg *Cfg) readRenderingSettings(iniFile *ini.File) error { 1380 renderSec := iniFile.Section("rendering") 1381 cfg.RendererUrl = valueAsString(renderSec, "server_url", "") 1382 cfg.RendererCallbackUrl = valueAsString(renderSec, "callback_url", "") 1383 1384 if cfg.RendererCallbackUrl == "" { 1385 cfg.RendererCallbackUrl = AppUrl 1386 } else { 1387 if cfg.RendererCallbackUrl[len(cfg.RendererCallbackUrl)-1] != '/' { 1388 cfg.RendererCallbackUrl += "/" 1389 } 1390 _, err := url.Parse(cfg.RendererCallbackUrl) 1391 if err != nil { 1392 // XXX: Should return an error? 1393 cfg.Logger.Error("Invalid callback_url.", "url", cfg.RendererCallbackUrl, "error", err) 1394 os.Exit(1) 1395 } 1396 } 1397 1398 cfg.RendererConcurrentRequestLimit = renderSec.Key("concurrent_render_request_limit").MustInt(30) 1399 cfg.ImagesDir = filepath.Join(cfg.DataPath, "png") 1400 cfg.CSVsDir = filepath.Join(cfg.DataPath, "csv") 1401 1402 return nil 1403} 1404 1405func (cfg *Cfg) readFeatureToggles(iniFile *ini.File) error { 1406 // Read and populate feature toggles list 1407 featureTogglesSection := iniFile.Section("feature_toggles") 1408 cfg.FeatureToggles = make(map[string]bool) 1409 featuresTogglesStr := valueAsString(featureTogglesSection, "enable", "") 1410 for _, feature := range util.SplitString(featuresTogglesStr) { 1411 cfg.FeatureToggles[feature] = true 1412 } 1413 return nil 1414} 1415 1416func readAlertingSettings(iniFile *ini.File) error { 1417 alerting := iniFile.Section("alerting") 1418 enabled, err := alerting.Key("enabled").Bool() 1419 AlertingEnabled = nil 1420 if err == nil { 1421 AlertingEnabled = &enabled 1422 } 1423 ExecuteAlerts = alerting.Key("execute_alerts").MustBool(true) 1424 AlertingRenderLimit = alerting.Key("concurrent_render_limit").MustInt(5) 1425 1426 AlertingErrorOrTimeout = valueAsString(alerting, "error_or_timeout", "alerting") 1427 AlertingNoDataOrNullValues = valueAsString(alerting, "nodata_or_nullvalues", "no_data") 1428 1429 evaluationTimeoutSeconds := alerting.Key("evaluation_timeout_seconds").MustInt64(30) 1430 AlertingEvaluationTimeout = time.Second * time.Duration(evaluationTimeoutSeconds) 1431 notificationTimeoutSeconds := alerting.Key("notification_timeout_seconds").MustInt64(30) 1432 AlertingNotificationTimeout = time.Second * time.Duration(notificationTimeoutSeconds) 1433 AlertingMaxAttempts = alerting.Key("max_attempts").MustInt(3) 1434 AlertingMinInterval = alerting.Key("min_interval_seconds").MustInt64(1) 1435 1436 return nil 1437} 1438 1439func readSnapshotsSettings(cfg *Cfg, iniFile *ini.File) error { 1440 snapshots := iniFile.Section("snapshots") 1441 1442 ExternalSnapshotUrl = valueAsString(snapshots, "external_snapshot_url", "") 1443 ExternalSnapshotName = valueAsString(snapshots, "external_snapshot_name", "") 1444 1445 ExternalEnabled = snapshots.Key("external_enabled").MustBool(true) 1446 SnapShotRemoveExpired = snapshots.Key("snapshot_remove_expired").MustBool(true) 1447 cfg.SnapshotPublicMode = snapshots.Key("public_mode").MustBool(false) 1448 1449 return nil 1450} 1451 1452func (cfg *Cfg) readServerSettings(iniFile *ini.File) error { 1453 server := iniFile.Section("server") 1454 var err error 1455 AppUrl, AppSubUrl, err = cfg.parseAppUrlAndSubUrl(server) 1456 if err != nil { 1457 return err 1458 } 1459 ServeFromSubPath = server.Key("serve_from_sub_path").MustBool(false) 1460 1461 cfg.AppURL = AppUrl 1462 cfg.AppSubURL = AppSubUrl 1463 cfg.ServeFromSubPath = ServeFromSubPath 1464 cfg.Protocol = HTTPScheme 1465 1466 protocolStr := valueAsString(server, "protocol", "http") 1467 1468 if protocolStr == "https" { 1469 cfg.Protocol = HTTPSScheme 1470 cfg.CertFile = server.Key("cert_file").String() 1471 cfg.KeyFile = server.Key("cert_key").String() 1472 } 1473 if protocolStr == "h2" { 1474 cfg.Protocol = HTTP2Scheme 1475 cfg.CertFile = server.Key("cert_file").String() 1476 cfg.KeyFile = server.Key("cert_key").String() 1477 } 1478 if protocolStr == "socket" { 1479 cfg.Protocol = SocketScheme 1480 cfg.SocketPath = server.Key("socket").String() 1481 } 1482 1483 cfg.Domain = valueAsString(server, "domain", "localhost") 1484 cfg.HTTPAddr = valueAsString(server, "http_addr", DefaultHTTPAddr) 1485 cfg.HTTPPort = valueAsString(server, "http_port", "3000") 1486 cfg.RouterLogging = server.Key("router_logging").MustBool(false) 1487 1488 cfg.EnableGzip = server.Key("enable_gzip").MustBool(false) 1489 cfg.EnforceDomain = server.Key("enforce_domain").MustBool(false) 1490 staticRoot := valueAsString(server, "static_root_path", "") 1491 StaticRootPath = makeAbsolute(staticRoot, HomePath) 1492 cfg.StaticRootPath = StaticRootPath 1493 1494 if err := cfg.validateStaticRootPath(); err != nil { 1495 return err 1496 } 1497 1498 cdnURL := valueAsString(server, "cdn_url", "") 1499 if cdnURL != "" { 1500 cfg.CDNRootURL, err = url.Parse(cdnURL) 1501 if err != nil { 1502 return err 1503 } 1504 } 1505 1506 cfg.ReadTimeout = server.Key("read_timeout").MustDuration(0) 1507 1508 return nil 1509} 1510 1511// GetContentDeliveryURL returns full content delivery URL with /<edition>/<version> added to URL 1512func (cfg *Cfg) GetContentDeliveryURL(prefix string) string { 1513 if cfg.CDNRootURL != nil { 1514 url := *cfg.CDNRootURL 1515 preReleaseFolder := "" 1516 1517 url.Path = path.Join(url.Path, prefix, preReleaseFolder, cfg.BuildVersion) 1518 return url.String() + "/" 1519 } 1520 1521 return "" 1522} 1523 1524func (cfg *Cfg) readDataSourcesSettings() { 1525 datasources := cfg.Raw.Section("datasources") 1526 cfg.DataSourceLimit = datasources.Key("datasource_limit").MustInt(5000) 1527} 1528 1529func GetAllowedOriginGlobs(originPatterns []string) ([]glob.Glob, error) { 1530 var originGlobs []glob.Glob 1531 allowedOrigins := originPatterns 1532 for _, originPattern := range allowedOrigins { 1533 g, err := glob.Compile(originPattern) 1534 if err != nil { 1535 return nil, fmt.Errorf("error parsing origin pattern: %v", err) 1536 } 1537 originGlobs = append(originGlobs, g) 1538 } 1539 return originGlobs, nil 1540} 1541 1542func (cfg *Cfg) readLiveSettings(iniFile *ini.File) error { 1543 section := iniFile.Section("live") 1544 cfg.LiveMaxConnections = section.Key("max_connections").MustInt(100) 1545 if cfg.LiveMaxConnections < -1 { 1546 return fmt.Errorf("unexpected value %d for [live] max_connections", cfg.LiveMaxConnections) 1547 } 1548 cfg.LiveHAEngine = section.Key("ha_engine").MustString("") 1549 switch cfg.LiveHAEngine { 1550 case "", "redis": 1551 default: 1552 return fmt.Errorf("unsupported live HA engine type: %s", cfg.LiveHAEngine) 1553 } 1554 cfg.LiveHAEngineAddress = section.Key("ha_engine_address").MustString("127.0.0.1:6379") 1555 1556 var originPatterns []string 1557 allowedOrigins := section.Key("allowed_origins").MustString("") 1558 for _, originPattern := range strings.Split(allowedOrigins, ",") { 1559 originPattern = strings.TrimSpace(originPattern) 1560 if originPattern == "" { 1561 continue 1562 } 1563 originPatterns = append(originPatterns, originPattern) 1564 } 1565 _, err := GetAllowedOriginGlobs(originPatterns) 1566 if err != nil { 1567 return err 1568 } 1569 cfg.LiveAllowedOrigins = originPatterns 1570 return nil 1571} 1572