Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions pkg/acquisition/modules/appsec/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ func (w *Source) Configure(ctx context.Context, yamlConfig []byte, logger *log.E

appsecRuntime, err := appsecCfg.Build(ctx, w.hub)
if err != nil {
if len(appsecCfg.LoadedFrom) > 0 {
return fmt.Errorf("unable to build appsec_config [%s]: %w", strings.Join(appsecCfg.LoadedFrom, ", "), err)
}

return fmt.Errorf("unable to build appsec_config: %w", err)
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/appsec/appsec.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,10 @@ type AppsecConfig struct {

LogLevel *log.Level `yaml:"log_level"`
Logger *log.Entry `yaml:"-"`

// LoadedFrom lists the appsec-config file(s) merged into this config, in load order.
// Used to track error origins at load time
LoadedFrom []string `yaml:"-"`
Comment on lines +523 to +525
}

func (w *AppsecRuntimeConfig) NewRequestState() AppsecRequestState {
Expand Down Expand Up @@ -607,6 +611,8 @@ func (wc *AppsecConfig) LoadByPath(file string) error {
// into flat fields. Hooks stay in the phase sections for Build() to compile separately.
tmp.normalizePhaseScoped()

wc.LoadedFrom = append(wc.LoadedFrom, file)

if wc.Name == "" && tmp.Name != "" {
wc.Name = tmp.Name
}
Expand Down
24 changes: 24 additions & 0 deletions pkg/appsec/appsec_config_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package appsec

import (
"context"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -193,6 +194,29 @@
// Phase-scoped hook from file 2
require.NotNil(t, cfg.InBand)
assert.Len(t, cfg.InBand.OnMatch, 1)

// Both source files are tracked, in load order.
assert.Equal(t, []string{f1, f2}, cfg.LoadedFrom)
}

// A hook filter that fails to compile must surface an error naming the
// appsec-config file it came from, so `crowdsec -t` points at the faulty config.
func TestBuildErrorNamesSourceFile(t *testing.T) {
cfg := newTestConfig()
Comment on lines +202 to +205
f := writeTempYAML(t, `
name: broken-config
inband:
pre_eval:
- filter: "req.URL.Path matches '(?i)\\.(css)$'"
`)

require.NoError(t, cfg.LoadByPath(f))
require.Equal(t, []string{f}, cfg.LoadedFrom)

// hub is unused: compilation fails before any rule is loaded.
_, err := cfg.Build(context.Background(), nil)

Check failure on line 217 in pkg/appsec/appsec_config_test.go

View workflow job for this annotation

GitHub Actions / Build + tests

context.Background() could be replaced by t.Context() in TestBuildErrorNamesSourceFile (usetesting)
require.Error(t, err)
assert.Contains(t, err.Error(), "unable to compile filter")
}

func TestLoadByPathPhaseOptionsOverride(t *testing.T) {
Expand Down
Loading