From f0db1611cadde7756bf9a5871801e15e23294ac4 Mon Sep 17 00:00:00 2001 From: Fionera Date: Mon, 19 Feb 2024 19:09:04 +0100 Subject: [PATCH] feat: move id generation from haproxy to app This allows us to not rely on the http header, but instead use a variable inside the transaction. --- examples/coraza.cfg | 4 ++-- examples/haproxy.cfg | 4 +--- internal/agent.go | 4 ++-- internal/application.go | 20 ++++++++++++-------- internal/e2e_test.go | 7 ++----- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/examples/coraza.cfg b/examples/coraza.cfg index 5b7fcfb..839f647 100644 --- a/examples/coraza.cfg +++ b/examples/coraza.cfg @@ -15,11 +15,11 @@ spoe-agent coraza-agent log global spoe-message coraza-req - args app=str(sample_app) id=unique-id src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body + args app=str(sample_app) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body event on-frontend-http-request spoe-message coraza-res - args app=str(sample_app) id=unique-id version=res.ver status=status headers=res.hdrs body=res.body + args app=str(sample_app) id=var(txn.e2e.id) version=res.ver status=status headers=res.hdrs body=res.body event on-http-response diff --git a/examples/haproxy.cfg b/examples/haproxy.cfg index 6564bd8..e28555d 100644 --- a/examples/haproxy.cfg +++ b/examples/haproxy.cfg @@ -15,9 +15,7 @@ defaults frontend test mode http bind *:80 - - unique-id-format %[uuid()] - unique-id-header X-Unique-ID + filter spoe engine coraza config /etc/haproxy/coraza.cfg # Currently haproxy cannot use variables to set the code or deny_status, so this needs to be manually configured here diff --git a/internal/agent.go b/internal/agent.go index e6952f3..8b308f5 100644 --- a/internal/agent.go +++ b/internal/agent.go @@ -32,7 +32,7 @@ func (a *Agent) HandleSPOE(ctx context.Context, writer *encoding.ActionWriter, m messageCorazaResponse = "coraza-res" ) - var messageHandler func(*Application, context.Context, *encoding.Message) error + var messageHandler func(*Application, context.Context, *encoding.ActionWriter, *encoding.Message) error switch name := string(message.NameBytes()); name { case messageCorazaRequest: messageHandler = (*Application).HandleRequest @@ -65,7 +65,7 @@ func (a *Agent) HandleSPOE(ctx context.Context, writer *encoding.ActionWriter, m return } - err := messageHandler(app, ctx, message) + err := messageHandler(app, ctx, writer, message) if err == nil { return } diff --git a/internal/application.go b/internal/application.go index ceb2ded..b0d76fd 100644 --- a/internal/application.go +++ b/internal/application.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "fmt" + "math/rand" "net/netip" "strings" "time" @@ -26,7 +27,6 @@ type Application struct { } type applicationRequest struct { - ID string SrcIp netip.Addr SrcPort int64 DstIp netip.Addr @@ -39,15 +39,13 @@ type applicationRequest struct { Body []byte } -func (a *Application) HandleRequest(ctx context.Context, message *encoding.Message) error { +func (a *Application) HandleRequest(ctx context.Context, writer *encoding.ActionWriter, message *encoding.Message) error { k := encoding.AcquireKVEntry() defer encoding.ReleaseKVEntry(k) var req applicationRequest for message.KV.Next(k) { switch name := string(k.NameBytes()); name { - case "id": - req.ID = string(k.ValueBytes()) case "src-ip": req.SrcIp = k.ValueAddr() case "src-port": @@ -98,13 +96,19 @@ func (a *Application) HandleRequest(ctx context.Context, message *encoding.Messa } } - if req.ID == "" { - return fmt.Errorf("request id is empty") + const idLength = 16 + var sb strings.Builder + sb.Grow(idLength) + for i := 0; i < idLength; i++ { + sb.WriteRune(rune('A' + rand.Intn(26))) } - tx := a.waf.NewTransactionWithID(req.ID) + tx := a.waf.NewTransactionWithID(sb.String()) // write transaction as early as possible to prevent cache misses a.cache.SetWithExpiration(tx.ID(), tx, a.TransactionTTLMs*time.Millisecond) + if err := writer.SetString(encoding.VarScopeTransaction, "id", tx.ID()); err != nil { + return err + } tx.ProcessConnection(req.SrcIp.String(), int(req.SrcPort), req.DstIp.String(), int(req.DstPort)) @@ -178,7 +182,7 @@ type applicationResponse struct { Body []byte } -func (a *Application) HandleResponse(ctx context.Context, message *encoding.Message) error { +func (a *Application) HandleResponse(ctx context.Context, writer *encoding.ActionWriter, message *encoding.Message) error { if !a.ResponseCheck { return fmt.Errorf("got response but response check is disabled") } diff --git a/internal/e2e_test.go b/internal/e2e_test.go index 46f20dd..1725b91 100644 --- a/internal/e2e_test.go +++ b/internal/e2e_test.go @@ -76,9 +76,6 @@ func withCoraza(t *testing.T, f func(*testing.T, testutil.HAProxyConfig, string) EngineAddr: l.Addr().String(), FrontendPort: fmt.Sprintf("%d", testutil.TCPPort(t)), CustomFrontendConfig: ` - unique-id-format %[uuid()] - unique-id-header X-Unique-ID - # Currently haproxy cannot use variables to set the code or deny_status, so this needs to be manually configured here http-request redirect code 302 location %[var(txn.e2e.data)] if { var(txn.e2e.action) -m str redirect } http-response redirect code 302 location %[var(txn.e2e.data)] if { var(txn.e2e.action) -m str redirect } @@ -113,11 +110,11 @@ spoe-agent e2e log global spoe-message coraza-req - args app=str(default) id=unique-id src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body + args app=str(default) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body event on-frontend-http-request spoe-message coraza-res - args app=str(default) id=unique-id version=res.ver status=status headers=res.hdrs body=res.body + args app=str(default) id=var(txn.e2e.id) version=res.ver status=status headers=res.hdrs body=res.body event on-http-response `, BackendConfig: fmt.Sprintf(`