Skip to content

Commit

Permalink
serverless/appsec: add API sec config & testing
Browse files Browse the repository at this point in the history
  • Loading branch information
Hellzy committed Nov 9, 2023
1 parent c4ace14 commit 9767c9f
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 15 deletions.
9 changes: 5 additions & 4 deletions pkg/serverless/appsec/appsec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package appsec

import (
"encoding/json"
"math/rand"
"time"

"github.com/DataDog/appsec-internal-go/appsec"
Expand Down Expand Up @@ -117,10 +118,10 @@ func (a *AppSec) Monitor(addresses map[string]any) (res waf.Result) {
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}
// }
// Ask the WAF for schema reporting if API security is enabled
if config.APISecurityEnabled() && config.APISecuritySampleRate() >= rand.Float64() {
addresses["waf.context.processor"] = map[string]any{"extract-schema": true}
}

res, err := ctx.Run(waf.RunAddressData{Persistent: addresses}, timeout)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions pkg/serverless/appsec/appsec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strconv"
"testing"

"github.com/DataDog/datadog-agent/pkg/serverless/appsec/config"
waf "github.com/DataDog/go-libddwaf"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -74,6 +75,9 @@ func TestMonitor(t *testing.T) {
})

t.Run("api-security", func(t *testing.T) {
t.Setenv("DD_API_SECURITY_REQUEST_SAMPLE_RATE", "1.0")
t.Setenv("DD_EXPERIMENTAL_API_SECURITY_ENABLED", "true")
config.Refresh()
for _, tc := range []struct {
name string
pathParams map[string]any
Expand Down
62 changes: 52 additions & 10 deletions pkg/serverless/appsec/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@ import (
)

const (
enabledEnvVar = "DD_SERVERLESS_APPSEC_ENABLED"
rulesEnvVar = "DD_APPSEC_RULES"
wafTimeoutEnvVar = "DD_APPSEC_WAF_TIMEOUT"
traceRateLimitEnvVar = "DD_APPSEC_TRACE_RATE_LIMIT"
obfuscatorKeyEnvVar = "DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP"
obfuscatorValueEnvVar = "DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP"
tracingEnabledEnvVar = "DD_APM_TRACING_ENABLED"
enabledEnvVar = "DD_SERVERLESS_APPSEC_ENABLED"
rulesEnvVar = "DD_APPSEC_RULES"
wafTimeoutEnvVar = "DD_APPSEC_WAF_TIMEOUT"
traceRateLimitEnvVar = "DD_APPSEC_TRACE_RATE_LIMIT"
obfuscatorKeyEnvVar = "DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP"
obfuscatorValueEnvVar = "DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP"
tracingEnabledEnvVar = "DD_APM_TRACING_ENABLED"
apiSecurityEnabledEnvVar = "DD_EXPERIMENTAL_API_SECURITY_ENABLED"
apiSecuritySampleRateEnvVar = "DD_API_SECURITY_REQUEST_SAMPLE_RATE"
)

const (
defaultAPISecSampleRate = 10. / 100
defaultWAFTimeout = 4 * time.Millisecond
defaultTraceRate = 100 // up to 100 appsec traces/s
defaultObfuscatorKeyRegex = `(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization`
Expand Down Expand Up @@ -57,8 +60,19 @@ type ObfuscatorConfig struct {
ValueRegex string
}

// standalone is assigned at init by reading the environment
var standalone bool
// Config variables assigned at init by reading the environment
var (
apiSecEnabled bool
apiSecSampleRate float64
standalone bool
)

// Refresh updates the configuration by reading from the environment
func Refresh() {
standalone = isStandalone()
apiSecEnabled = apiSecurityEnabled()
apiSecSampleRate = apiSecuritySampleRate()
}

// IsEnabled returns true when appsec is enabled when the environment variable
// It also returns whether the env var is actually set in the env or not
Expand All @@ -79,6 +93,16 @@ func IsStandalone() bool {
return standalone
}

// APISecurityEnabled returns whether API security is enabled through the environment
func APISecurityEnabled() bool {
return apiSecEnabled
}

// APISecuritySampleRate returns the sample rate for API Security schema collection, read from the env
func APISecuritySampleRate() float64 {
return apiSecSampleRate
}

// NewConfig returns a new appsec configuration read from the environment
func NewConfig() (*Config, error) {
rules, err := readRulesConfig()
Expand Down Expand Up @@ -191,6 +215,24 @@ func isStandalone() bool {
return set && !enabled
}

func apiSecurityEnabled() bool {
enabled, _ := strconv.ParseBool(os.Getenv(apiSecurityEnabledEnvVar))
return enabled
}

func apiSecuritySampleRate() float64 {
rate, err := strconv.ParseFloat(os.Getenv(apiSecuritySampleRateEnvVar), 64)
if err != nil {
log.Debugf("appsec: could not parse %s. Defaulting to %f", apiSecuritySampleRateEnvVar, defaultAPISecSampleRate)
return defaultAPISecSampleRate
}
if rate < 0. || rate > 1. {
log.Debugf("appsec: %s value must be between 0 and 1. Defaulting to %f", apiSecuritySampleRateEnvVar, defaultAPISecSampleRate)
return defaultAPISecSampleRate
}
return rate
}

func init() {
standalone = isStandalone()
Refresh()
}
60 changes: 59 additions & 1 deletion pkg/serverless/appsec/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,70 @@ func TestConfig(t *testing.T) {
if tc.env != "" {
require.NoError(t, os.Setenv(tracingEnabledEnvVar, tc.env))
}
standalone = isStandalone()
Refresh()
require.Equal(t, tc.standalone, IsStandalone())
})
}

})

t.Run("api security", func(t *testing.T) {
for _, tc := range []struct {
name string
enabledVar string
sampleRateVar string
enabled bool
sampleRate float64
}{
{
name: "disabled",
sampleRate: defaultAPISecSampleRate,
},
{
name: "disabled",
enabledVar: "false",
sampleRate: defaultAPISecSampleRate,
},
{
name: "disabled",
enabledVar: "0",
sampleRate: defaultAPISecSampleRate,
},
{
name: "disabled",
enabledVar: "weirdvalue",
sampleRate: defaultAPISecSampleRate,
},
{
name: "enabled",
enabledVar: "true",
enabled: true,
sampleRate: defaultAPISecSampleRate,
},
{
name: "enabled",
enabledVar: "1",
enabled: true,
sampleRate: defaultAPISecSampleRate,
},
{
name: "sampleRate 1.0",
enabledVar: "true",
sampleRateVar: "1.0",
enabled: true,
sampleRate: 1.0,
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Setenv(apiSecurityEnabledEnvVar, tc.enabledVar)
t.Setenv(apiSecuritySampleRateEnvVar, tc.sampleRateVar)
Refresh()
require.Equal(t, tc.enabled, APISecurityEnabled())
require.Equal(t, tc.sampleRate, APISecuritySampleRate())
})
}

})
}

func cleanEnv() func() {
Expand Down

0 comments on commit 9767c9f

Please sign in to comment.