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 }