From 4d7dbf0ef054c41c3a737de238cdd5a704cb741a Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 18 Aug 2022 11:04:49 +0900 Subject: [PATCH] Implement all phases (#1) * Implement all phases --- Makefile | 1 + go.mod | 3 +- go.sum | 7 ++- main.go | 173 ++++++++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 148 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index 7617ad4c9ba3..6b9eaf838fb0 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ CONTAINER_NAME=$(ARTIFACT_NAME)-build build: mkdir -p ./build tinygo build -o build/mainraw.wasm -scheduler=none -target=wasi ./main.go + wasm2wat build/mainraw.wasm -o build/mainraw.wat # Removes unused code, which is important since compiled unused code may import unavailable host functions wasm-opt -Os -c build/mainraw.wasm -o build/mainopt.wasm # Unfortuantely the imports themselves are left due to potential use with call_indirect. Hack away missing functions diff --git a/go.mod b/go.mod index a52c5a5c2fbf..32d39dcfcd5d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/jcchavezs/coraza-wasm-filter go 1.17 require ( - github.com/corazawaf/coraza/v3 v3.0.0-20220816155047-a86760516fd9 + github.com/corazawaf/coraza/v3 v3.0.0-20220818013656-f749c07295aa github.com/stretchr/testify v1.7.1 github.com/tetratelabs/proxy-wasm-go-sdk v0.18.1-0.20220510133519-6240ca761207 github.com/tidwall/gjson v1.14.2 @@ -13,6 +13,7 @@ require ( github.com/corazawaf/libinjection-go v0.0.0-20220207031228-44e9c4250eb5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.1.0 // indirect + github.com/magefile/mage v1.13.0 // indirect github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/go.sum b/go.sum index 2792764c347f..886624b49f6d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ -github.com/corazawaf/coraza/v3 v3.0.0-20220816155047-a86760516fd9 h1:StT0O9ZPmihr25eJeFvWFkHfyLEaqd41vg8CYAE6qHY= -github.com/corazawaf/coraza/v3 v3.0.0-20220816155047-a86760516fd9/go.mod h1:HW14DrTnueAukBpS06YbaY46FtVI3L1v4MElDD+aKCk= +github.com/anuraaga/go-modsecurity v0.0.0-20220816070944-f36055ce7d5d/go.mod h1:7jguE759ADzy2EkxGRXigiC0ER1Yq2IFk2qNtwgzc7U= +github.com/corazawaf/coraza/v3 v3.0.0-20220818013656-f749c07295aa h1:VcGQMo37Il7VWGGaTKfDs+/1BaJaDEFiwuq6N39uKDg= +github.com/corazawaf/coraza/v3 v3.0.0-20220818013656-f749c07295aa/go.mod h1:WhAgJsa8yn/1Yx25VbLaRmPLOUAT0vkA5TzlVOkXsMc= github.com/corazawaf/libinjection-go v0.0.0-20220207031228-44e9c4250eb5 h1:SukhxLQRRBM3nJFEUF+ePG7l0JTWAvaxaG/o6X/FQVY= github.com/corazawaf/libinjection-go v0.0.0-20220207031228-44e9c4250eb5/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -12,6 +13,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magefile/mage v1.13.0 h1:XtLJl8bcCM7EFoO8FyH8XK3t7G5hQAeK+i4tq+veT9M= +github.com/magefile/mage v1.13.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= diff --git a/main.go b/main.go index 44470d4c8160..233f99453dfa 100644 --- a/main.go +++ b/main.go @@ -3,9 +3,11 @@ package main import ( "context" "fmt" + "strconv" "github.com/corazawaf/coraza/v3" "github.com/corazawaf/coraza/v3/seclang" + ctypes "github.com/corazawaf/coraza/v3/types" "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" "github.com/tidwall/gjson" @@ -23,15 +25,15 @@ type vmContext struct { // Override types.DefaultVMContext. func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { - return &pluginContext{} + return &corazaPlugin{} } -type pluginContext struct { +type corazaPlugin struct { // Embed the default plugin context here, // so that we don't need to reimplement all the methods. types.DefaultPluginContext - configuration pluginConfiguration + waf *coraza.Waf } // pluginConfiguration is a type to represent an example configuration for this wasm plugin. @@ -40,7 +42,7 @@ type pluginConfiguration struct { } // Override types.DefaultPluginContext. -func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { +func (ctx *corazaPlugin) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { data, err := proxywasm.GetPluginConfiguration() if err != nil && err != types.ErrorStatusNotFound { proxywasm.LogCriticalf("error reading plugin configuration: %v", err) @@ -51,7 +53,21 @@ func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPlu proxywasm.LogCriticalf("error parsing plugin configuration: %v", err) return types.OnPluginStartStatusFailed } - ctx.configuration = config + + // First we initialize our waf and our seclang parser + waf := coraza.NewWaf() + parser, err := seclang.NewParser(waf) + if err != nil { + proxywasm.LogCriticalf("failed to create seclang parser: %v", err) + } + + err = parser.FromString(config.rules) + if err != nil { + proxywasm.LogCriticalf("failed to parse rules: %v", err) + } + + ctx.waf = waf + return types.OnPluginStartStatusOK } @@ -71,34 +87,23 @@ func parsePluginConfiguration(data []byte) (pluginConfiguration, error) { } // Override types.DefaultPluginContext. -func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext { - // First we initialize our waf and our seclang parser - waf := coraza.NewWaf() - parser, err := seclang.NewParser(waf) - if err != nil { - proxywasm.LogCriticalf("failed to create seclang parser: %v", err) - } - - err = parser.FromString(ctx.configuration.rules) - if err != nil { - proxywasm.LogCriticalf("failed to parse rules: %v", err) - } - - return &httpHeaders{contextID: contextID, waf: waf} +func (ctx *corazaPlugin) NewHttpContext(contextID uint32) types.HttpContext { + return &httpContext{contextID: contextID, tx: ctx.waf.NewTransaction(context.Background())} } -type httpHeaders struct { +type httpContext struct { // Embed the default http context here, // so that we don't need to reimplement all the methods. types.DefaultHttpContext contextID uint32 - waf *coraza.Waf + tx *coraza.Transaction } // Override types.DefaultHttpContext. -func (ctx *httpHeaders) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { - tx := ctx.waf.NewTransaction(context.Background()) +func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { + tx := ctx.tx + // TODO(anuraaga): Do these work with HTTP/1? path, err := proxywasm.GetHttpRequestHeader(":path") if err != nil { proxywasm.LogCriticalf("failed to get path header: %v", err) @@ -111,7 +116,7 @@ func (ctx *httpHeaders) OnHttpRequestHeaders(numHeaders int, endOfStream bool) t return types.ActionContinue } - tx.ProcessURI(path, method, "1.1") // TODO use the right HTTP version + tx.ProcessURI(path, method, "2.0") // TODO use the right HTTP version hs, err := proxywasm.GetHttpRequestHeaders() if err != nil { @@ -125,15 +130,103 @@ func (ctx *httpHeaders) OnHttpRequestHeaders(numHeaders int, endOfStream bool) t interruption := tx.ProcessRequestHeaders() if interruption != nil { - proxywasm.LogInfof("%d interrupted, action %q", ctx.contextID, interruption.Action) - statusCode := interruption.Status - if statusCode == 0 { - statusCode = 403 - } + ctx.handleInterruption(interruption) + return types.ActionContinue + } + + return types.ActionContinue +} + +func (ctx *httpContext) OnHttpRequestBody(bodySize int, endOfStream bool) types.Action { + tx := ctx.tx - if err := proxywasm.SendHttpResponse(uint32(statusCode), nil, nil, -1); err != nil { - panic(err) - } + body, err := proxywasm.GetHttpRequestBody(0, bodySize) + if err != nil { + proxywasm.LogCriticalf("failed to get request body: %v", err) + return types.ActionContinue + } + + _, err = tx.RequestBodyBuffer.Write(body) + if err != nil { + proxywasm.LogCriticalf("failed to read request body: %v", err) + return types.ActionContinue + } + + if !endOfStream { + return types.ActionContinue + } + + interruption, err := tx.ProcessRequestBody() + if err != nil { + proxywasm.LogCriticalf("failed to process request body: %v", err) + return types.ActionContinue + } + if interruption != nil { + ctx.handleInterruption(interruption) + return types.ActionContinue + } + + return types.ActionContinue +} + +func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action { + tx := ctx.tx + + status, err := proxywasm.GetHttpResponseHeader(":status") + if err != nil { + proxywasm.LogCriticalf("failed to get status header: %v", err) + return types.ActionContinue + } + code, err := strconv.Atoi(status) + if err != nil { + code = 0 + } + + hs, err := proxywasm.GetHttpResponseHeaders() + if err != nil { + proxywasm.LogCriticalf("failed to get response headers: %v", err) + return types.ActionContinue + } + + for _, h := range hs { + tx.AddResponseHeader(h[0], h[1]) + } + + interruption := tx.ProcessResponseHeaders(code, "2.0") + if interruption != nil { + ctx.handleInterruption(interruption) + return types.ActionContinue + } + + return types.ActionContinue +} + +func (ctx *httpContext) OnHttpResponseBody(bodySize int, endOfStream bool) types.Action { + tx := ctx.tx + + body, err := proxywasm.GetHttpResponseBody(0, bodySize) + if err != nil { + proxywasm.LogCriticalf("failed to get response body: %v", err) + return types.ActionContinue + } + + _, err = tx.ResponseBodyBuffer.Write(body) + if err != nil { + proxywasm.LogCriticalf("failed to read response body: %v", err) + return types.ActionContinue + } + + if !endOfStream { + return types.ActionContinue + } + + interruption, err := tx.ProcessResponseBody() + if err != nil { + proxywasm.LogCriticalf("failed to process response body: %v", err) + return types.ActionContinue + } + if interruption != nil { + ctx.handleInterruption(interruption) return types.ActionContinue } @@ -141,6 +234,20 @@ func (ctx *httpHeaders) OnHttpRequestHeaders(numHeaders int, endOfStream bool) t } // Override types.DefaultHttpContext. -func (ctx *httpHeaders) OnHttpStreamDone() { +func (ctx *httpContext) OnHttpStreamDone() { + ctx.tx.ProcessLogging() + _ = ctx.tx.Clean() proxywasm.LogInfof("%d finished", ctx.contextID) } + +func (ctx *httpContext) handleInterruption(interruption *ctypes.Interruption) { + proxywasm.LogInfof("%d interrupted, action %q", ctx.contextID, interruption.Action) + statusCode := interruption.Status + if statusCode == 0 { + statusCode = 403 + } + + if err := proxywasm.SendHttpResponse(uint32(statusCode), nil, nil, -1); err != nil { + panic(err) + } +}