Skip to content
This repository was archived by the owner on Apr 3, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions apisec/internal/config/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@

package config

import "time"

const (
// MaxItemCount is the maximum amount of items to keep in a timed set.
MaxItemCount = 4_096

// Interval is the interval between two samples being taken.
Interval = 30 * time.Second
)
13 changes: 8 additions & 5 deletions apisec/internal/timed/lru.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ package timed

import (
"fmt"
"math"
"math/rand"
"sync/atomic"
"time"

"github.com/DataDog/appsec-internal-go/apisec/internal/config"
"github.com/DataDog/appsec-internal-go/log"
)

// capacity is the maximum number of items that may be temporarily present in a
Expand Down Expand Up @@ -45,17 +47,18 @@ type LRU struct {
}

// NewSet initializes a new, empty [LRU] with the given interval and clock
// function. The provided interval must be at least 1 second, and may not exceed
// [config.Interval].
// function. A warning will be logged if it is set below 1 second. Panics if
// the interval is more than [math.MaxUint32] seconds, as this value cannot be
// used internally.
//
// Note: timestamps are stored at second resolution, so the interval will be
// rounded down to the nearest second.
func NewSet(interval time.Duration, clock ClockFunc) *LRU {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

side comment: function name is a little confusing after the renaming

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I just don't see the name anymore at this point -_-

if interval < time.Second {
panic(fmt.Errorf("NewSet: interval must be at least 1s, got %v", interval))
log.Warn("NewSet: interval is less than one second; this should not be attempted in production (value: %s)", interval)
}
if interval > config.Interval {
panic(fmt.Errorf("NewSet: interval must not exceed %s, got %v", config.Interval, interval))
if interval > time.Second*math.MaxUint32 {
panic(fmt.Errorf("NewSet: interval must be <= %s, but was %s", time.Second*math.MaxUint32, interval))
}

intervalSeconds := uint32(interval.Seconds())
Expand Down
14 changes: 7 additions & 7 deletions apisec/internal/timed/lru_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package timed

import (
"context"
"math"
"runtime"
"sync"
"testing"
Expand All @@ -18,20 +19,19 @@ import (
)

func TestLRU(t *testing.T) {
t.Run("New", func(t *testing.T) {
require.PanicsWithError(t, "NewSet: interval must be at least 1s, got 0s", func() { NewSet(0, UnixTime) })
require.PanicsWithError(t, "NewSet: interval must be at least 1s, got 10ms", func() { NewSet(10*time.Millisecond, UnixTime) })
require.PanicsWithError(t, "NewSet: interval must not exceed 30s, got 1m0s", func() { NewSet(time.Minute, UnixTime) })
t.Run("NewSet", func(t *testing.T) {
require.PanicsWithError(t, "NewSet: interval must be <= 1193046h28m15s, but was 1193046h28m16s", func() { NewSet(time.Second*(math.MaxUint32+1), UnixTime) })
})

t.Run("Hit", func(t *testing.T) {
fakeTime := time.Now().Unix()
fakeClock := func() int64 { return fakeTime }

subject := NewSet(config.Interval, fakeClock)
const sampleIntervalSeconds = 30
subject := NewSet(sampleIntervalSeconds*time.Second, fakeClock)

require.True(t, subject.Hit(1337))
for range config.Interval / time.Second {
for range sampleIntervalSeconds {
require.False(t, subject.Hit(1337))
fakeTime++
}
Expand Down Expand Up @@ -63,7 +63,7 @@ func TestLRU(t *testing.T) {
clock.WaitUntilDone()
}()

subject := NewSet(config.Interval, clock.Unix)
subject := NewSet(30*time.Second, clock.Unix)

var (
startBarrier sync.WaitGroup
Expand Down
10 changes: 1 addition & 9 deletions apisec/sampler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"hash/fnv"
"time"

"github.com/DataDog/appsec-internal-go/apisec/internal/config"
"github.com/DataDog/appsec-internal-go/apisec/internal/timed"
)

Expand All @@ -33,14 +32,7 @@ type (
clockFunc = func() int64
)

// NewSampler returns a new [*Sampler] with the default clock function based on
// [time.Now].
func NewSampler() Sampler {
return NewSamplerWithInterval(config.Interval)
}

// NewSamplerWithInterval returns a new [*Sampler] with the specified interval
// instead of the default of 30 seconds.
// NewSamplerWithInterval returns a new [*Sampler] with the specified interval.
func NewSamplerWithInterval(interval time.Duration) Sampler {
return newSampler(interval, timed.UnixTime)
}
Expand Down
21 changes: 20 additions & 1 deletion appsec/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,18 @@ const (
EnvRules = "DD_APPSEC_RULES"
// EnvRASPEnabled is the env var used to enable/disable RASP functionalities for ASM
EnvRASPEnabled = "DD_APPSEC_RASP_ENABLED"

// envAPISecSampleDelay is the env var used to set the delay for the API Security sampler in system tests.
// It is not indended to be set by users.
envAPISecSampleDelay = "DD_API_SECURITY_SAMPLE_DELAY"
)

// Configuration constants and default values
const (
// DefaultAPISecSampleRate is the default rate at which API Security schemas are extracted from requests
DefaultAPISecSampleRate = .1
// DefaultAPISecSampleInterval is the default interval between two samples being taken.
DefaultAPISecSampleInterval = 30 * time.Second
// DefaultObfuscatorKeyRegex is the default regexp used to obfuscate keys
DefaultObfuscatorKeyRegex = `(?i)pass|pw(?:or)?d|secret|(?:api|private|public|access)[_-]?key|token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\.net[_-]sessionid|sid|jwt`
// DefaultObfuscatorValueRegex is the default regexp used to obfuscate values
Expand Down Expand Up @@ -73,7 +79,7 @@ type APISecOption func(*APISecConfig)
func NewAPISecConfig(opts ...APISecOption) APISecConfig {
cfg := APISecConfig{
Enabled: boolEnv(EnvAPISecEnabled, true),
Sampler: apisec.NewSampler(),
Sampler: apisec.NewSamplerWithInterval(durationEnv(envAPISecSampleDelay, "s", DefaultAPISecSampleInterval)),
SampleRate: readAPISecuritySampleRate(),
}
for _, opt := range opts {
Expand Down Expand Up @@ -224,3 +230,16 @@ func boolEnv(key string, def bool) bool {
}
return v
}

func durationEnv(key string, unit string, def time.Duration) time.Duration {
strVal, ok := os.LookupEnv(key)
if !ok {
return def
}
val, err := time.ParseDuration(strVal + unit)
if err != nil {
logEnvVarParsingError(key, strVal, err, def)
return def
}
return val
}
34 changes: 34 additions & 0 deletions appsec/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,37 @@ func TestRASPEnablement(t *testing.T) {
require.True(t, RASPEnabled())
})
}

func TestDurationEnv(t *testing.T) {
const varName = "DD_TEST_VARIABLE_DURATION"

type testCase struct {
EnvVal string
EnvUnit string
Expected time.Duration
}
testCases := map[string]testCase{
"blank": {
EnvVal: "",
EnvUnit: "s",
Expected: 1337,
},
"1m": {
EnvVal: "1",
EnvUnit: "m",
Expected: time.Minute,
},
"invalid": {
EnvVal: "invalid",
EnvUnit: "s",
Expected: 1337,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
t.Setenv(varName, tc.EnvVal)
require.Equal(t, tc.Expected, durationEnv(varName, tc.EnvUnit, 1337))
})
}
}