config.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. // Unless explicitly stated otherwise all files in this repository are licensed
  2. // under the Apache License Version 2.0.
  3. // This product includes software developed at Datadog (https://www.datadoghq.com/).
  4. // Copyright 2016 Datadog, Inc.
  5. package appsec
  6. import (
  7. "fmt"
  8. "os"
  9. "regexp"
  10. "strconv"
  11. "time"
  12. "unicode"
  13. "unicode/utf8"
  14. "gopkg.in/DataDog/dd-trace-go.v1/internal/log"
  15. "gopkg.in/DataDog/dd-trace-go.v1/internal/remoteconfig"
  16. )
  17. const (
  18. enabledEnvVar = "DD_APPSEC_ENABLED"
  19. rulesEnvVar = "DD_APPSEC_RULES"
  20. wafTimeoutEnvVar = "DD_APPSEC_WAF_TIMEOUT"
  21. traceRateLimitEnvVar = "DD_APPSEC_TRACE_RATE_LIMIT"
  22. obfuscatorKeyEnvVar = "DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP"
  23. obfuscatorValueEnvVar = "DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP"
  24. )
  25. const (
  26. defaultWAFTimeout = 4 * time.Millisecond
  27. defaultTraceRate = 100 // up to 100 appsec traces/s
  28. defaultObfuscatorKeyRegex = `(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization`
  29. 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,}`
  30. )
  31. // StartOption is used to customize the AppSec configuration when invoked with appsec.Start()
  32. type StartOption func(c *Config)
  33. // Config is the AppSec configuration.
  34. type Config struct {
  35. // rules loaded via the env var DD_APPSEC_RULES. When not set, the builtin rules will be used.
  36. rules []byte
  37. // Maximum WAF execution time
  38. wafTimeout time.Duration
  39. // AppSec trace rate limit (traces per second).
  40. traceRateLimit uint
  41. // Obfuscator configuration parameters
  42. obfuscator ObfuscatorConfig
  43. // rc is the remote configuration client used to receive product configuration updates. Nil if rc is disabled (default)
  44. rc *remoteconfig.ClientConfig
  45. }
  46. // WithRCConfig sets the AppSec remote config client configuration to the specified cfg
  47. func WithRCConfig(cfg remoteconfig.ClientConfig) StartOption {
  48. return func(c *Config) {
  49. c.rc = &cfg
  50. }
  51. }
  52. // ObfuscatorConfig wraps the key and value regexp to be passed to the WAF to perform obfuscation.
  53. type ObfuscatorConfig struct {
  54. KeyRegex string
  55. ValueRegex string
  56. }
  57. // isEnabled returns true when appsec is enabled when the environment variable
  58. // It also returns whether the env var is actually set in the env or not
  59. // DD_APPSEC_ENABLED is set to true.
  60. func isEnabled() (enabled bool, set bool, err error) {
  61. enabledStr, set := os.LookupEnv(enabledEnvVar)
  62. if enabledStr == "" {
  63. return false, set, nil
  64. } else if enabled, err = strconv.ParseBool(enabledStr); err != nil {
  65. return false, set, fmt.Errorf("could not parse %s value `%s` as a boolean value", enabledEnvVar, enabledStr)
  66. } else {
  67. return enabled, set, nil
  68. }
  69. }
  70. func newConfig() (*Config, error) {
  71. rules, err := readRulesConfig()
  72. if err != nil {
  73. return nil, err
  74. }
  75. return &Config{
  76. rules: rules,
  77. wafTimeout: readWAFTimeoutConfig(),
  78. traceRateLimit: readRateLimitConfig(),
  79. obfuscator: readObfuscatorConfig(),
  80. }, nil
  81. }
  82. func readWAFTimeoutConfig() (timeout time.Duration) {
  83. timeout = defaultWAFTimeout
  84. value := os.Getenv(wafTimeoutEnvVar)
  85. if value == "" {
  86. return
  87. }
  88. // Check if the value ends with a letter, which means the user has
  89. // specified their own time duration unit(s) such as 1s200ms.
  90. // Otherwise, default to microseconds.
  91. if lastRune, _ := utf8.DecodeLastRuneInString(value); !unicode.IsLetter(lastRune) {
  92. value += "us" // Add the default microsecond time-duration suffix
  93. }
  94. parsed, err := time.ParseDuration(value)
  95. if err != nil {
  96. logEnvVarParsingError(wafTimeoutEnvVar, value, err, timeout)
  97. return
  98. }
  99. if parsed <= 0 {
  100. logUnexpectedEnvVarValue(wafTimeoutEnvVar, parsed, "expecting a strictly positive duration", timeout)
  101. return
  102. }
  103. return parsed
  104. }
  105. func readRateLimitConfig() (rate uint) {
  106. rate = defaultTraceRate
  107. value := os.Getenv(traceRateLimitEnvVar)
  108. if value == "" {
  109. return rate
  110. }
  111. parsed, err := strconv.ParseUint(value, 10, 0)
  112. if err != nil {
  113. logEnvVarParsingError(traceRateLimitEnvVar, value, err, rate)
  114. return
  115. }
  116. if rate == 0 {
  117. logUnexpectedEnvVarValue(traceRateLimitEnvVar, parsed, "expecting a value strictly greater than 0", rate)
  118. return
  119. }
  120. return uint(parsed)
  121. }
  122. func readObfuscatorConfig() ObfuscatorConfig {
  123. keyRE := readObfuscatorConfigRegexp(obfuscatorKeyEnvVar, defaultObfuscatorKeyRegex)
  124. valueRE := readObfuscatorConfigRegexp(obfuscatorValueEnvVar, defaultObfuscatorValueRegex)
  125. return ObfuscatorConfig{KeyRegex: keyRE, ValueRegex: valueRE}
  126. }
  127. func readObfuscatorConfigRegexp(name, defaultValue string) string {
  128. val, present := os.LookupEnv(name)
  129. if !present {
  130. log.Debug("appsec: %s not defined, starting with the default obfuscator regular expression", name)
  131. return defaultValue
  132. }
  133. if _, err := regexp.Compile(val); err != nil {
  134. log.Error("appsec: could not compile the configured obfuscator regular expression `%s=%s`. Using the default value instead", name, val)
  135. return defaultValue
  136. }
  137. log.Debug("appsec: starting with the configured obfuscator regular expression %s", name)
  138. return val
  139. }
  140. func readRulesConfig() (rules []byte, err error) {
  141. rules = []byte(staticRecommendedRules)
  142. filepath := os.Getenv(rulesEnvVar)
  143. if filepath == "" {
  144. log.Info("appsec: starting with the default recommended security rules")
  145. return rules, nil
  146. }
  147. buf, err := os.ReadFile(filepath)
  148. if err != nil {
  149. if os.IsNotExist(err) {
  150. log.Error("appsec: could not find the rules file in path %s: %v.", filepath, err)
  151. }
  152. return nil, err
  153. }
  154. log.Info("appsec: starting with the security rules from file %s", filepath)
  155. return buf, nil
  156. }
  157. func logEnvVarParsingError(name, value string, err error, defaultValue interface{}) {
  158. log.Error("appsec: could not parse the env var %s=%s as a duration: %v. Using default value %v.", name, value, err, defaultValue)
  159. }
  160. func logUnexpectedEnvVarValue(name string, value interface{}, reason string, defaultValue interface{}) {
  161. log.Error("appsec: unexpected configuration value of %s=%v: %s. Using default value %v.", name, value, reason, defaultValue)
  162. }