remoteconfig.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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 2022 Datadog, Inc.
  5. //go:build appsec
  6. // +build appsec
  7. package appsec
  8. import (
  9. "encoding/json"
  10. "fmt"
  11. "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/waf"
  12. "gopkg.in/DataDog/dd-trace-go.v1/internal/log"
  13. "gopkg.in/DataDog/dd-trace-go.v1/internal/remoteconfig"
  14. rc "github.com/DataDog/datadog-agent/pkg/remoteconfig/state"
  15. )
  16. func genApplyStatus(ack bool, err error) rc.ApplyStatus {
  17. status := rc.ApplyStatus{
  18. State: rc.ApplyStateUnacknowledged,
  19. }
  20. if err != nil {
  21. status.State = rc.ApplyStateError
  22. status.Error = err.Error()
  23. } else if ack {
  24. status.State = rc.ApplyStateAcknowledged
  25. }
  26. return status
  27. }
  28. func statusesFromUpdate(u remoteconfig.ProductUpdate, ack bool, err error) map[string]rc.ApplyStatus {
  29. statuses := make(map[string]rc.ApplyStatus, len(u))
  30. for path := range u {
  31. statuses[path] = genApplyStatus(ack, err)
  32. }
  33. return statuses
  34. }
  35. // asmFeaturesCallback deserializes an ASM_FEATURES configuration received through remote config
  36. // and starts/stops appsec accordingly. Used as a callback for the ASM_FEATURES remote config product.
  37. func (a *appsec) asmFeaturesCallback(u remoteconfig.ProductUpdate) map[string]rc.ApplyStatus {
  38. statuses := statusesFromUpdate(u, false, nil)
  39. if l := len(u); l > 1 {
  40. log.Error("appsec: Remote config: %d configs received for ASM_FEATURES. Expected one at most, returning early", l)
  41. return statuses
  42. }
  43. for path, raw := range u {
  44. var data rc.ASMFeaturesData
  45. status := rc.ApplyStatus{State: rc.ApplyStateAcknowledged}
  46. var err error
  47. log.Debug("appsec: Remote config: processing %s", path)
  48. // A nil config means ASM was disabled, and we stopped receiving the config file
  49. // Don't ack the config in this case and return early
  50. if raw == nil {
  51. log.Debug("appsec: Remote config: Stopping AppSec")
  52. a.stop()
  53. return statuses
  54. }
  55. if err = json.Unmarshal(raw, &data); err != nil {
  56. log.Error("appsec: Remote config: error while unmarshalling %s: %v. Configuration won't be applied.", path, err)
  57. } else if data.ASM.Enabled && !a.started {
  58. log.Debug("appsec: Remote config: Starting AppSec")
  59. if err = a.start(); err != nil {
  60. log.Error("appsec: Remote config: error while processing %s. Configuration won't be applied: %v", path, err)
  61. }
  62. } else if !data.ASM.Enabled && a.started {
  63. log.Debug("appsec: Remote config: Stopping AppSec")
  64. a.stop()
  65. }
  66. if err != nil {
  67. status = genApplyStatus(false, err)
  68. }
  69. statuses[path] = status
  70. }
  71. return statuses
  72. }
  73. type wafHandleWrapper struct {
  74. handle interface {
  75. UpdateRulesData([]rc.ASMDataRuleData) error
  76. }
  77. }
  78. func (h *wafHandleWrapper) asmDataCallback(u remoteconfig.ProductUpdate) map[string]rc.ApplyStatus {
  79. // Following the RFC, merging should only happen when two rules data with the same ID and same Type are received
  80. // allRulesData[ID][Type] will return the rules data of said id and type, if it exists
  81. allRulesData := make(map[string]map[string]rc.ASMDataRuleData)
  82. statuses := statusesFromUpdate(u, true, nil)
  83. for path, raw := range u {
  84. log.Debug("appsec: Remote config: processing %s", path)
  85. // A nil config means ASM_DATA was disabled, and we stopped receiving the config file
  86. // Don't ack the config in this case
  87. if raw == nil {
  88. log.Debug("appsec: remote config: %s disabled", path)
  89. statuses[path] = genApplyStatus(false, nil)
  90. continue
  91. }
  92. var rulesData rc.ASMDataRulesData
  93. if err := json.Unmarshal(raw, &rulesData); err != nil {
  94. log.Debug("appsec: Remote config: error while unmarshalling payload for %s: %v. Configuration won't be applied.", path, err)
  95. statuses[path] = genApplyStatus(false, err)
  96. continue
  97. }
  98. // Check each entry against allRulesData to see if merging is necessary
  99. for _, ruleData := range rulesData.RulesData {
  100. if allRulesData[ruleData.ID] == nil {
  101. allRulesData[ruleData.ID] = make(map[string]rc.ASMDataRuleData)
  102. }
  103. if data, ok := allRulesData[ruleData.ID][ruleData.Type]; ok {
  104. // Merge rules data entries with the same ID and Type
  105. data.Data = mergeRulesDataEntries(data.Data, ruleData.Data)
  106. allRulesData[ruleData.ID][ruleData.Type] = data
  107. } else {
  108. allRulesData[ruleData.ID][ruleData.Type] = ruleData
  109. }
  110. }
  111. }
  112. // Aggregate all the rules data before passing it over to the WAF
  113. var rulesData []rc.ASMDataRuleData
  114. for _, m := range allRulesData {
  115. for _, data := range m {
  116. rulesData = append(rulesData, data)
  117. }
  118. }
  119. if err := h.handle.UpdateRulesData(rulesData); err != nil {
  120. log.Debug("appsec: Remote config: could not update WAF rule data: %v.", err)
  121. statuses = statusesFromUpdate(u, false, err)
  122. }
  123. return statuses
  124. }
  125. // mergeRulesDataEntries merges two slices of rules data entries together, removing duplicates and
  126. // only keeping the longest expiration values for similar entries.
  127. func mergeRulesDataEntries(entries1, entries2 []rc.ASMDataRuleDataEntry) []rc.ASMDataRuleDataEntry {
  128. mergeMap := map[string]int64{}
  129. for _, entry := range entries1 {
  130. mergeMap[entry.Value] = entry.Expiration
  131. }
  132. // Replace the entry only if the new expiration timestamp goes later than the current one
  133. // If no expiration timestamp was provided (default to 0), then the data doesn't expire
  134. for _, entry := range entries2 {
  135. if exp, ok := mergeMap[entry.Value]; !ok || entry.Expiration == 0 || entry.Expiration > exp {
  136. mergeMap[entry.Value] = entry.Expiration
  137. }
  138. }
  139. // Create the final slice and return it
  140. entries := make([]rc.ASMDataRuleDataEntry, 0, len(mergeMap))
  141. for val, exp := range mergeMap {
  142. entries = append(entries, rc.ASMDataRuleDataEntry{
  143. Value: val,
  144. Expiration: exp,
  145. })
  146. }
  147. return entries
  148. }
  149. func (a *appsec) startRC() {
  150. if a.rc != nil {
  151. a.rc.Start()
  152. }
  153. }
  154. func (a *appsec) stopRC() {
  155. if a.rc != nil {
  156. a.rc.Stop()
  157. }
  158. }
  159. func (a *appsec) registerRCProduct(product string) error {
  160. if a.rc == nil {
  161. return fmt.Errorf("no valid remote configuration client")
  162. }
  163. // Don't do anything if the product is already registered
  164. for _, p := range a.rc.Products {
  165. if p == product {
  166. return nil
  167. }
  168. }
  169. a.rc.Products = append(a.rc.Products, product)
  170. return nil
  171. }
  172. func (a *appsec) registerRCCapability(c remoteconfig.Capability) error {
  173. if a.rc == nil {
  174. return fmt.Errorf("no valid remote configuration client")
  175. }
  176. // Don't do anything if the capability is already registered
  177. for _, cap := range a.rc.Capabilities {
  178. if cap == c {
  179. return nil
  180. }
  181. }
  182. a.rc.Capabilities = append(a.rc.Capabilities, c)
  183. return nil
  184. }
  185. func (a *appsec) registerRCCallback(c remoteconfig.Callback, product string) error {
  186. if a.rc == nil {
  187. return fmt.Errorf("no valid remote configuration client")
  188. }
  189. a.rc.RegisterCallback(c, product)
  190. return nil
  191. }
  192. func (a *appsec) enableRemoteActivation() error {
  193. if a.rc == nil {
  194. return fmt.Errorf("no valid remote configuration client")
  195. }
  196. // First verify that the WAF is in good health. We perform this check in order not to falsely "allow" users to
  197. // activate ASM through remote config if activation would fail when trying to register a WAF handle
  198. // (ex: if the service runs on an unsupported platform).
  199. if err := waf.Health(); err != nil {
  200. log.Debug("appsec: WAF health check failed, remote activation will be disabled: %v", err)
  201. return err
  202. }
  203. a.registerRCProduct(rc.ProductASMFeatures)
  204. a.registerRCCapability(remoteconfig.ASMActivation)
  205. a.registerRCCallback(a.asmFeaturesCallback, rc.ProductASMFeatures)
  206. return nil
  207. }
  208. func (a *appsec) enableRCBlocking(handle wafHandleWrapper) error {
  209. if a.rc == nil {
  210. return fmt.Errorf("no valid remote configuration client")
  211. }
  212. a.registerRCProduct(rc.ProductASMData)
  213. a.registerRCCapability(remoteconfig.ASMIPBlocking)
  214. a.registerRCCallback(handle.asmDataCallback, rc.ProductASMData)
  215. return nil
  216. }