| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- // Unless explicitly stated otherwise all files in this repository are licensed
- // under the Apache License Version 2.0.
- // This product includes software developed at Datadog (https://www.datadoghq.com/).
- // Copyright 2022-present Datadog, Inc.
- package state
- import (
- "encoding/json"
- "errors"
- "fmt"
- "github.com/DataDog/go-tuf/data"
- )
- /*
- To add support for a new product:
- 1. Add the definition of the product to the const() block of products and the `allProducts` list.
- 2. Define the serialized configuration struct as well as a function to parse the config from a []byte.
- 3. Add the product to the `parseConfig` function
- 4. Add a method on the `Repository` to retrieved typed configs for the product.
- */
- var allProducts = []string{ProductAPMSampling, ProductCWSDD, ProductASMFeatures, ProductASMDD, ProductASMData}
- const (
- // ProductAPMSampling is the apm sampling product
- ProductAPMSampling = "APM_SAMPLING"
- // ProductCWSDD is the cloud workload security product managed by datadog employees
- ProductCWSDD = "CWS_DD"
- // ProductASMFeatures is the ASM product used form ASM activation through remote config
- ProductASMFeatures = "ASM_FEATURES"
- // ProductASMDD is the application security monitoring product managed by datadog employees
- ProductASMDD = "ASM_DD"
- // ProductASMData is the ASM product used to configure WAF rules data
- ProductASMData = "ASM_DATA"
- )
- // ErrNoConfigVersion occurs when a target file's custom meta is missing the config version
- var ErrNoConfigVersion = errors.New("version missing in custom file meta")
- func parseConfig(product string, raw []byte, metadata Metadata) (interface{}, error) {
- var c interface{}
- var err error
- switch product {
- case ProductAPMSampling:
- c, err = parseConfigAPMSampling(raw, metadata)
- case ProductASMFeatures:
- c, err = parseASMFeaturesConfig(raw, metadata)
- case ProductCWSDD:
- c, err = parseConfigCWSDD(raw, metadata)
- case ProductASMDD:
- c, err = parseConfigASMDD(raw, metadata)
- case ProductASMData:
- c, err = parseConfigASMData(raw, metadata)
- default:
- return nil, fmt.Errorf("unknown product - %s", product)
- }
- return c, err
- }
- // APMSamplingConfig is a deserialized APM Sampling configuration file
- // along with its associated remote config metadata.
- type APMSamplingConfig struct {
- Config []byte
- Metadata Metadata
- }
- func parseConfigAPMSampling(data []byte, metadata Metadata) (APMSamplingConfig, error) {
- // We actually don't parse the payload here, we delegate this responsibility to the trace agent
- return APMSamplingConfig{
- Config: data,
- Metadata: metadata,
- }, nil
- }
- // APMConfigs returns the currently active APM configs
- func (r *Repository) APMConfigs() map[string]APMSamplingConfig {
- typedConfigs := make(map[string]APMSamplingConfig)
- configs := r.getConfigs(ProductAPMSampling)
- for path, conf := range configs {
- // We control this, so if this has gone wrong something has gone horribly wrong
- typed, ok := conf.(APMSamplingConfig)
- if !ok {
- panic("unexpected config stored as APMSamplingConfig")
- }
- typedConfigs[path] = typed
- }
- return typedConfigs
- }
- // ConfigCWSDD is a deserialized CWS DD configuration file along with its
- // associated remote config metadata
- type ConfigCWSDD struct {
- Config []byte
- Metadata Metadata
- }
- func parseConfigCWSDD(data []byte, metadata Metadata) (ConfigCWSDD, error) {
- return ConfigCWSDD{
- Config: data,
- Metadata: metadata,
- }, nil
- }
- // CWSDDConfigs returns the currently active CWSDD config files
- func (r *Repository) CWSDDConfigs() map[string]ConfigCWSDD {
- typedConfigs := make(map[string]ConfigCWSDD)
- configs := r.getConfigs(ProductCWSDD)
- for path, conf := range configs {
- // We control this, so if this has gone wrong something has gone horribly wrong
- typed, ok := conf.(ConfigCWSDD)
- if !ok {
- panic("unexpected config stored as CWSDD Config")
- }
- typedConfigs[path] = typed
- }
- return typedConfigs
- }
- // ConfigASMDD is a deserialized ASM DD configuration file along with its
- // associated remote config metadata
- type ConfigASMDD struct {
- Config []byte
- Metadata Metadata
- }
- func parseConfigASMDD(data []byte, metadata Metadata) (ConfigASMDD, error) {
- return ConfigASMDD{
- Config: data,
- Metadata: metadata,
- }, nil
- }
- // ASMDDConfigs returns the currently active ASMDD configs
- func (r *Repository) ASMDDConfigs() map[string]ConfigASMDD {
- typedConfigs := make(map[string]ConfigASMDD)
- configs := r.getConfigs(ProductASMDD)
- for path, conf := range configs {
- // We control this, so if this has gone wrong something has gone horribly wrong
- typed, ok := conf.(ConfigASMDD)
- if !ok {
- panic("unexpected config stored as ASMDD Config")
- }
- typedConfigs[path] = typed
- }
- return typedConfigs
- }
- // ASMFeaturesConfig is a deserialized configuration file that indicates whether ASM should be enabled
- // within a tracer, along with its associated remote config metadata.
- type ASMFeaturesConfig struct {
- Config ASMFeaturesData
- Metadata Metadata
- }
- // ASMFeaturesData describes the enabled state of ASM features
- type ASMFeaturesData struct {
- ASM struct {
- Enabled bool `json:"enabled"`
- } `json:"asm"`
- }
- func parseASMFeaturesConfig(data []byte, metadata Metadata) (ASMFeaturesConfig, error) {
- var f ASMFeaturesData
- err := json.Unmarshal(data, &f)
- if err != nil {
- return ASMFeaturesConfig{}, nil
- }
- return ASMFeaturesConfig{
- Config: f,
- Metadata: metadata,
- }, nil
- }
- // ASMFeaturesConfigs returns the currently active ASMFeatures configs
- func (r *Repository) ASMFeaturesConfigs() map[string]ASMFeaturesConfig {
- typedConfigs := make(map[string]ASMFeaturesConfig)
- configs := r.getConfigs(ProductASMFeatures)
- for path, conf := range configs {
- // We control this, so if this has gone wrong something has gone horribly wrong
- typed, ok := conf.(ASMFeaturesConfig)
- if !ok {
- panic("unexpected config stored as ASMFeaturesConfig")
- }
- typedConfigs[path] = typed
- }
- return typedConfigs
- }
- // ApplyState represents the status of a configuration application by a remote configuration client
- // Clients need to either ack the correct application of received configurations, or communicate that
- // they haven't applied it yet, or communicate any error that may have happened while doing so
- type ApplyState uint64
- const (
- ApplyStateUnknown ApplyState = iota
- ApplyStateUnacknowledged
- ApplyStateAcknowledged
- ApplyStateError
- )
- // ApplyStatus is the processing status for a given configuration.
- // It basically represents whether a config was successfully processed and apply, or if an error occurred
- type ApplyStatus struct {
- State ApplyState
- Error string
- }
- // ASMDataConfig is a deserialized configuration file that holds rules data that can be used
- // by the ASM WAF for specific features (example: ip blocking).
- type ASMDataConfig struct {
- Config ASMDataRulesData
- Metadata Metadata
- }
- // ASMDataRulesData is a serializable array of rules data entries
- type ASMDataRulesData struct {
- RulesData []ASMDataRuleData `json:"rules_data"`
- }
- // ASMDataRuleData is an entry in the rules data list held by an ASMData configuration
- type ASMDataRuleData struct {
- ID string `json:"id"`
- Type string `json:"type"`
- Data []ASMDataRuleDataEntry `json:"data"`
- }
- // ASMDataRuleDataEntry represents a data entry in a rule data file
- type ASMDataRuleDataEntry struct {
- Expiration int64 `json:"expiration,omitempty"`
- Value string `json:"value"`
- }
- func parseConfigASMData(data []byte, metadata Metadata) (ASMDataConfig, error) {
- cfg := ASMDataConfig{
- Metadata: metadata,
- }
- err := json.Unmarshal(data, &cfg.Config)
- return cfg, err
- }
- // ASMDataConfigs returns the currently active ASMData configs
- func (r *Repository) ASMDataConfigs() map[string]ASMDataConfig {
- typedConfigs := make(map[string]ASMDataConfig)
- configs := r.getConfigs(ProductASMData)
- for path, cfg := range configs {
- // We control this, so if this has gone wrong something has gone horribly wrong
- typed, ok := cfg.(ASMDataConfig)
- if !ok {
- panic("unexpected config stored as ASMDataConfig")
- }
- typedConfigs[path] = typed
- }
- return typedConfigs
- }
- // Metadata stores remote config metadata for a given configuration
- type Metadata struct {
- Product string
- ID string
- Name string
- Version uint64
- RawLength uint64
- Hashes map[string][]byte
- ApplyStatus ApplyStatus
- }
- func newConfigMetadata(parsedPath configPath, tfm data.TargetFileMeta) (Metadata, error) {
- var m Metadata
- m.ID = parsedPath.ConfigID
- m.Product = parsedPath.Product
- m.Name = parsedPath.Name
- m.RawLength = uint64(tfm.Length)
- m.Hashes = make(map[string][]byte)
- for k, v := range tfm.Hashes {
- m.Hashes[k] = []byte(v)
- }
- v, err := fileMetaVersion(tfm)
- if err != nil {
- return Metadata{}, err
- }
- m.Version = v
- return m, nil
- }
- type fileMetaCustom struct {
- Version *uint64 `json:"v"`
- }
- func fileMetaVersion(fm data.TargetFileMeta) (uint64, error) {
- if fm.Custom == nil {
- return 0, ErrNoConfigVersion
- }
- fmc, err := parseFileMetaCustom(*fm.Custom)
- if err != nil {
- return 0, err
- }
- return *fmc.Version, nil
- }
- func parseFileMetaCustom(rawCustom []byte) (fileMetaCustom, error) {
- var custom fileMetaCustom
- err := json.Unmarshal(rawCustom, &custom)
- if err != nil {
- return fileMetaCustom{}, err
- }
- if custom.Version == nil {
- return fileMetaCustom{}, ErrNoConfigVersion
- }
- return custom, nil
- }
|