github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/envutil/env.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package envutil
    12  
    13  import (
    14  	"bytes"
    15  	"fmt"
    16  	"os"
    17  	"os/user"
    18  	"runtime"
    19  	"sort"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/cockroachdb/cockroachdb-parser/pkg/util/humanizeutil"
    25  	"github.com/cockroachdb/cockroachdb-parser/pkg/util/syncutil"
    26  	"github.com/cockroachdb/redact"
    27  )
    28  
    29  type envVarInfo struct {
    30  	consumer string
    31  	present  bool
    32  	value    string
    33  }
    34  
    35  var envVarRegistry struct {
    36  	mu    syncutil.Mutex
    37  	cache map[string]envVarInfo
    38  }
    39  
    40  func init() {
    41  	ClearEnvCache()
    42  }
    43  
    44  func checkVarName(name string) {
    45  	// Env vars must:
    46  	//  - be uppercase
    47  	//  - only contain letters, digits, and _
    48  	valid := true
    49  	for i := 0; valid && i < len(name); i++ {
    50  		c := name[i]
    51  		valid = ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')
    52  	}
    53  	if !valid {
    54  		panic("invalid env var name " + name)
    55  	}
    56  }
    57  
    58  func checkInternalVarName(name string) {
    59  	// Env vars must:
    60  	//  - start with COCKROACH_
    61  	//  - pass basic validity checks in checkVarName
    62  	if !strings.HasPrefix(name, "COCKROACH_") {
    63  		panic("invalid env var name " + name)
    64  	}
    65  	checkVarName(name)
    66  }
    67  
    68  func checkExternalVarName(name string) {
    69  	// Env vars must:
    70  	//  - not start with COCKROACH_
    71  	//  - pass basic validity checks in checkVarName
    72  	if strings.HasPrefix(name, "COCKROACH_") {
    73  		panic("invalid env var name " + name)
    74  	}
    75  	checkVarName(name)
    76  }
    77  
    78  // getEnv performs all of the same actions as getAndCacheEnv but also includes
    79  // a validity check of the variable name.
    80  func getEnv(varName string, depth int) (string, bool) {
    81  	checkInternalVarName(varName)
    82  	return getAndCacheEnv(varName, depth+1)
    83  }
    84  
    85  // getExternalEnv performs all of the same actions as getEnv but also asserts
    86  // that the variable is not of the form of an internal environment variable,
    87  // eg. "COCKROACH_".
    88  func getExternalEnv(varName string, depth int) (string, bool) {
    89  	checkExternalVarName(varName)
    90  	return getAndCacheEnv(varName, depth+1)
    91  }
    92  
    93  // getAndCacheEnv retrieves an environment variable, keeps track of where
    94  // it was accessed, and checks that each environment variable is accessed
    95  // from at most one place.
    96  // The bookkeeping enables a report of all influential environment
    97  // variables with "cockroach debug env". To keep this report useful,
    98  // all relevant environment variables should be read during start up.
    99  // This function should not be used directly; getEnv or getExternalEnv should
   100  // be used instead.
   101  func getAndCacheEnv(varName string, depth int) (string, bool) {
   102  	_, consumer, _, _ := runtime.Caller(depth + 1)
   103  
   104  	envVarRegistry.mu.Lock()
   105  	defer envVarRegistry.mu.Unlock()
   106  
   107  	if f, ok := envVarRegistry.cache[varName]; ok {
   108  		if f.consumer != consumer {
   109  			panic("environment variable " + varName + " already used from " + f.consumer)
   110  		}
   111  		return f.value, f.present
   112  	}
   113  	v, found := os.LookupEnv(varName)
   114  	envVarRegistry.cache[varName] = envVarInfo{consumer: consumer, present: found, value: v}
   115  	return v, found
   116  }
   117  
   118  // ClearEnvCache clears saved environment values so that
   119  // a new read access the environment again. (Used for testing)
   120  func ClearEnvCache() {
   121  	envVarRegistry.mu.Lock()
   122  	defer envVarRegistry.mu.Unlock()
   123  
   124  	envVarRegistry.cache = make(map[string]envVarInfo)
   125  }
   126  
   127  // GetEnvReport dumps all configuration variables that may have been
   128  // used and their value.
   129  func GetEnvReport() string {
   130  	envVarRegistry.mu.Lock()
   131  	defer envVarRegistry.mu.Unlock()
   132  
   133  	var b bytes.Buffer
   134  	for k, v := range envVarRegistry.cache {
   135  		if v.present {
   136  			fmt.Fprintf(&b, "%s = %s # %s\n", k, v.value, v.consumer)
   137  		} else {
   138  			fmt.Fprintf(&b, "# %s is not set (read from %s)\n", k, v.consumer)
   139  		}
   140  	}
   141  	return b.String()
   142  }
   143  
   144  // GetEnvVarsUsed returns the names of all environment variables that
   145  // may have been used.
   146  func GetEnvVarsUsed() (result []redact.RedactableString) {
   147  	allVarsRaw := os.Environ()
   148  	sort.Strings(allVarsRaw)
   149  	allVarsValues := make(map[redact.SafeString]string, len(allVarsRaw))
   150  	allVarNames := make([]redact.SafeString, 0, len(allVarsRaw))
   151  	for _, v := range allVarsRaw {
   152  		i := strings.IndexByte(v, '=')
   153  		if i < 0 {
   154  			continue
   155  		}
   156  		varName := redact.SafeString(v[:i])
   157  		allVarNames = append(allVarNames, varName)
   158  		var value string
   159  		if i+1 < len(v) {
   160  			value = v[i+1:]
   161  		}
   162  		allVarsValues[varName] = value
   163  	}
   164  
   165  	envVarRegistry.mu.Lock()
   166  	defer envVarRegistry.mu.Unlock()
   167  
   168  	for _, varName := range allVarNames {
   169  		_, crdbVar := envVarRegistry.cache[string(varName)]
   170  		_, safeVar := safeVarRegistry[varName]
   171  		if crdbVar || safeVar {
   172  			result = append(result, redact.Sprintf("%s=%s", varName, redact.Safe(allVarsValues[varName])))
   173  		} else if _, reportable := valueReportableUnsafeVarRegistry[varName]; reportable {
   174  			result = append(result, redact.Sprintf("%s=%s", varName, allVarsValues[varName]))
   175  		} else if _, reportable := nameReportableUnsafeVarRegistry[varName]; reportable {
   176  			result = append(result, redact.Sprintf("%s=...", varName))
   177  		}
   178  		// For any env var just the name could contain too many sensitive details and we
   179  		// don't really want them to show up in logs.
   180  	}
   181  
   182  	return result
   183  }
   184  
   185  // safeVarRegistry is the list of variables where we can both report
   186  // the name and the value safely: the value is known to never contain
   187  // sensitive information.
   188  var safeVarRegistry = map[redact.SafeString]struct{}{
   189  	// Go runtime.
   190  	"GOGC":        {},
   191  	"GODEBUG":     {},
   192  	"GOMAXPROCS":  {},
   193  	"GOTRACEBACK": {},
   194  	"GOMEMLIMIT":  {},
   195  	// gRPC.
   196  	"GRPC_GO_LOG_SEVERITY_LEVEL":  {},
   197  	"GRPC_GO_LOG_VERBOSITY_LEVEL": {},
   198  }
   199  
   200  // valueReportableUnsafeVarRegistry is the list of variables where we can
   201  // report the name safely, and the value as a redactable payload.
   202  // The value may contain sensitive information, but not so sensitive
   203  // that users would be unhappy to see them enclosed within redaction
   204  // markers in log files.
   205  var valueReportableUnsafeVarRegistry = map[redact.SafeString]struct{}{
   206  	"DEBUG_HTTP2_GOROUTINES": {},
   207  	"HOST_IP":                {},
   208  	"LANG":                   {},
   209  	"LC_ALL":                 {},
   210  	"LC_COLLATE":             {},
   211  	"LC_CTYPE":               {},
   212  	"LC_TIME":                {},
   213  	"LC_NUMERIC":             {},
   214  	"LC_MESSAGES":            {},
   215  	"LS_METRICS_ENABLED":     {},
   216  	"TERM":                   {},
   217  	"TZ":                     {},
   218  	"ZONEINFO":               {},
   219  	// From the Go runtime.
   220  	"LOCALDOMAIN":    {},
   221  	"RES_OPTIONS":    {},
   222  	"HOSTALIASES":    {},
   223  	"HTTP_PROXY":     {},
   224  	"HTTPS_PROXY":    {},
   225  	"NO_PROXY":       {},
   226  	"REQUEST_METHOD": {},
   227  }
   228  
   229  // nameReportableUnsafeVarRegistry is the list of variables where we can
   230  // report the name safely, but not the value in any form because it is
   231  // too likely to contain an unsafe payload that users would be horrified
   232  // to see in a log file, redaction markers or not.
   233  var nameReportableUnsafeVarRegistry = map[redact.SafeString]struct{}{
   234  	// GCP.
   235  	"GOOGLE_API_USE_MTLS":  {},
   236  	"GOOGLE_CLOUD_PROJECT": {},
   237  	// AWS.
   238  	"AWS_ACCESS_KEY":              {},
   239  	"AWS_ACCESS_KEY_ID":           {},
   240  	"AWS_PROFILE":                 {},
   241  	"AWS_REGION":                  {},
   242  	"AWS_SDK_LOAD_CONFIG":         {},
   243  	"AWS_SECRET_ACCESS_KEY":       {},
   244  	"AWS_SECRET_KEY":              {},
   245  	"AWS_SESSION_TOKEN":           {},
   246  	"AWS_SHARED_CREDENTIALS_FILE": {},
   247  	// Azure.
   248  	"AZURE_ACCESS_TOKEN_FILE": {},
   249  	"AZURE_AUTH_LOCATION":     {},
   250  	"AZURE_CONFIG_DIR":        {},
   251  	"AZURE_GO_SDK_LOG_FILE":   {},
   252  	"AZURE_GO_SDK_LOG_LEVEL":  {},
   253  	// Google auth.
   254  	"GAE_APPLICATION":     {},
   255  	"GAE_DEPLOYMENT_ID":   {},
   256  	"GAE_ENV":             {},
   257  	"GAE_INSTANCE":        {},
   258  	"GAE_LONG_APP_ID":     {},
   259  	"GAE_MINOR_VERSION":   {},
   260  	"GAE_MODULE_INSTANCE": {},
   261  	"GAE_MODULE_NAME":     {},
   262  	"GAE_PARTITION":       {},
   263  	"GAE_SERVICE":         {},
   264  	// Kerberos.
   265  	"KRB5CCNAME": {},
   266  	// Pprof.
   267  	"PPROF_BINARY_PATH": {},
   268  	"PPROF_TMPDIR":      {},
   269  	"PPROF_TOOLS":       {},
   270  	// Sentry-go.
   271  	"SENTRY_RELEASE": {},
   272  }
   273  
   274  // GetShellCommand returns a complete command to run with a prefix of the command line.
   275  func GetShellCommand(cmd string) []string {
   276  	if runtime.GOOS == "windows" {
   277  		if shell := os.Getenv("COMSPEC"); len(shell) > 0 {
   278  			return []string{shell, "/C", cmd}
   279  		}
   280  		return []string{`C:\Windows\system32\cmd.exe`, "/C", cmd}
   281  	}
   282  	if shell := os.Getenv("SHELL"); len(shell) > 0 {
   283  		return []string{shell, "-c", cmd}
   284  	}
   285  
   286  	return []string{"/bin/sh", "-c", cmd}
   287  }
   288  
   289  // HomeDir returns the user's home directory, as determined by the env
   290  // var HOME, if it exists, and otherwise the system's idea of the user
   291  // configuration (e.g. on non-UNIX systems).
   292  func HomeDir() (string, error) {
   293  	if homeDir := os.Getenv("HOME"); len(homeDir) > 0 {
   294  		return homeDir, nil
   295  	}
   296  	userAcct, err := user.Current()
   297  	if err != nil {
   298  		return "", err
   299  	}
   300  	return userAcct.HomeDir, nil
   301  }
   302  
   303  // EnvString returns the value set by the specified environment variable. The
   304  // depth argument indicates the stack depth of the caller that should be
   305  // associated with the variable.
   306  // The returned boolean flag indicates if the variable is set.
   307  func EnvString(name string, depth int) (string, bool) {
   308  	return getEnv(name, depth+1)
   309  }
   310  
   311  // ExternalEnvString returns the value set by the specified environment
   312  // variable. Only non-CRDB environment variables should be accessed via this
   313  // method. CRDB specific variables should be accessed via EnvString. The depth
   314  // argument indicates the stack depth of the caller that should be associated
   315  // with the variable. The returned boolean flag indicates if the variable is
   316  // set.
   317  func ExternalEnvString(name string, depth int) (string, bool) {
   318  	return getExternalEnv(name, depth+1)
   319  }
   320  
   321  // EnvOrDefaultString returns the value set by the specified
   322  // environment variable, if any, otherwise the specified default
   323  // value.
   324  func EnvOrDefaultString(name string, value string) string {
   325  	if v, present := getEnv(name, 1); present {
   326  		return v
   327  	}
   328  	return value
   329  }
   330  
   331  // EnvOrDefaultBool returns the value set by the specified environment
   332  // variable, if any, otherwise the specified default value.
   333  //
   334  // N.B. EnvOrDefaultBool has the desired side-effect of populating envVarRegistry.cache.
   335  // It has to be invoked during (var) init; otherwise, cli/start.go:reportConfiguration will not report the
   336  // value of this environment variable in the server log, upon startup.
   337  //
   338  //	Correct Usage: var allowUpgradeToDev = envutil.EnvOrDefaultBool("COCKROACH_UPGRADE_TO_DEV_VERSION", false)
   339  //
   340  //	Incorrect Usage: func() {
   341  //											...
   342  //											var allowUpgradeToDev envutil.EnvOrDefaultBool("COCKROACH_UPGRADE_TO_DEV_VERSION", false)
   343  //										}
   344  //
   345  // N.B. The same rule applies to the remaining EnvOrDefaultXXX defined here.
   346  func EnvOrDefaultBool(name string, value bool) bool {
   347  	if str, present := getEnv(name, 1); present {
   348  		v, err := strconv.ParseBool(str)
   349  		if err != nil {
   350  			panic(fmt.Sprintf("error parsing %s: %s", name, err))
   351  		}
   352  		return v
   353  	}
   354  	return value
   355  }
   356  
   357  // EnvOrDefaultInt returns the value set by the specified environment
   358  // variable, if any, otherwise the specified default value.
   359  func EnvOrDefaultInt(name string, value int) int {
   360  	if str, present := getEnv(name, 1); present {
   361  		v, err := strconv.ParseInt(str, 0, 0)
   362  		if err != nil {
   363  			panic(fmt.Sprintf("error parsing %s: %s", name, err))
   364  		}
   365  		return int(v)
   366  	}
   367  	return value
   368  }
   369  
   370  // EnvOrDefaultInt64 returns the value set by the specified environment
   371  // variable, if any, otherwise the specified default value.
   372  func EnvOrDefaultInt64(name string, value int64) int64 {
   373  	if str, present := getEnv(name, 1); present {
   374  		v, err := strconv.ParseInt(str, 0, 64)
   375  		if err != nil {
   376  			panic(fmt.Sprintf("error parsing %s: %s", name, err))
   377  		}
   378  		return v
   379  	}
   380  	return value
   381  }
   382  
   383  // EnvOrDefaultFloat64 returns the value set by the specified environment
   384  // variable, if any, otherwise the specified default value.
   385  func EnvOrDefaultFloat64(name string, value float64) float64 {
   386  	if str, present := getEnv(name, 1); present {
   387  		v, err := strconv.ParseFloat(str, 64)
   388  		if err != nil {
   389  			panic(fmt.Sprintf("error parsing %s: %s", name, err))
   390  		}
   391  		return v
   392  	}
   393  	return value
   394  }
   395  
   396  var _ = EnvOrDefaultFloat64 // silence unused warning
   397  
   398  // EnvOrDefaultBytes returns the value set by the specified environment
   399  // variable, if any, otherwise the specified default value.
   400  func EnvOrDefaultBytes(name string, value int64) int64 {
   401  	if str, present := getEnv(name, 1); present {
   402  		v, err := humanizeutil.ParseBytes(str)
   403  		if err != nil {
   404  			panic(fmt.Sprintf("error parsing %s: %s", name, err))
   405  		}
   406  		return v
   407  	}
   408  	return value
   409  }
   410  
   411  // EnvOrDefaultDuration returns the value set by the specified environment
   412  // variable, if any, otherwise the specified default value.
   413  func EnvOrDefaultDuration(name string, value time.Duration) time.Duration {
   414  	if str, present := getEnv(name, 1); present {
   415  		v, err := time.ParseDuration(str)
   416  		if err != nil {
   417  			panic(fmt.Sprintf("error parsing %s: %s", name, err))
   418  		}
   419  		return v
   420  	}
   421  	return value
   422  }
   423  
   424  // TB is a slimmed down version of testing.T for use below.
   425  // We would like to use testutils.TB but this is not possible
   426  // due to a dependency cycle.
   427  type TB interface {
   428  	Fatal(args ...interface{})
   429  	Helper()
   430  }
   431  
   432  // TestSetEnv sets an environment variable and the cleanup function
   433  // resets it to the original value.
   434  func TestSetEnv(t TB, name string, value string) func() {
   435  	t.Helper()
   436  	ClearEnvCache()
   437  	before, exists := os.LookupEnv(name)
   438  
   439  	if err := os.Setenv(name, value); err != nil {
   440  		t.Fatal(err)
   441  	}
   442  	return func() {
   443  		if exists {
   444  			if err := os.Setenv(name, before); err != nil {
   445  				t.Fatal(err)
   446  			}
   447  		} else {
   448  			if err := os.Unsetenv(name); err != nil {
   449  				t.Fatal(err)
   450  			}
   451  		}
   452  		ClearEnvCache()
   453  	}
   454  }
   455  
   456  // TestUnsetEnv unsets an environment variable and the cleanup function
   457  // resets it to the original value.
   458  func TestUnsetEnv(t TB, name string) func() {
   459  	t.Helper()
   460  	ClearEnvCache()
   461  	before, exists := os.LookupEnv(name)
   462  	if !exists {
   463  		return func() {}
   464  	}
   465  	if err := os.Unsetenv(name); err != nil {
   466  		t.Fatal(err)
   467  	}
   468  	return func() {
   469  		if err := os.Setenv(name, before); err != nil {
   470  			t.Fatal(err)
   471  		}
   472  		ClearEnvCache()
   473  	}
   474  }