diff --git a/main_test.go b/main_test.go index 975a3fc..f23203c 100644 --- a/main_test.go +++ b/main_test.go @@ -1421,7 +1421,7 @@ func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { buildPath := filepath.Join("build", "mainraw.wasm") wasm, err := os.ReadFile(buildPath) if err != nil { - t.Fatal("wasm not found") + t.Skipf("wasm not found at %s, run mage build to generate it", buildPath) } v, err := proxytest.NewWasmVMContext(wasm) require.NoError(t, err) diff --git a/wasmplugin/fs.go b/wasmplugin/fs.go index b7d0862..a753029 100644 --- a/wasmplugin/fs.go +++ b/wasmplugin/fs.go @@ -7,6 +7,7 @@ import ( "embed" "fmt" "io/fs" + "path/filepath" "strings" ) @@ -44,6 +45,7 @@ func (r rulesFS) Open(name string) (fs.File, error) { } func (r rulesFS) ReadDir(name string) ([]fs.DirEntry, error) { + name = normalizePath(name) for a, dst := range r.dirsMapping { if a == name { return fs.ReadDir(r.fs, dst) @@ -62,6 +64,7 @@ func (r rulesFS) ReadFile(name string) ([]byte, error) { } func (r rulesFS) mapPath(p string) string { + p = normalizePath(p) if strings.IndexByte(p, '/') != -1 { // is not in root, hence we can do dir mapping for a, dst := range r.dirsMapping { @@ -80,3 +83,7 @@ func (r rulesFS) mapPath(p string) string { return p } + +func normalizePath(p string) string { + return filepath.ToSlash(p) +} diff --git a/wasmplugin/plugin.go b/wasmplugin/plugin.go index cdc73ec..54db0f2 100644 --- a/wasmplugin/plugin.go +++ b/wasmplugin/plugin.go @@ -180,7 +180,7 @@ func (ctx *corazaPlugin) NewHttpContext(contextID uint32) types.HttpContext { return &httpContext{ contextID: contextID, metrics: ctx.metrics, - metricLabelsKV: ctx.metricLabelsKV, + metricLabelsKV: append([]string{}, ctx.metricLabelsKV...), perAuthorityWAFs: ctx.perAuthorityWAFs, } } @@ -660,33 +660,47 @@ func (ctx *httpContext) OnHttpStreamDone() { defer logTime("OnHttpStreamDone", currentTime()) tx := ctx.tx - if tx != nil { - if !tx.IsRuleEngineOff() && !ctx.interruptedAt.isInterrupted() { - // Responses without body won't call OnHttpResponseBody, but there are rules in the response body - // phase that still need to be executed. If they haven't been executed yet, and there has not been a previous - // interruption, now is the time. - if !ctx.processedResponseBody { - ctx.logger.Info().Msg("Running ProcessResponseBody in OnHttpStreamDone, triggered actions will not be enforced. Further logs are for detection only purposes") - ctx.processedResponseBody = true - _, err := tx.ProcessResponseBody() - if err != nil { - ctx.logger.Error(). - Err(err). - Msg("Failed to process response body") - } + if tx == nil { + return + } + + logger := ctx.logger + + defer func() { + // Ensure logging and transaction close always run so TinyGo can release allocations. + tx.ProcessLogging() + if err := tx.Close(); err != nil { + if logger != nil { + logger.Error().Err(err).Msg("Failed to close transaction") + } else { + proxywasm.LogErrorf("Failed to close transaction: %v", err) } } + if logger != nil { + logger.Info().Msg("Finished") + } + // Drop last reference so the GC can reclaim the transaction promptly. + ctx.tx = nil + logMemStats() + }() - // ProcessLogging is still called even if RuleEngine is off for potential logs generated before the engine is turned off. - // Internally, if the engine is off, no log phase rules are evaluated - ctx.tx.ProcessLogging() + if tx.IsRuleEngineOff() || ctx.interruptedAt.isInterrupted() || ctx.processedResponseBody { + return + } - err := ctx.tx.Close() - if err != nil { - ctx.logger.Error().Err(err).Msg("Failed to close transaction") + // Responses without body won't call OnHttpResponseBody, but there are rules in the response body + // phase that still need to be executed. If they haven't been executed yet, and there has not been a previous + // interruption, now is the time. + if logger != nil { + logger.Info().Msg("Running ProcessResponseBody in OnHttpStreamDone, triggered actions will not be enforced. Further logs are for detection only purposes") + } + ctx.processedResponseBody = true + if _, err := tx.ProcessResponseBody(); err != nil { + if logger != nil { + logger.Error().Err(err).Msg("Failed to process response body") + } else { + proxywasm.LogErrorf("Failed to process response body: %v", err) } - ctx.logger.Info().Msg("Finished") - logMemStats() } } diff --git a/wasmplugin/plugin_test.go b/wasmplugin/plugin_test.go index 0e6485b..e10620b 100644 --- a/wasmplugin/plugin_test.go +++ b/wasmplugin/plugin_test.go @@ -72,3 +72,21 @@ func TestRetrieveAddressInfo(t *testing.T) { }) } } + +func TestNewHttpContextMetricLabelsCopy(t *testing.T) { + plugin := &corazaPlugin{ + metricLabelsKV: []string{"foo", "bar"}, + metrics: NewWAFMetrics(), + } + + ctx1 := plugin.NewHttpContext(1).(*httpContext) + require.Equal(t, []string{"foo", "bar"}, plugin.metricLabelsKV) + require.Equal(t, []string{"foo", "bar"}, ctx1.metricLabelsKV) + + ctx1.metricLabelsKV = append(ctx1.metricLabelsKV, "authority", "example.com") + require.Equal(t, []string{"foo", "bar"}, plugin.metricLabelsKV) + require.Equal(t, []string{"foo", "bar", "authority", "example.com"}, ctx1.metricLabelsKV) + + ctx2 := plugin.NewHttpContext(2).(*httpContext) + require.Equal(t, []string{"foo", "bar"}, ctx2.metricLabelsKV) +}