configs.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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-present Datadog, Inc.
  5. package state
  6. import (
  7. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "github.com/DataDog/go-tuf/data"
  11. )
  12. /*
  13. To add support for a new product:
  14. 1. Add the definition of the product to the const() block of products and the `allProducts` list.
  15. 2. Define the serialized configuration struct as well as a function to parse the config from a []byte.
  16. 3. Add the product to the `parseConfig` function
  17. 4. Add a method on the `Repository` to retrieved typed configs for the product.
  18. */
  19. var allProducts = []string{ProductAPMSampling, ProductCWSDD, ProductASMFeatures, ProductASMDD, ProductASMData}
  20. const (
  21. // ProductAPMSampling is the apm sampling product
  22. ProductAPMSampling = "APM_SAMPLING"
  23. // ProductCWSDD is the cloud workload security product managed by datadog employees
  24. ProductCWSDD = "CWS_DD"
  25. // ProductASMFeatures is the ASM product used form ASM activation through remote config
  26. ProductASMFeatures = "ASM_FEATURES"
  27. // ProductASMDD is the application security monitoring product managed by datadog employees
  28. ProductASMDD = "ASM_DD"
  29. // ProductASMData is the ASM product used to configure WAF rules data
  30. ProductASMData = "ASM_DATA"
  31. )
  32. // ErrNoConfigVersion occurs when a target file's custom meta is missing the config version
  33. var ErrNoConfigVersion = errors.New("version missing in custom file meta")
  34. func parseConfig(product string, raw []byte, metadata Metadata) (interface{}, error) {
  35. var c interface{}
  36. var err error
  37. switch product {
  38. case ProductAPMSampling:
  39. c, err = parseConfigAPMSampling(raw, metadata)
  40. case ProductASMFeatures:
  41. c, err = parseASMFeaturesConfig(raw, metadata)
  42. case ProductCWSDD:
  43. c, err = parseConfigCWSDD(raw, metadata)
  44. case ProductASMDD:
  45. c, err = parseConfigASMDD(raw, metadata)
  46. case ProductASMData:
  47. c, err = parseConfigASMData(raw, metadata)
  48. default:
  49. return nil, fmt.Errorf("unknown product - %s", product)
  50. }
  51. return c, err
  52. }
  53. // APMSamplingConfig is a deserialized APM Sampling configuration file
  54. // along with its associated remote config metadata.
  55. type APMSamplingConfig struct {
  56. Config []byte
  57. Metadata Metadata
  58. }
  59. func parseConfigAPMSampling(data []byte, metadata Metadata) (APMSamplingConfig, error) {
  60. // We actually don't parse the payload here, we delegate this responsibility to the trace agent
  61. return APMSamplingConfig{
  62. Config: data,
  63. Metadata: metadata,
  64. }, nil
  65. }
  66. // APMConfigs returns the currently active APM configs
  67. func (r *Repository) APMConfigs() map[string]APMSamplingConfig {
  68. typedConfigs := make(map[string]APMSamplingConfig)
  69. configs := r.getConfigs(ProductAPMSampling)
  70. for path, conf := range configs {
  71. // We control this, so if this has gone wrong something has gone horribly wrong
  72. typed, ok := conf.(APMSamplingConfig)
  73. if !ok {
  74. panic("unexpected config stored as APMSamplingConfig")
  75. }
  76. typedConfigs[path] = typed
  77. }
  78. return typedConfigs
  79. }
  80. // ConfigCWSDD is a deserialized CWS DD configuration file along with its
  81. // associated remote config metadata
  82. type ConfigCWSDD struct {
  83. Config []byte
  84. Metadata Metadata
  85. }
  86. func parseConfigCWSDD(data []byte, metadata Metadata) (ConfigCWSDD, error) {
  87. return ConfigCWSDD{
  88. Config: data,
  89. Metadata: metadata,
  90. }, nil
  91. }
  92. // CWSDDConfigs returns the currently active CWSDD config files
  93. func (r *Repository) CWSDDConfigs() map[string]ConfigCWSDD {
  94. typedConfigs := make(map[string]ConfigCWSDD)
  95. configs := r.getConfigs(ProductCWSDD)
  96. for path, conf := range configs {
  97. // We control this, so if this has gone wrong something has gone horribly wrong
  98. typed, ok := conf.(ConfigCWSDD)
  99. if !ok {
  100. panic("unexpected config stored as CWSDD Config")
  101. }
  102. typedConfigs[path] = typed
  103. }
  104. return typedConfigs
  105. }
  106. // ConfigASMDD is a deserialized ASM DD configuration file along with its
  107. // associated remote config metadata
  108. type ConfigASMDD struct {
  109. Config []byte
  110. Metadata Metadata
  111. }
  112. func parseConfigASMDD(data []byte, metadata Metadata) (ConfigASMDD, error) {
  113. return ConfigASMDD{
  114. Config: data,
  115. Metadata: metadata,
  116. }, nil
  117. }
  118. // ASMDDConfigs returns the currently active ASMDD configs
  119. func (r *Repository) ASMDDConfigs() map[string]ConfigASMDD {
  120. typedConfigs := make(map[string]ConfigASMDD)
  121. configs := r.getConfigs(ProductASMDD)
  122. for path, conf := range configs {
  123. // We control this, so if this has gone wrong something has gone horribly wrong
  124. typed, ok := conf.(ConfigASMDD)
  125. if !ok {
  126. panic("unexpected config stored as ASMDD Config")
  127. }
  128. typedConfigs[path] = typed
  129. }
  130. return typedConfigs
  131. }
  132. // ASMFeaturesConfig is a deserialized configuration file that indicates whether ASM should be enabled
  133. // within a tracer, along with its associated remote config metadata.
  134. type ASMFeaturesConfig struct {
  135. Config ASMFeaturesData
  136. Metadata Metadata
  137. }
  138. // ASMFeaturesData describes the enabled state of ASM features
  139. type ASMFeaturesData struct {
  140. ASM struct {
  141. Enabled bool `json:"enabled"`
  142. } `json:"asm"`
  143. }
  144. func parseASMFeaturesConfig(data []byte, metadata Metadata) (ASMFeaturesConfig, error) {
  145. var f ASMFeaturesData
  146. err := json.Unmarshal(data, &f)
  147. if err != nil {
  148. return ASMFeaturesConfig{}, nil
  149. }
  150. return ASMFeaturesConfig{
  151. Config: f,
  152. Metadata: metadata,
  153. }, nil
  154. }
  155. // ASMFeaturesConfigs returns the currently active ASMFeatures configs
  156. func (r *Repository) ASMFeaturesConfigs() map[string]ASMFeaturesConfig {
  157. typedConfigs := make(map[string]ASMFeaturesConfig)
  158. configs := r.getConfigs(ProductASMFeatures)
  159. for path, conf := range configs {
  160. // We control this, so if this has gone wrong something has gone horribly wrong
  161. typed, ok := conf.(ASMFeaturesConfig)
  162. if !ok {
  163. panic("unexpected config stored as ASMFeaturesConfig")
  164. }
  165. typedConfigs[path] = typed
  166. }
  167. return typedConfigs
  168. }
  169. // ApplyState represents the status of a configuration application by a remote configuration client
  170. // Clients need to either ack the correct application of received configurations, or communicate that
  171. // they haven't applied it yet, or communicate any error that may have happened while doing so
  172. type ApplyState uint64
  173. const (
  174. ApplyStateUnknown ApplyState = iota
  175. ApplyStateUnacknowledged
  176. ApplyStateAcknowledged
  177. ApplyStateError
  178. )
  179. // ApplyStatus is the processing status for a given configuration.
  180. // It basically represents whether a config was successfully processed and apply, or if an error occurred
  181. type ApplyStatus struct {
  182. State ApplyState
  183. Error string
  184. }
  185. // ASMDataConfig is a deserialized configuration file that holds rules data that can be used
  186. // by the ASM WAF for specific features (example: ip blocking).
  187. type ASMDataConfig struct {
  188. Config ASMDataRulesData
  189. Metadata Metadata
  190. }
  191. // ASMDataRulesData is a serializable array of rules data entries
  192. type ASMDataRulesData struct {
  193. RulesData []ASMDataRuleData `json:"rules_data"`
  194. }
  195. // ASMDataRuleData is an entry in the rules data list held by an ASMData configuration
  196. type ASMDataRuleData struct {
  197. ID string `json:"id"`
  198. Type string `json:"type"`
  199. Data []ASMDataRuleDataEntry `json:"data"`
  200. }
  201. // ASMDataRuleDataEntry represents a data entry in a rule data file
  202. type ASMDataRuleDataEntry struct {
  203. Expiration int64 `json:"expiration,omitempty"`
  204. Value string `json:"value"`
  205. }
  206. func parseConfigASMData(data []byte, metadata Metadata) (ASMDataConfig, error) {
  207. cfg := ASMDataConfig{
  208. Metadata: metadata,
  209. }
  210. err := json.Unmarshal(data, &cfg.Config)
  211. return cfg, err
  212. }
  213. // ASMDataConfigs returns the currently active ASMData configs
  214. func (r *Repository) ASMDataConfigs() map[string]ASMDataConfig {
  215. typedConfigs := make(map[string]ASMDataConfig)
  216. configs := r.getConfigs(ProductASMData)
  217. for path, cfg := range configs {
  218. // We control this, so if this has gone wrong something has gone horribly wrong
  219. typed, ok := cfg.(ASMDataConfig)
  220. if !ok {
  221. panic("unexpected config stored as ASMDataConfig")
  222. }
  223. typedConfigs[path] = typed
  224. }
  225. return typedConfigs
  226. }
  227. // Metadata stores remote config metadata for a given configuration
  228. type Metadata struct {
  229. Product string
  230. ID string
  231. Name string
  232. Version uint64
  233. RawLength uint64
  234. Hashes map[string][]byte
  235. ApplyStatus ApplyStatus
  236. }
  237. func newConfigMetadata(parsedPath configPath, tfm data.TargetFileMeta) (Metadata, error) {
  238. var m Metadata
  239. m.ID = parsedPath.ConfigID
  240. m.Product = parsedPath.Product
  241. m.Name = parsedPath.Name
  242. m.RawLength = uint64(tfm.Length)
  243. m.Hashes = make(map[string][]byte)
  244. for k, v := range tfm.Hashes {
  245. m.Hashes[k] = []byte(v)
  246. }
  247. v, err := fileMetaVersion(tfm)
  248. if err != nil {
  249. return Metadata{}, err
  250. }
  251. m.Version = v
  252. return m, nil
  253. }
  254. type fileMetaCustom struct {
  255. Version *uint64 `json:"v"`
  256. }
  257. func fileMetaVersion(fm data.TargetFileMeta) (uint64, error) {
  258. if fm.Custom == nil {
  259. return 0, ErrNoConfigVersion
  260. }
  261. fmc, err := parseFileMetaCustom(*fm.Custom)
  262. if err != nil {
  263. return 0, err
  264. }
  265. return *fmc.Version, nil
  266. }
  267. func parseFileMetaCustom(rawCustom []byte) (fileMetaCustom, error) {
  268. var custom fileMetaCustom
  269. err := json.Unmarshal(rawCustom, &custom)
  270. if err != nil {
  271. return fileMetaCustom{}, err
  272. }
  273. if custom.Version == nil {
  274. return fileMetaCustom{}, ErrNoConfigVersion
  275. }
  276. return custom, nil
  277. }