diff --git a/pkg/serverless/appsec/appsec.go b/pkg/serverless/appsec/appsec.go index 94d2b31409ae1..d0147a40d63e6 100644 --- a/pkg/serverless/appsec/appsec.go +++ b/pkg/serverless/appsec/appsec.go @@ -120,7 +120,7 @@ func (a *AppSec) Monitor(addresses map[string]any) (res waf.Result) { timeout := a.cfg.WafTimeout // Ask the WAF for schema reporting if API security is enabled - if config.APISecurityEnabled() && config.APISecuritySampleRate() >= rand.Float64() { + if a.canExtractSchemas() { addresses["waf.context.processor"] = map[string]any{"extract-schema": true} } @@ -140,7 +140,7 @@ func (a *AppSec) Monitor(addresses map[string]any) (res waf.Result) { } if !a.eventsRateLimiter.Allow() { log.Debugf("appsec: security events discarded: the rate limit of %d events/s is reached", a.cfg.TraceRateLimit) - return waf.Result{} + res = waf.Result{} } return res } @@ -157,3 +157,9 @@ func wafHealth() error { } return nil } + +// canExtractSchemas checks that API Security is enabled +// and that sampling rate allows schema extraction for a specific monitoring instance +func (a *AppSec) canExtractSchemas() bool { + return a.cfg.APISec.Enabled && a.cfg.APISec.SampleRate >= rand.Float64() +} diff --git a/pkg/serverless/appsec/appsec_test.go b/pkg/serverless/appsec/appsec_test.go index d8b8e13f35ee6..ba6c1b0e42a0c 100644 --- a/pkg/serverless/appsec/appsec_test.go +++ b/pkg/serverless/appsec/appsec_test.go @@ -11,7 +11,6 @@ import ( "strconv" "testing" - "github.com/DataDog/datadog-agent/pkg/serverless/appsec/config" waf "github.com/DataDog/go-libddwaf/v2" "github.com/stretchr/testify/require" @@ -49,13 +48,13 @@ func TestMonitor(t *testing.T) { t.Skip("host not supported by appsec", err) } - t.Setenv("DD_SERVERLESS_APPSEC_ENABLED", "true") - t.Setenv("DD_APPSEC_WAF_TIMEOUT", "2s") - asm, err := newAppSec() - require.NoError(t, err) - require.Nil(t, err) - t.Run("events-detection", func(t *testing.T) { + t.Setenv("DD_SERVERLESS_APPSEC_ENABLED", "true") + t.Setenv("DD_APPSEC_WAF_TIMEOUT", "2s") + asm, err := newAppSec() + require.NoError(t, err) + defer asm.Close() + uri := "/path/to/resource/../../../../../database.yml.sqlite3" addresses := map[string]interface{}{ "server.request.uri.raw": uri, @@ -77,7 +76,11 @@ func TestMonitor(t *testing.T) { t.Run("api-security", func(t *testing.T) { t.Setenv("DD_API_SECURITY_REQUEST_SAMPLE_RATE", "1.0") t.Setenv("DD_EXPERIMENTAL_API_SECURITY_ENABLED", "true") - config.Refresh() + t.Setenv("DD_SERVERLESS_APPSEC_ENABLED", "true") + t.Setenv("DD_APPSEC_WAF_TIMEOUT", "2s") + asm, err := newAppSec() + require.NoError(t, err) + defer asm.Close() for _, tc := range []struct { name string pathParams map[string]any diff --git a/pkg/serverless/appsec/config/config.go b/pkg/serverless/appsec/config/config.go index e02916aa69594..8108e320310dd 100644 --- a/pkg/serverless/appsec/config/config.go +++ b/pkg/serverless/appsec/config/config.go @@ -9,69 +9,35 @@ package config import ( "fmt" "os" - "regexp" "strconv" "time" - "unicode" - "unicode/utf8" "github.com/DataDog/appsec-internal-go/appsec" - "github.com/DataDog/datadog-agent/pkg/util/log" ) const ( - enabledEnvVar = "DD_SERVERLESS_APPSEC_ENABLED" - rulesEnvVar = "DD_APPSEC_RULES" - wafTimeoutEnvVar = "DD_APPSEC_WAF_TIMEOUT" - traceRateLimitEnvVar = "DD_APPSEC_TRACE_RATE_LIMIT" - obfuscatorKeyEnvVar = "DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP" - obfuscatorValueEnvVar = "DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP" - tracingEnabledEnvVar = "DD_APM_TRACING_ENABLED" - apiSecurityEnabledEnvVar = "DD_EXPERIMENTAL_API_SECURITY_ENABLED" - apiSecuritySampleRateEnvVar = "DD_API_SECURITY_REQUEST_SAMPLE_RATE" + enabledEnvVar = "DD_SERVERLESS_APPSEC_ENABLED" + tracingEnabledEnvVar = "DD_APM_TRACING_ENABLED" ) -const ( - defaultAPISecSampleRate = 10. / 100 - defaultWAFTimeout = 4 * time.Millisecond - defaultTraceRate = 100 // up to 100 appsec traces/s - defaultObfuscatorKeyRegex = `(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization` - defaultObfuscatorValueRegex = `(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:\s*=[^;]|"\s*:\s*"[^"]+")|bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}` -) +var standalone bool // StartOption is used to customize the AppSec configuration when invoked with appsec.Start() type StartOption func(c *Config) // Config is the AppSec configuration. type Config struct { - // rules loaded via the env var DD_APPSEC_RULES. When not set, the builtin rules will be used. + // Rules is the security rules loaded via the env var DD_APPSEC_RULES. + // When not set, the builtin rules will be used. Rules []byte - // Maximum WAF execution time + // WafTimeout is the maximum WAF execution time WafTimeout time.Duration - // AppSec trace rate limit (traces per second). + // TraceRateLimit is the rate limit of AppSec traces (per second). TraceRateLimit uint - // Obfuscator configuration parameters - Obfuscator ObfuscatorConfig -} - -// ObfuscatorConfig wraps the key and value regexp to be passed to the WAF to perform obfuscation. -type ObfuscatorConfig struct { - KeyRegex string - ValueRegex string -} - -// Config variables assigned at init by reading the environment -var ( - apiSecEnabled bool - apiSecSampleRate float64 - standalone bool -) - -// Refresh updates the configuration by reading from the environment -func Refresh() { - standalone = isStandalone() - apiSecEnabled = apiSecurityEnabled() - apiSecSampleRate = apiSecuritySampleRate() + // Obfuscator is the configuration for sensitive data obfuscation (in-WAF) + Obfuscator appsec.ObfuscatorConfig + // APISec is the configuration for API Security schema collection + APISec appsec.APISecConfig } // IsEnabled returns true when appsec is enabled when the environment variable @@ -93,120 +59,21 @@ func IsStandalone() bool { return standalone } -// APISecurityEnabled returns whether API security is enabled through the environment -func APISecurityEnabled() bool { - return apiSecEnabled -} - -// APISecuritySampleRate returns the sample rate for API Security schema collection, read from the env -func APISecuritySampleRate() float64 { - return apiSecSampleRate -} - // NewConfig returns a new appsec configuration read from the environment func NewConfig() (*Config, error) { - rules, err := readRulesConfig() + rules, err := appsec.RulesFromEnv() if err != nil { return nil, err } return &Config{ Rules: rules, - WafTimeout: readWAFTimeoutConfig(), - TraceRateLimit: readRateLimitConfig(), - Obfuscator: readObfuscatorConfig(), + WafTimeout: appsec.WAFTimeoutFromEnv(), + TraceRateLimit: appsec.RateLimitFromEnv(), + Obfuscator: appsec.NewObfuscatorConfig(), + APISec: appsec.NewAPISecConfig(), }, nil } -func readWAFTimeoutConfig() (timeout time.Duration) { - timeout = defaultWAFTimeout - value := os.Getenv(wafTimeoutEnvVar) - if value == "" { - return - } - - // Check if the value ends with a letter, which means the user has - // specified their own time duration unit(s) such as 1s200ms. - // Otherwise, default to microseconds. - if lastRune, _ := utf8.DecodeLastRuneInString(value); !unicode.IsLetter(lastRune) { - value += "us" // Add the default microsecond time-duration suffix - } - - parsed, err := time.ParseDuration(value) - if err != nil { - logEnvVarParsingError(wafTimeoutEnvVar, value, err, timeout) - return - } - if parsed <= 0 { - logUnexpectedEnvVarValue(wafTimeoutEnvVar, parsed, "expecting a strictly positive duration", timeout) - return - } - return parsed -} - -func readRateLimitConfig() (rate uint) { - rate = defaultTraceRate - value := os.Getenv(traceRateLimitEnvVar) - if value == "" { - return rate - } - parsed, err := strconv.ParseUint(value, 10, 0) - if err != nil { - logEnvVarParsingError(traceRateLimitEnvVar, value, err, rate) - return - } - if rate == 0 { - logUnexpectedEnvVarValue(traceRateLimitEnvVar, parsed, "expecting a value strictly greater than 0", rate) - return - } - return uint(parsed) -} - -func readObfuscatorConfig() ObfuscatorConfig { - keyRE := readObfuscatorConfigRegexp(obfuscatorKeyEnvVar, defaultObfuscatorKeyRegex) - valueRE := readObfuscatorConfigRegexp(obfuscatorValueEnvVar, defaultObfuscatorValueRegex) - return ObfuscatorConfig{KeyRegex: keyRE, ValueRegex: valueRE} -} - -func readObfuscatorConfigRegexp(name, defaultValue string) string { - val, present := os.LookupEnv(name) - if !present { - log.Debugf("appsec: %s not defined, starting with the default obfuscator regular expression", name) - return defaultValue - } - if _, err := regexp.Compile(val); err != nil { - log.Errorf("appsec: could not compile the configured obfuscator regular expression `%s=%s`. Using the default value instead", name, val) - return defaultValue - } - log.Debugf("appsec: starting with the configured obfuscator regular expression %s", name) - return val -} - -func readRulesConfig() (rules []byte, err error) { - rules = []byte(appsec.StaticRecommendedRules) - filepath := os.Getenv(rulesEnvVar) - if filepath == "" { - log.Info("appsec: starting with the default recommended security rules") - return rules, nil - } - buf, err := os.ReadFile(filepath) - if err != nil { - if os.IsNotExist(err) { - log.Errorf("appsec: could not find the rules file in path %s: %v.", filepath, err) - } - return nil, err - } - log.Info("appsec: starting with the security rules from file %s", filepath) - return buf, nil -} - -func logEnvVarParsingError(name, value string, err error, defaultValue interface{}) { - log.Errorf("appsec: could not parse the env var %s=%s as a duration: %v. Using default value %v.", name, value, err, defaultValue) -} - -func logUnexpectedEnvVarValue(name string, value interface{}, reason string, defaultValue interface{}) { - log.Errorf("appsec: unexpected configuration value of %s=%v: %s. Using default value %v.", name, value, reason, defaultValue) -} - // isStandalone is reads the env and reports whether appsec runs in standalone mode // Used at init and for testing func isStandalone() bool { @@ -215,24 +82,6 @@ func isStandalone() bool { return set && !enabled } -func apiSecurityEnabled() bool { - enabled, _ := strconv.ParseBool(os.Getenv(apiSecurityEnabledEnvVar)) - return enabled -} - -func apiSecuritySampleRate() float64 { - rate, err := strconv.ParseFloat(os.Getenv(apiSecuritySampleRateEnvVar), 64) - if err != nil { - log.Debugf("appsec: could not parse %s. Defaulting to %f", apiSecuritySampleRateEnvVar, defaultAPISecSampleRate) - return defaultAPISecSampleRate - } - if rate < 0. || rate > 1. { - log.Debugf("appsec: %s value must be between 0 and 1. Defaulting to %f", apiSecuritySampleRateEnvVar, defaultAPISecSampleRate) - return defaultAPISecSampleRate - } - return rate -} - func init() { - Refresh() + standalone = isStandalone() } diff --git a/pkg/serverless/appsec/config/config_test.go b/pkg/serverless/appsec/config/config_test.go index 1e27be214f50c..a4bd29ea9c724 100644 --- a/pkg/serverless/appsec/config/config_test.go +++ b/pkg/serverless/appsec/config/config_test.go @@ -6,11 +6,8 @@ package config import ( - "os" - "testing" - "time" - "github.com/DataDog/appsec-internal-go/appsec" + "testing" "github.com/stretchr/testify/require" ) @@ -18,233 +15,63 @@ import ( func TestConfig(t *testing.T) { expectedDefaultConfig := &Config{ Rules: []byte(appsec.StaticRecommendedRules), - WafTimeout: defaultWAFTimeout, - TraceRateLimit: defaultTraceRate, - Obfuscator: ObfuscatorConfig{ - KeyRegex: defaultObfuscatorKeyRegex, - ValueRegex: defaultObfuscatorValueRegex, - }, + WafTimeout: appsec.WAFTimeoutFromEnv(), + TraceRateLimit: appsec.RateLimitFromEnv(), + Obfuscator: appsec.NewObfuscatorConfig(), + APISec: appsec.NewAPISecConfig(), } t.Run("default", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() cfg, err := NewConfig() require.NoError(t, err) require.Equal(t, expectedDefaultConfig, cfg) }) - t.Run("waf-timeout", func(t *testing.T) { - t.Run("parsable", func(t *testing.T) { - expCfg := *expectedDefaultConfig - expCfg.WafTimeout = 5 * time.Second - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(wafTimeoutEnvVar, "5s")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, &expCfg, cfg) - }) - - t.Run("parsable-default-microsecond", func(t *testing.T) { - expCfg := *expectedDefaultConfig - expCfg.WafTimeout = 1 * time.Microsecond - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(wafTimeoutEnvVar, "1")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, &expCfg, cfg) - }) - - t.Run("not-parsable", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(wafTimeoutEnvVar, "not a duration string")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) - }) - - t.Run("negative", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(wafTimeoutEnvVar, "-1s")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) - }) - - t.Run("zero", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(wafTimeoutEnvVar, "0")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) - }) - - t.Run("empty-string", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(wafTimeoutEnvVar, "")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) - }) - }) - - t.Run("rules", func(t *testing.T) { - t.Run("empty-string", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - os.Setenv(rulesEnvVar, "") - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) - }) - - t.Run("file-not-found", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - os.Setenv(rulesEnvVar, "i do not exist") - cfg, err := NewConfig() - require.Error(t, err) - require.Nil(t, cfg) - }) - - t.Run("local-file", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - file, err := os.CreateTemp("", "example-*") - require.NoError(t, err) - defer func() { - file.Close() - os.Remove(file.Name()) - }() - expectedRules := `custom rule file content` - expCfg := *expectedDefaultConfig - expCfg.Rules = []byte(expectedRules) - _, err = file.WriteString(expectedRules) - require.NoError(t, err) - os.Setenv(rulesEnvVar, file.Name()) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, &expCfg, cfg) - }) - }) - - t.Run("trace-rate-limit", func(t *testing.T) { - t.Run("parsable", func(t *testing.T) { - expCfg := *expectedDefaultConfig - expCfg.TraceRateLimit = 1234567890 - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(traceRateLimitEnvVar, "1234567890")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, &expCfg, cfg) - }) - - t.Run("not-parsable", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(wafTimeoutEnvVar, "not a uint")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) - }) - - t.Run("negative", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(wafTimeoutEnvVar, "-1")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) - }) - - t.Run("zero", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(wafTimeoutEnvVar, "0")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) - }) - - t.Run("empty-string", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(wafTimeoutEnvVar, "")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) - }) - }) - - t.Run("obfuscator", func(t *testing.T) { - t.Run("key-regexp", func(t *testing.T) { - t.Run("env-var-normal", func(t *testing.T) { - expCfg := *expectedDefaultConfig - expCfg.Obfuscator.KeyRegex = "test" - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(obfuscatorKeyEnvVar, "test")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, &expCfg, cfg) - }) - t.Run("env-var-empty", func(t *testing.T) { - expCfg := *expectedDefaultConfig - expCfg.Obfuscator.KeyRegex = "" - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(obfuscatorKeyEnvVar, "")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, &expCfg, cfg) - }) - t.Run("compile-error", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(obfuscatorKeyEnvVar, "+")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) - }) - }) - - t.Run("value-regexp", func(t *testing.T) { - t.Run("env-var-normal", func(t *testing.T) { - expCfg := *expectedDefaultConfig - expCfg.Obfuscator.ValueRegex = "test" - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(obfuscatorValueEnvVar, "test")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, &expCfg, cfg) - }) - t.Run("env-var-empty", func(t *testing.T) { - expCfg := *expectedDefaultConfig - expCfg.Obfuscator.ValueRegex = "" - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(obfuscatorValueEnvVar, "")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, &expCfg, cfg) - }) - t.Run("compile-error", func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - require.NoError(t, os.Setenv(obfuscatorValueEnvVar, "+")) - cfg, err := NewConfig() - require.NoError(t, err) - require.Equal(t, expectedDefaultConfig, cfg) + t.Run("appsec", func(t *testing.T) { + for _, tc := range []struct { + name string + env string + enabled bool + }{ + { + name: "default", + }, + { + name: "enabled: 0", + env: "0", + }, + { + name: "enabled: false", + env: "false", + }, + { + name: "enabled: -1", + env: "-1", + }, + { + name: "enabled: junk data", + env: "junk data", + }, + { + name: "enabled: 1", + env: "1", + enabled: true, + }, + { + name: "enabled: true", + env: "1", + enabled: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Setenv(enabledEnvVar, tc.env) + enabled, _, err := IsEnabled() + if tc.enabled { + require.NoError(t, err) + } + require.Equal(t, tc.enabled, enabled) }) - }) + } }) t.Run("standalone", func(t *testing.T) { @@ -281,106 +108,11 @@ func TestConfig(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - restoreEnv := cleanEnv() - defer restoreEnv() - // This is what happens at init() in config.go if tc.env != "" { - require.NoError(t, os.Setenv(tracingEnabledEnvVar, tc.env)) + t.Setenv(tracingEnabledEnvVar, tc.env) } - Refresh() - require.Equal(t, tc.standalone, IsStandalone()) + require.Equal(t, tc.standalone, isStandalone()) }) } - }) - - t.Run("api security", func(t *testing.T) { - for _, tc := range []struct { - name string - enabledVar string - sampleRateVar string - enabled bool - sampleRate float64 - }{ - { - name: "disabled", - sampleRate: defaultAPISecSampleRate, - }, - { - name: "disabled", - enabledVar: "false", - sampleRate: defaultAPISecSampleRate, - }, - { - name: "disabled", - enabledVar: "0", - sampleRate: defaultAPISecSampleRate, - }, - { - name: "disabled", - enabledVar: "weirdvalue", - sampleRate: defaultAPISecSampleRate, - }, - { - name: "enabled", - enabledVar: "true", - enabled: true, - sampleRate: defaultAPISecSampleRate, - }, - { - name: "enabled", - enabledVar: "1", - enabled: true, - sampleRate: defaultAPISecSampleRate, - }, - { - name: "sampleRate 1.0", - enabledVar: "true", - sampleRateVar: "1.0", - enabled: true, - sampleRate: 1.0, - }, - } { - t.Run(tc.name, func(t *testing.T) { - t.Setenv(apiSecurityEnabledEnvVar, tc.enabledVar) - t.Setenv(apiSecuritySampleRateEnvVar, tc.sampleRateVar) - Refresh() - require.Equal(t, tc.enabled, APISecurityEnabled()) - require.Equal(t, tc.sampleRate, APISecuritySampleRate()) - }) - } - - }) -} - -func cleanEnv() func() { - env := map[string]string{ - wafTimeoutEnvVar: os.Getenv(wafTimeoutEnvVar), - rulesEnvVar: os.Getenv(rulesEnvVar), - traceRateLimitEnvVar: os.Getenv(traceRateLimitEnvVar), - obfuscatorKeyEnvVar: os.Getenv(obfuscatorKeyEnvVar), - obfuscatorValueEnvVar: os.Getenv(obfuscatorValueEnvVar), - } - for k := range env { - if err := os.Unsetenv(k); err != nil { - panic(err) - } - } - return func() { - for k, v := range env { - restoreEnv(k, v) - } - } -} - -func restoreEnv(key, value string) { - if value != "" { - if err := os.Setenv(key, value); err != nil { - panic(err) - } - } else { - if err := os.Unsetenv(key); err != nil { - panic(err) - } - } }