Skip to content

Commit

Permalink
feat: move id generation from haproxy to app
Browse files Browse the repository at this point in the history
This allows us to not rely on the http header,
but instead use a variable inside the transaction.
  • Loading branch information
fionera committed Feb 19, 2024
1 parent 72a004a commit f0db161
Show file tree
Hide file tree
Showing 5 changed files with 19 additions and 20 deletions.
4 changes: 2 additions & 2 deletions examples/coraza.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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


4 changes: 1 addition & 3 deletions examples/haproxy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions internal/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
20 changes: 12 additions & 8 deletions internal/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"bytes"
"context"
"fmt"
"math/rand"
"net/netip"
"strings"
"time"
Expand All @@ -26,7 +27,6 @@ type Application struct {
}

type applicationRequest struct {
ID string
SrcIp netip.Addr
SrcPort int64
DstIp netip.Addr
Expand All @@ -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":
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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")
}
Expand Down
7 changes: 2 additions & 5 deletions internal/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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(`
Expand Down

0 comments on commit f0db161

Please sign in to comment.