From 11c835cb046ec849d9814b8c9a67292f3f2a1397 Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Thu, 2 Dec 2021 16:03:46 +0100 Subject: [PATCH] .github: add system tests in CI (#1059) System tests is a functional test suite that perform black box testing to ensure the good behavior of feature across all components. As now, the coverage is mainly on AppSec, but it can check all features, as long as they have a visible effect (stdout, communication with agent ...). Co-authored-by: Gabriel Aszalos Co-authored-by: Julio Guerra --- .github/workflows/system-tests.yml | 52 ++++++++++++++++++++++++++++++ internal/appsec/appsec.go | 12 ++++++- internal/appsec/config.go | 2 ++ internal/appsec/waf.go | 12 +++---- 4 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/system-tests.yml diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml new file mode 100644 index 0000000000..0c3068bb22 --- /dev/null +++ b/.github/workflows/system-tests.yml @@ -0,0 +1,52 @@ +name: System Tests + +on: + pull_request: + branches: + - "**" + workflow_dispatch: {} + schedule: + - cron: '00 04 * * 2-6' + +jobs: + system-tests: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - library: golang + weblog-variant: net-http + - library: golang + weblog-variant: gorilla + fail-fast: false + env: + TEST_LIBRARY: golang + WEBLOG_VARIANT: ${{ matrix.weblog-variant }} + DD_API_KEY: ${{ secrets.DD_API_KEY }} + steps: + - name: Checkout system tests + uses: actions/checkout@v2 + with: + repository: 'DataDog/system-tests' + + - name: Checkout dd-trace-go + uses: actions/checkout@v2 + with: + path: 'binaries/dd-trace-go' + + - name: Build + run: ./build.sh + + - name: Run + run: ./run.sh + + - name: Compress artifact + if: ${{ always() }} + run: tar -czvf artifact.tar.gz $(ls | grep logs) + + - name: Upload artifact + uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: logs_${{ matrix.weblog-variant }} + path: artifact.tar.gz diff --git a/internal/appsec/appsec.go b/internal/appsec/appsec.go index 6e89b8f00a..48f95e0528 100644 --- a/internal/appsec/appsec.go +++ b/internal/appsec/appsec.go @@ -70,6 +70,16 @@ func Start(cfg *Config) { log.Info("appsec: starting with default recommended security rules") } + cfg.wafTimeout = 4 * time.Millisecond + if wafTimeout := os.Getenv("DD_APPSEC_WAF_TIMEOUT"); wafTimeout != "" { + timeout, err := time.ParseDuration(wafTimeout) + if err != nil { + cfg.wafTimeout = timeout + } else { + log.Error("appsec: could not parse the value of DD_APPSEC_WAF_TIMEOUT %s as a duration: %v. Using default value %s.", wafTimeout, err, cfg.wafTimeout) + } + } + appsec, err := newAppSec(cfg) if err != nil { logUnexpectedStartError(err) @@ -138,7 +148,7 @@ func newAppSec(cfg *Config) (*appsec, error) { // Start starts the AppSec background goroutine. func (a *appsec) start() error { // Register the WAF operation event listener - unregisterWAF, err := registerWAF(a.cfg.rules, a) + unregisterWAF, err := registerWAF(a.cfg.rules, a.cfg.wafTimeout, a) if err != nil { return err } diff --git a/internal/appsec/config.go b/internal/appsec/config.go index 36524822cb..d73a52e7af 100644 --- a/internal/appsec/config.go +++ b/internal/appsec/config.go @@ -38,6 +38,8 @@ type ( // rules loaded via the env var DD_APPSEC_RULES. When not set, the builtin rules will be used. rules []byte + // Maximum WAF execution time + wafTimeout time.Duration } // ServiceConfig is the optional context about the running service. diff --git a/internal/appsec/waf.go b/internal/appsec/waf.go index 544fb182ed..620a029022 100644 --- a/internal/appsec/waf.go +++ b/internal/appsec/waf.go @@ -30,7 +30,7 @@ type wafEvent struct { } // Register the WAF event listener. -func registerWAF(rules []byte, appsec *appsec) (unreg dyngo.UnregisterFunc, err error) { +func registerWAF(rules []byte, timeout time.Duration, appsec *appsec) (unreg dyngo.UnregisterFunc, err error) { // Check the WAF is healthy if _, err := waf.Health(); err != nil { return nil, err @@ -65,7 +65,7 @@ func registerWAF(rules []byte, appsec *appsec) (unreg dyngo.UnregisterFunc, err } // Register the WAF event listener - unregister := dyngo.Register(newWAFEventListener(waf, addresses, appsec)) + unregister := dyngo.Register(newWAFEventListener(waf, addresses, appsec, timeout)) // Return an unregistration function that will also release the WAF instance. return func() { defer waf.Close() @@ -74,7 +74,7 @@ func registerWAF(rules []byte, appsec *appsec) (unreg dyngo.UnregisterFunc, err } // newWAFEventListener returns the WAF event listener to register in order to enable it. -func newWAFEventListener(handle *waf.Handle, addresses []string, appsec *appsec) dyngo.EventListener { +func newWAFEventListener(handle *waf.Handle, addresses []string, appsec *appsec, timeout time.Duration) dyngo.EventListener { return httpinstr.OnHandlerOperationStart(func(op dyngo.Operation, args httpinstr.HandlerOperationArgs) { // For this handler operation lifetime, create a WAF context and the // list of detected attacks @@ -126,12 +126,12 @@ func newWAFEventListener(handle *waf.Handle, addresses []string, appsec *appsec) values[serverRequestQueryAddr] = args.Query } } - baseEvent = runWAF(wafCtx, values) + baseEvent = runWAF(wafCtx, values, timeout) }) } -func runWAF(wafCtx *waf.Context, values map[string]interface{}) *wafEvent { - matches, err := wafCtx.Run(values, 4*time.Millisecond) +func runWAF(wafCtx *waf.Context, values map[string]interface{}, timeout time.Duration) *wafEvent { + matches, err := wafCtx.Run(values, timeout) if err != nil { log.Error("appsec: waf error: %v", err) return nil