diff --git a/go.mod b/go.mod index 8da101e581..d4ea624382 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/99designs/gqlgen v0.17.36 github.com/DataDog/appsec-internal-go v1.7.0 github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 - github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 + github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0 github.com/DataDog/datadog-go/v5 v5.3.0 github.com/DataDog/go-libddwaf/v3 v3.4.0 github.com/DataDog/gostackparse v0.7.0 @@ -96,7 +96,7 @@ require ( go.uber.org/atomic v1.11.0 golang.org/x/mod v0.18.0 golang.org/x/oauth2 v0.9.0 - golang.org/x/sys v0.21.0 + golang.org/x/sys v0.23.0 golang.org/x/time v0.3.0 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/api v0.128.0 @@ -119,7 +119,7 @@ require ( cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.2 // indirect - github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect + github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect @@ -256,11 +256,11 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/metric v1.20.0 // indirect golang.org/x/arch v0.4.0 // indirect - golang.org/x/crypto v0.24.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/term v0.21.0 // indirect + golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 0d6bcaf22c..427525a587 100644 --- a/go.sum +++ b/go.sum @@ -629,15 +629,15 @@ github.com/DataDog/appsec-internal-go v1.7.0 h1:iKRNLih83dJeVya3IoUfK+6HLD/hQsIb github.com/DataDog/appsec-internal-go v1.7.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g= github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8= github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3xzMthNFhXfOaXlrsdgqmJ73lndFf8c= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0 h1:LplNAmMgZvGU7kKA0+4c1xWOjz828xweW5TCi8Mw9Q0= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0/go.mod h1:4Vo3SJ24uzfKHUHLoFa8t8o+LH+7TCQ7sPcZDtOpSP4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8= github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q= github.com/DataDog/go-libddwaf/v3 v3.4.0 h1:NJ2W2vhYaOm1OWr1LJCbdgp7ezG/XLJcQKBmjFwhSuM= github.com/DataDog/go-libddwaf/v3 v3.4.0/go.mod h1:n98d9nZ1gzenRSk53wz8l6d34ikxS+hs62A31Fqmyi4= -github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I= -github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= +github.com/DataDog/go-tuf v1.1.0-0.5.2 h1:4CagiIekonLSfL8GMHRHcHudo1fQnxELS9g4tiAupQ4= +github.com/DataDog/go-tuf v1.1.0-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= github.com/DataDog/sketches-go v1.4.5 h1:ki7VfeNz7IcNafq7yI/j5U/YCkO3LJiMDtXz9OMQbyE= @@ -2222,8 +2222,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2565,8 +2565,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2581,8 +2581,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/apps/go.mod b/internal/apps/go.mod index e1c301da0b..72b14e8123 100644 --- a/internal/apps/go.mod +++ b/internal/apps/go.mod @@ -30,9 +30,9 @@ require ( require ( github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect - github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect + github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0 // indirect github.com/DataDog/datadog-go/v5 v5.3.0 // indirect - github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect + github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect github.com/DataDog/gostackparse v0.7.0 // indirect github.com/DataDog/sketches-go v1.4.5 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect @@ -47,7 +47,7 @@ require ( github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/stretchr/testify v1.9.0 github.com/tinylib/msgp v1.2.1 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.23.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/internal/apps/go.sum b/internal/apps/go.sum index 0d2214610c..b5c314d6e7 100644 --- a/internal/apps/go.sum +++ b/internal/apps/go.sum @@ -2,14 +2,14 @@ github.com/DataDog/appsec-internal-go v1.7.0 h1:iKRNLih83dJeVya3IoUfK+6HLD/hQsIb github.com/DataDog/appsec-internal-go v1.7.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g= github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8= github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3xzMthNFhXfOaXlrsdgqmJ73lndFf8c= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0 h1:LplNAmMgZvGU7kKA0+4c1xWOjz828xweW5TCi8Mw9Q0= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0/go.mod h1:4Vo3SJ24uzfKHUHLoFa8t8o+LH+7TCQ7sPcZDtOpSP4= github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8= github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q= github.com/DataDog/go-libddwaf/v3 v3.4.0 h1:NJ2W2vhYaOm1OWr1LJCbdgp7ezG/XLJcQKBmjFwhSuM= github.com/DataDog/go-libddwaf/v3 v3.4.0/go.mod h1:n98d9nZ1gzenRSk53wz8l6d34ikxS+hs62A31Fqmyi4= -github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I= -github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= +github.com/DataDog/go-tuf v1.1.0-0.5.2 h1:4CagiIekonLSfL8GMHRHcHudo1fQnxELS9g4tiAupQ4= +github.com/DataDog/go-tuf v1.1.0-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= github.com/DataDog/sketches-go v1.4.5 h1:ki7VfeNz7IcNafq7yI/j5U/YCkO3LJiMDtXz9OMQbyE= @@ -128,8 +128,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -149,8 +149,8 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/internal/appsec/appsec.go b/internal/appsec/appsec.go index ae68d0d668..44d12ebec9 100644 --- a/internal/appsec/appsec.go +++ b/internal/appsec/appsec.go @@ -199,6 +199,11 @@ func (a *appsec) stop() { a.wafHandle.Close() a.wafHandle = nil } + + // Reset rules edits received from the remote configuration + // We skip the error because we can't do anything about and it was already logged in config.NewRulesManager + a.cfg.RulesManager, _ = config.NewRulesManager(nil) + // TODO: block until no more requests are using dyngo operations a.limiter.Stop() diff --git a/internal/appsec/config/config.go b/internal/appsec/config/config.go index e2a0b7736a..e2b8cef8cd 100644 --- a/internal/appsec/config/config.go +++ b/internal/appsec/config/config.go @@ -105,7 +105,7 @@ func NewConfig() (*Config, error) { return nil, err } - r, err := NewRulesManeger(rules) + r, err := NewRulesManager(rules) if err != nil { return nil, err } diff --git a/internal/appsec/config/rules_manager.go b/internal/appsec/config/rules_manager.go index f2eedb8cf9..e4e003eda1 100644 --- a/internal/appsec/config/rules_manager.go +++ b/internal/appsec/config/rules_manager.go @@ -29,24 +29,21 @@ type ( } // RulesFragment can represent a full ruleset or a fragment of it. RulesFragment struct { - Version string `json:"version,omitempty"` - Metadata any `json:"metadata,omitempty"` - Rules []any `json:"rules,omitempty"` - Overrides []any `json:"rules_override,omitempty"` - Exclusions []any `json:"exclusions,omitempty"` - RulesData []RuleDataEntry `json:"rules_data,omitempty"` - Actions []any `json:"actions,omitempty"` - CustomRules []any `json:"custom_rules,omitempty"` - Processors []any `json:"processors,omitempty"` - Scanners []any `json:"scanners,omitempty"` + Version string `json:"version,omitempty"` + Metadata any `json:"metadata,omitempty"` + Rules []any `json:"rules,omitempty"` + Overrides []any `json:"rules_override,omitempty"` + Exclusions []any `json:"exclusions,omitempty"` + ExclusionData []DataEntry `json:"exclusion_data,omitempty"` + RulesData []DataEntry `json:"rules_data,omitempty"` + Actions []any `json:"actions,omitempty"` + CustomRules []any `json:"custom_rules,omitempty"` + Processors []any `json:"processors,omitempty"` + Scanners []any `json:"scanners,omitempty"` } - // RuleDataEntry represents an entry in the "rules_data" top level field of a rules file - RuleDataEntry rc.ASMDataRuleData - // RulesData is a slice of RulesDataEntry - RulesData struct { - RulesData []RuleDataEntry `json:"rules_data"` - } + // DataEntry represents an entry in the "rules_data" top level field of a rules file + DataEntry rc.ASMDataRuleData ) // DefaultRulesFragment returns a RulesFragment created using the default static recommended rules @@ -63,18 +60,18 @@ func (f *RulesFragment) clone() (clone RulesFragment) { clone.Metadata = f.Metadata clone.Overrides = slices.Clone(f.Overrides) clone.Exclusions = slices.Clone(f.Exclusions) + clone.ExclusionData = slices.Clone(f.ExclusionData) clone.RulesData = slices.Clone(f.RulesData) clone.CustomRules = slices.Clone(f.CustomRules) clone.Processors = slices.Clone(f.Processors) clone.Scanners = slices.Clone(f.Scanners) - // TODO (Francois Mazeau): copy more fields once we handle them return } -// NewRulesManeger initializes and returns a new RulesManager using the provided rules. +// NewRulesManager initializes and returns a new RulesManager using the provided rules. // If no rules are provided (nil), the default rules are used instead. // If the provided rules are invalid, an error is returned -func NewRulesManeger(rules []byte) (*RulesManager, error) { +func NewRulesManager(rules []byte) (*RulesManager, error) { var f RulesFragment if rules == nil { f = DefaultRulesFragment() @@ -129,6 +126,7 @@ func (r *RulesManager) Compile() { for _, v := range r.Edits { r.Latest.Overrides = append(r.Latest.Overrides, v.Overrides...) r.Latest.Exclusions = append(r.Latest.Exclusions, v.Exclusions...) + r.Latest.ExclusionData = append(r.Latest.ExclusionData, v.ExclusionData...) r.Latest.Actions = append(r.Latest.Actions, v.Actions...) r.Latest.RulesData = append(r.Latest.RulesData, v.RulesData...) r.Latest.CustomRules = append(r.Latest.CustomRules, v.CustomRules...) diff --git a/internal/appsec/listener/httpsec/http.go b/internal/appsec/listener/httpsec/http.go index c458931336..bb05947504 100644 --- a/internal/appsec/listener/httpsec/http.go +++ b/internal/appsec/listener/httpsec/http.go @@ -114,9 +114,7 @@ func (l *wafEventListener) onEvent(op *types.Operation, args types.HandlerOperat } if SSRFAddressesPresent(l.addresses) { - dyngo.On(op, shared.MakeWAFRunListener(&op.SecurityEventsHolder, wafCtx, l.limiter, func(args types.RoundTripOperationArgs) waf.RunAddressData { - return waf.RunAddressData{Ephemeral: map[string]any{ServerIoNetURLAddr: args.URL}} - })) + RegisterRoundTripperListener(op, &op.SecurityEventsHolder, wafCtx, l.limiter) } if ossec.OSAddressesPresent(l.addresses) { diff --git a/internal/appsec/remoteconfig.go b/internal/appsec/remoteconfig.go index 1258944a0e..8288a76b71 100644 --- a/internal/appsec/remoteconfig.go +++ b/internal/appsec/remoteconfig.go @@ -9,6 +9,7 @@ import ( "encoding/json" "errors" "fmt" + "maps" "os" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/config" @@ -42,20 +43,13 @@ func statusesFromUpdate(u remoteconfig.ProductUpdate, ack bool, err error) map[s return statuses } -func mergeMaps[K comparable, V any](m1 map[K]V, m2 map[K]V) map[K]V { - for key, value := range m2 { - m1[key] = value - } - return m1 -} - // combineRCRulesUpdates updates the state of the given RulesManager with the combination of all the provided rules updates func combineRCRulesUpdates(r *config.RulesManager, updates map[string]remoteconfig.ProductUpdate) (statuses map[string]rc.ApplyStatus, err error) { // Spare some re-allocations (but there may still be some because 1 update may contain N configs) statuses = make(map[string]rc.ApplyStatus, len(updates)) // Set the default statuses for all updates to unacknowledged for _, u := range updates { - statuses = mergeMaps(statuses, statusesFromUpdate(u, false, nil)) + maps.Copy(statuses, statusesFromUpdate(u, false, nil)) } updateLoop: @@ -67,9 +61,9 @@ updateLoop: switch p { case rc.ProductASMData: // Merge all rules data entries together and store them as a RulesManager edit entry - rulesData, status := mergeRulesData(u) - statuses = mergeMaps(statuses, status) - r.AddEdit("asmdata", config.RulesFragment{RulesData: rulesData}) + fragment, status := mergeASMDataUpdates(u) + maps.Copy(statuses, status) + r.AddEdit("asmdata", fragment) case rc.ProductASMDD: var ( removalFound = false @@ -84,7 +78,7 @@ updateLoop: } // Already seen a removal or an update, return an error if err != nil { - statuses = mergeMaps(statuses, statusesFromUpdate(u, true, err)) + maps.Copy(statuses, statusesFromUpdate(u, true, err)) break updateLoop } @@ -104,7 +98,7 @@ updateLoop: if removalFound { log.Debug("appsec: Remote config: ASM_DD config removed. Switching back to default rules") r.ChangeBase(config.DefaultRulesFragment(), "") - statuses = mergeMaps(statuses, statusesFromUpdate(u, true, nil)) + maps.Copy(statuses, statusesFromUpdate(u, true, nil)) } continue } @@ -146,7 +140,7 @@ updateLoop: // Set all statuses to ack if no error occured if err == nil { for _, u := range updates { - statuses = mergeMaps(statuses, statusesFromUpdate(u, true, nil)) + maps.Copy(statuses, statusesFromUpdate(u, true, nil)) } } @@ -241,12 +235,41 @@ func (a *appsec) handleASMFeatures(u remoteconfig.ProductUpdate) map[string]rc.A return statuses } -func mergeRulesData(u remoteconfig.ProductUpdate) ([]config.RuleDataEntry, map[string]rc.ApplyStatus) { +func mergeASMDataUpdates(u remoteconfig.ProductUpdate) (config.RulesFragment, map[string]rc.ApplyStatus) { // Following the RFC, merging should only happen when two rules data with the same ID and same Type are received - // allRulesData[ID][Type] will return the rules data of said id and type, if it exists - allRulesData := make(map[string]map[string]config.RuleDataEntry) + type mapKey struct { + id string + typ string + } + mergedRulesData := make(map[mapKey]config.DataEntry) + mergedExclusionData := make(map[mapKey]config.DataEntry) statuses := statusesFromUpdate(u, true, nil) + mergeUpdateEntry := func(mergeMap map[mapKey]config.DataEntry, data []config.DataEntry) { + for _, ruleData := range data { + key := mapKey{id: ruleData.ID, typ: ruleData.Type} + if data, ok := mergeMap[key]; ok { + // Merge rules data entries with the same ID and Type + mergeMap[key] = config.DataEntry{ + ID: data.ID, + Type: data.Type, + Data: mergeRulesDataEntries(data.Data, ruleData.Data), + } + continue + } + + mergeMap[key] = ruleData + } + } + + mapValues := func(m map[mapKey]config.DataEntry) []config.DataEntry { + values := make([]config.DataEntry, 0, len(m)) + for _, v := range m { + values = append(values, v) + } + return values + } + for path, raw := range u { log.Debug("appsec: Remote config: processing %s", path) @@ -258,36 +281,30 @@ func mergeRulesData(u remoteconfig.ProductUpdate) ([]config.RuleDataEntry, map[s continue } - var rulesData config.RulesData - if err := json.Unmarshal(raw, &rulesData); err != nil { + var asmdataUpdate struct { + RulesData []config.DataEntry `json:"rules_data,omitempty"` + ExclusionData []config.DataEntry `json:"exclusion_data,omitempty"` + } + if err := json.Unmarshal(raw, &asmdataUpdate); err != nil { log.Debug("appsec: Remote config: error while unmarshalling payload for %s: %v. Configuration won't be applied.", path, err) statuses[path] = genApplyStatus(false, err) continue } - // Check each entry against allRulesData to see if merging is necessary - for _, ruleData := range rulesData.RulesData { - if allRulesData[ruleData.ID] == nil { - allRulesData[ruleData.ID] = make(map[string]config.RuleDataEntry) - } - if data, ok := allRulesData[ruleData.ID][ruleData.Type]; ok { - // Merge rules data entries with the same ID and Type - data.Data = mergeRulesDataEntries(data.Data, ruleData.Data) - allRulesData[ruleData.ID][ruleData.Type] = data - } else { - allRulesData[ruleData.ID][ruleData.Type] = ruleData - } - } + mergeUpdateEntry(mergedExclusionData, asmdataUpdate.ExclusionData) + mergeUpdateEntry(mergedRulesData, asmdataUpdate.RulesData) } - // Aggregate all the rules data before passing it over to the WAF - var rulesData []config.RuleDataEntry - for _, m := range allRulesData { - for _, data := range m { - rulesData = append(rulesData, data) - } + var fragment config.RulesFragment + if len(mergedRulesData) > 0 { + fragment.RulesData = mapValues(mergedRulesData) } - return rulesData, statuses + + if len(mergedExclusionData) > 0 { + fragment.ExclusionData = mapValues(mergedExclusionData) + } + + return fragment, statuses } // mergeRulesDataEntries merges two slices of rules data entries together, removing duplicates and @@ -373,6 +390,7 @@ var blockingCapabilities = [...]remoteconfig.Capability{ remoteconfig.ASMCustomRules, remoteconfig.ASMCustomBlockingResponse, remoteconfig.ASMTrustedIPs, + remoteconfig.ASMExclusionData, } func (a *appsec) enableRCBlocking() { diff --git a/internal/appsec/remoteconfig_test.go b/internal/appsec/remoteconfig_test.go index 34ebb224da..6e48f50cc7 100644 --- a/internal/appsec/remoteconfig_test.go +++ b/internal/appsec/remoteconfig_test.go @@ -10,7 +10,7 @@ import ( "errors" "os" "reflect" - "sort" + "slices" "strings" "testing" @@ -119,63 +119,63 @@ func TestMergeRulesData(t *testing.T) { for _, tc := range []struct { name string update remoteconfig.ProductUpdate - expected []config.RuleDataEntry + expected config.RulesFragment statuses map[string]rc.ApplyStatus }{ { name: "empty-rule-data", update: map[string][]byte{}, - statuses: map[string]rc.ApplyStatus{"some/path": {State: rc.ApplyStateAcknowledged}}, + statuses: map[string]rc.ApplyStatus{}, }, { name: "bad-json", update: map[string][]byte{ "some/path": []byte(`[}]`), }, - statuses: map[string]rc.ApplyStatus{"some/path": {State: rc.ApplyStateError}}, + statuses: map[string]rc.ApplyStatus{"some/path": {State: rc.ApplyStateError, Error: "invalid character '}' looking for beginning of value"}}, }, { - name: "single-value", + name: "single-rules-value", update: map[string][]byte{ "some/path": []byte(`{"rules_data":[{"id":"test","type":"data_with_expiration","data":[{"expiration":3494138481,"value":"user1"}]}]}`), }, - expected: []config.RuleDataEntry{{ID: "test", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ + expected: config.RulesFragment{RulesData: []config.DataEntry{{ID: "test", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ {Expiration: 3494138481, Value: "user1"}, - }}}, + }}}}, statuses: map[string]rc.ApplyStatus{"some/path": {State: rc.ApplyStateAcknowledged}}, }, { - name: "multiple-values", + name: "multiple-rules-values", update: map[string][]byte{ "some/path": []byte(`{"rules_data":[{"id":"test","type":"data_with_expiration","data":[{"expiration":3494138481,"value":"user1"},{"expiration":3494138441,"value":"user2"}]}]}`), }, - expected: []config.RuleDataEntry{{ID: "test", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ + expected: config.RulesFragment{RulesData: []config.DataEntry{{ID: "test", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ {Expiration: 3494138481, Value: "user1"}, {Expiration: 3494138441, Value: "user2"}, - }}}, + }}}}, statuses: map[string]rc.ApplyStatus{"some/path": {State: rc.ApplyStateAcknowledged}}, }, { - name: "multiple-entries", + name: "multiple-rules-entries", update: map[string][]byte{ "some/path": []byte(`{"rules_data":[{"id":"test1","type":"data_with_expiration","data":[{"expiration":3494138444,"value":"user3"}]},{"id":"test2","type":"data_with_expiration","data":[{"expiration":3495138481,"value":"user4"}]}]}`), }, - expected: []config.RuleDataEntry{ + expected: config.RulesFragment{RulesData: []config.DataEntry{ {ID: "test1", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ {Expiration: 3494138444, Value: "user3"}, }}, {ID: "test2", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ {Expiration: 3495138481, Value: "user4"}, }}, - }, + }}, statuses: map[string]rc.ApplyStatus{"some/path": {State: rc.ApplyStateAcknowledged}}, }, { - name: "merging-entries", + name: "merging-rules-entries", update: map[string][]byte{ "some/path/1": []byte(`{"rules_data":[{"id":"test1","type":"data_with_expiration","data":[{"expiration":3494138444,"value":"user3"}]},{"id":"test2","type":"data_with_expiration","data":[{"expiration":3495138481,"value":"user4"}]}]}`), "some/path/2": []byte(`{"rules_data":[{"id":"test1","type":"data_with_expiration","data":[{"expiration":3494138445,"value":"user3"}]},{"id":"test2","type":"data_with_expiration","data":[{"expiration":0,"value":"user5"}]}]}`), }, - expected: []config.RuleDataEntry{ + expected: config.RulesFragment{RulesData: []config.DataEntry{ {ID: "test1", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ {Expiration: 3494138445, Value: "user3"}, }}, @@ -183,7 +183,62 @@ func TestMergeRulesData(t *testing.T) { {Expiration: 3495138481, Value: "user4"}, {Expiration: 0, Value: "user5"}, }}, + }}, + statuses: map[string]rc.ApplyStatus{ + "some/path/1": {State: rc.ApplyStateAcknowledged}, + "some/path/2": {State: rc.ApplyStateAcknowledged}, + }, + }, + { + name: "single-exclusions-value", + update: map[string][]byte{ + "some/path": []byte(`{"exclusion_data":[{"id":"test","type":"data_with_expiration","data":[{"expiration":3494138481,"value":"user1"}]}]}`), + }, + expected: config.RulesFragment{ExclusionData: []config.DataEntry{{ID: "test", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ + {Expiration: 3494138481, Value: "user1"}, + }}}}, + statuses: map[string]rc.ApplyStatus{"some/path": {State: rc.ApplyStateAcknowledged}}, + }, + { + name: "multiple-exclusions-values", + update: map[string][]byte{ + "some/path": []byte(`{"exclusion_data":[{"id":"test","type":"data_with_expiration","data":[{"expiration":3494138481,"value":"user1"},{"expiration":3494138441,"value":"user2"}]}]}`), + }, + expected: config.RulesFragment{ExclusionData: []config.DataEntry{{ID: "test", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ + {Expiration: 3494138481, Value: "user1"}, + {Expiration: 3494138441, Value: "user2"}, + }}}}, + statuses: map[string]rc.ApplyStatus{"some/path": {State: rc.ApplyStateAcknowledged}}, + }, + { + name: "multiple-exclusions-entries", + update: map[string][]byte{ + "some/path": []byte(`{"exclusion_data":[{"id":"test1","type":"data_with_expiration","data":[{"expiration":3494138444,"value":"user3"}]},{"id":"test2","type":"data_with_expiration","data":[{"expiration":3495138481,"value":"user4"}]}]}`), + }, + expected: config.RulesFragment{ExclusionData: []config.DataEntry{ + {ID: "test1", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ + {Expiration: 3494138444, Value: "user3"}, + }}, {ID: "test2", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ + {Expiration: 3495138481, Value: "user4"}, + }}, + }}, + statuses: map[string]rc.ApplyStatus{"some/path": {State: rc.ApplyStateAcknowledged}}, + }, + { + name: "merging-exclusions-entries", + update: map[string][]byte{ + "some/path/1": []byte(`{"exclusion_data":[{"id":"test1","type":"data_with_expiration","data":[{"expiration":3494138444,"value":"user3"}]},{"id":"test2","type":"data_with_expiration","data":[{"expiration":3495138481,"value":"user4"}]}]}`), + "some/path/2": []byte(`{"exclusion_data":[{"id":"test1","type":"data_with_expiration","data":[{"expiration":3494138445,"value":"user3"}]},{"id":"test2","type":"data_with_expiration","data":[{"expiration":0,"value":"user5"}]}]}`), }, + expected: config.RulesFragment{ExclusionData: []config.DataEntry{ + {ID: "test1", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ + {Expiration: 3494138445, Value: "user3"}, + }}, + {ID: "test2", Type: "data_with_expiration", Data: []rc.ASMDataRuleDataEntry{ + {Expiration: 3495138481, Value: "user4"}, + {Expiration: 0, Value: "user5"}, + }}, + }}, statuses: map[string]rc.ApplyStatus{ "some/path/1": {State: rc.ApplyStateAcknowledged}, "some/path/2": {State: rc.ApplyStateAcknowledged}, @@ -191,30 +246,27 @@ func TestMergeRulesData(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - merged, statuses := mergeRulesData(tc.update) - // Sort the compared elements since ordering is not guaranteed and the slice hold types that embed - // more slices - require.Len(t, merged, len(tc.expected)) - sort.Slice(merged, func(i, j int) bool { - return strings.Compare(merged[i].ID, merged[j].ID) < 0 - }) - sort.Slice(tc.expected, func(i, j int) bool { - return strings.Compare(merged[i].ID, merged[j].ID) < 0 - }) - - for i := range tc.expected { - require.Equal(t, tc.expected[i].ID, merged[i].ID) - require.Equal(t, tc.expected[i].Type, merged[i].Type) - require.ElementsMatch(t, tc.expected[i].Data, merged[i].Data) - } - for k := range statuses { - require.Equal(t, tc.statuses[k].State, statuses[k].State) - if statuses[k].State == rc.ApplyStateError { - require.NotEmpty(t, statuses[k].Error) - } else { - require.Empty(t, statuses[k].Error) + fragment, statuses := mergeASMDataUpdates(tc.update) + + // Sort the data entries to make the comparison easier + sort := func(actual []config.DataEntry) { + slices.SortStableFunc(actual, func(a, b config.DataEntry) int { + return strings.Compare(a.ID, b.ID) + }) + for _, data := range actual { + slices.SortStableFunc(data.Data, func(a, b rc.ASMDataRuleDataEntry) int { + return strings.Compare(a.Value, b.Value) + }) } } + + sort(fragment.RulesData) + sort(fragment.ExclusionData) + sort(tc.expected.RulesData) + sort(tc.expected.ExclusionData) + + require.Equal(t, tc.expected, fragment) + require.Equal(t, tc.statuses, statuses) }) } } @@ -437,7 +489,7 @@ func craftRCUpdates(fragments map[string]config.RulesFragment) map[string]remote update[rc.ProductASM] = make(remoteconfig.ProductUpdate) } update[rc.ProductASM][path] = data - } else if len(frag.RulesData) > 0 { + } else if len(frag.RulesData) > 0 || len(frag.ExclusionData) > 0 { if _, ok := update[rc.ProductASMData]; !ok { update[rc.ProductASMData] = make(remoteconfig.ProductUpdate) } @@ -457,7 +509,7 @@ type testRulesOverrideEntry struct { func TestOnRCUpdate(t *testing.T) { - BaseRuleset, err := config.NewRulesManeger(nil) + BaseRuleset, err := config.NewRulesManager(nil) require.NoError(t, err) BaseRuleset.Compile() @@ -671,7 +723,7 @@ func TestOnRCUpdate(t *testing.T) { } func TestOnRCUpdateStatuses(t *testing.T) { - invalidRuleset, err := config.NewRulesManeger([]byte(`{"version": "2.2", "metadata": {"rules_version": "1.4.2"}, "rules": [{"id": "id","name":"name","tags":{},"conditions":[],"transformers":[],"on_match":[]}]}`)) + invalidRuleset, err := config.NewRulesManager([]byte(`{"version": "2.2", "metadata": {"rules_version": "1.4.2"}, "rules": [{"id": "id","name":"name","tags":{},"conditions":[],"transformers":[],"on_match":[]}]}`)) require.NoError(t, err) invalidRules := invalidRuleset.Base overrides := config.RulesFragment{ diff --git a/internal/appsec/testdata/sab.json b/internal/appsec/testdata/sab.json new file mode 100644 index 0000000000..8c1ce8e0df --- /dev/null +++ b/internal/appsec/testdata/sab.json @@ -0,0 +1,136 @@ +{ + "version": "2.2", + "metadata": { + "rules_version": "1.4.2" + }, + "rules": [ + { + "id": "crs-933-130-block", + "name": "PHP Injection Attack: Global Variables Found", + "tags": { + "type": "php_code_injection", + "crs_id": "933130", + "category": "attack_attempt", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + } + ], + "list": [ + "$globals", + "$_cookie", + "$_env", + "$_files", + "$_get", + "$_post", + "$_request", + "$_server", + "$_session", + "$argc", + "$argv", + "$http_\\u200bresponse_\\u200bheader", + "$php_\\u200berrormsg", + "$http_cookie_vars", + "$http_env_vars", + "$http_get_vars", + "$http_post_files", + "$http_post_vars", + "$http_raw_post_data", + "$http_request_vars", + "$http_server_vars" + ] + }, + "operator": "phrase_match" + } + ], + "transformers": [ + "lowercase" + ] + } + ], + "actions": [ + { + "id": "block_402", + "type": "block_request", + "parameters": { + "status_code": 402, + "type": "auto" + } + }, + { + "id": "block_401", + "type": "block_request", + "parameters": { + "status_code": 401, + "type": "auto" + } + } + ], + "exclusions": [ + { + "conditions": [ + { + "operator": "ip_match", + "parameters": { + "data": "suspicious_ips", + "inputs": [ + { + "address": "http.client_ip" + } + ] + } + } + ], + "id": "suspicious_ip_blocking", + "on_match": "block_402" + }, + { + "conditions": [ + { + "operator": "exact_match", + "parameters": { + "data": "suspicious_users", + "inputs": [ + { + "address": "usr.id" + } + ] + } + } + ], + "transformers": [], + "id": "suspicious_user_blocking", + "on_match": "block_401" + } + ], + "exclusion_data": [ + { + "id": "suspicious_ips", + "type": "ip_with_expiration", + "data": [ + { "value": "1.2.3.4" } + ] + }, + { + "id": "suspicious_users", + "type": "data_with_expiration", + "data": [ + { "value": "blocked-user-1" } + ] + } + ] +} diff --git a/internal/appsec/waf_test.go b/internal/appsec/waf_test.go index 93911e7736..dc46356381 100644 --- a/internal/appsec/waf_test.go +++ b/internal/appsec/waf_test.go @@ -743,6 +743,124 @@ func TestRASPLFI(t *testing.T) { } } +func TestSuspiciousAttackerBlocking(t *testing.T) { + t.Setenv("DD_APPSEC_RULES", "testdata/sab.json") + appsec.Start() + defer appsec.Stop() + if !appsec.Enabled() { + t.Skip("AppSec needs to be enabled for this test") + } + + const bodyBlockingRule = "crs-933-130-block" + + // Start and trace an HTTP server + mux := httptrace.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if err := pAppsec.SetUser(r.Context(), r.Header.Get("test-usr")); err != nil { + return + } + buf := new(strings.Builder) + io.Copy(buf, r.Body) + if err := pAppsec.MonitorParsedHTTPBody(r.Context(), buf.String()); err != nil { + return + } + w.Write([]byte("Hello World!\n")) + }) + srv := httptest.NewServer(mux) + defer srv.Close() + + for _, tc := range []struct { + name string + headers map[string]string + status int + ruleMatch string + attack string + }{ + { + name: "ip/not-suspicious/no-attack", + status: 200, + }, + { + name: "ip/suspicious/no-attack", + headers: map[string]string{"x-forwarded-for": "1.2.3.4"}, + status: 200, + }, + { + name: "ip/not-suspicious/attack", + status: 200, + attack: "$globals", + ruleMatch: bodyBlockingRule, + }, + { + name: "ip/suspicious/attack", + headers: map[string]string{"x-forwarded-for": "1.2.3.4"}, + status: 402, + attack: "$globals", + ruleMatch: bodyBlockingRule, + }, + { + name: "user/not-suspicious/no-attack", + status: 200, + }, + { + name: "user/suspicious/no-attack", + headers: map[string]string{"test-usr": "blocked-user-1"}, + status: 200, + }, + { + name: "user/not-suspicious/attack", + status: 200, + attack: "$globals", + ruleMatch: bodyBlockingRule, + }, + { + name: "user/suspicious/attack", + headers: map[string]string{"test-usr": "blocked-user-1"}, + status: 401, + attack: "$globals", + ruleMatch: bodyBlockingRule, + }, + { + name: "ip+user/suspicious/no-attack", + headers: map[string]string{"x-forwarded-for": "1.2.3.4", "test-usr": "blocked-user-1"}, + status: 200, + }, + { + name: "ip+user/suspicious/attack", + headers: map[string]string{"x-forwarded-for": "1.2.3.4", "test-usr": "blocked-user-1"}, + status: 402, + attack: "$globals", + ruleMatch: bodyBlockingRule, + }, + } { + t.Run(tc.name, func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + req, err := http.NewRequest("POST", srv.URL, strings.NewReader(tc.attack)) + require.NoError(t, err) + for k, v := range tc.headers { + req.Header.Set(k, v) + } + res, err := srv.Client().Do(req) + require.NoError(t, err) + defer res.Body.Close() + if tc.ruleMatch != "" { + spans := mt.FinishedSpans() + require.Len(t, spans, 1) + require.Contains(t, spans[0].Tag("_dd.appsec.json"), tc.ruleMatch) + } + require.Equal(t, tc.status, res.StatusCode) + b, err := io.ReadAll(res.Body) + require.NoError(t, err) + if tc.status == 200 { + require.Equal(t, "Hello World!\n", string(b)) + } else { + require.NotEqual(t, "Hello World!\n", string(b)) + } + }) + } +} + // BenchmarkSampleWAFContext benchmarks the creation of a WAF context and running the WAF on a request/response pair // This is a basic sample of what could happen in a real-world scenario. func BenchmarkSampleWAFContext(b *testing.B) { diff --git a/internal/exectracetest/go.mod b/internal/exectracetest/go.mod index 44fa2213ce..c4f2db2be9 100644 --- a/internal/exectracetest/go.mod +++ b/internal/exectracetest/go.mod @@ -12,10 +12,10 @@ require ( require ( github.com/DataDog/appsec-internal-go v1.7.0 // indirect github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect - github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect + github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0 // indirect github.com/DataDog/datadog-go/v5 v5.3.0 // indirect github.com/DataDog/go-libddwaf/v3 v3.4.0 // indirect - github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect + github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect github.com/DataDog/sketches-go v1.4.5 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -30,7 +30,7 @@ require ( go.uber.org/atomic v1.11.0 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.23.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.22.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect diff --git a/internal/exectracetest/go.sum b/internal/exectracetest/go.sum index fd6ac6cac9..8377c57bd8 100644 --- a/internal/exectracetest/go.sum +++ b/internal/exectracetest/go.sum @@ -2,14 +2,14 @@ github.com/DataDog/appsec-internal-go v1.7.0 h1:iKRNLih83dJeVya3IoUfK+6HLD/hQsIb github.com/DataDog/appsec-internal-go v1.7.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g= github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8= github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3xzMthNFhXfOaXlrsdgqmJ73lndFf8c= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0 h1:LplNAmMgZvGU7kKA0+4c1xWOjz828xweW5TCi8Mw9Q0= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0/go.mod h1:4Vo3SJ24uzfKHUHLoFa8t8o+LH+7TCQ7sPcZDtOpSP4= github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8= github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q= github.com/DataDog/go-libddwaf/v3 v3.4.0 h1:NJ2W2vhYaOm1OWr1LJCbdgp7ezG/XLJcQKBmjFwhSuM= github.com/DataDog/go-libddwaf/v3 v3.4.0/go.mod h1:n98d9nZ1gzenRSk53wz8l6d34ikxS+hs62A31Fqmyi4= -github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I= -github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= +github.com/DataDog/go-tuf v1.1.0-0.5.2 h1:4CagiIekonLSfL8GMHRHcHudo1fQnxELS9g4tiAupQ4= +github.com/DataDog/go-tuf v1.1.0-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= github.com/DataDog/sketches-go v1.4.5 h1:ki7VfeNz7IcNafq7yI/j5U/YCkO3LJiMDtXz9OMQbyE= @@ -114,8 +114,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -136,8 +136,8 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=