Skip to content

Commit

Permalink
feat: adds context to transaction. (#963)
Browse files Browse the repository at this point in the history
* feat: adds context to transaction.

* feat: uses options instead of functional arguments for NewTransactionWithOptions.
  • Loading branch information
jcchavezs authored Jan 31, 2024
1 parent a6e53a9 commit 2f18ba0
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 13 deletions.
17 changes: 17 additions & 0 deletions experimental/waf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors
// SPDX-License-Identifier: Apache-2.0

package experimental

import (
"github.com/corazawaf/coraza/v3/internal/corazawaf"
"github.com/corazawaf/coraza/v3/types"
)

type Options = corazawaf.Options

// WAFWithOptions is an interface that allows to create transactions
// with options
type WAFWithOptions interface {
NewTransactionWithOptions(Options) types.Transaction
}
32 changes: 32 additions & 0 deletions experimental/waf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors
// SPDX-License-Identifier: Apache-2.0

package experimental_test

import (
"fmt"

"github.com/corazawaf/coraza/v3"
"github.com/corazawaf/coraza/v3/experimental"
)

func ExampleWAFWithOptions_NewTransactionWithOptions() {
waf, err := coraza.NewWAF(coraza.NewWAFConfig())
if err != nil {
panic(err)
}

oWAF, ok := waf.(experimental.WAFWithOptions)
if !ok {
panic("WAF does not implement WAFWithOptions")
}

tx := oWAF.NewTransactionWithOptions(experimental.Options{
ID: "abc123",
})

fmt.Println("Transaction ID:", tx.ID())

// Output:
// Transaction ID: abc123
}
15 changes: 14 additions & 1 deletion http/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"strings"

"github.com/corazawaf/coraza/v3"
"github.com/corazawaf/coraza/v3/experimental"
"github.com/corazawaf/coraza/v3/types"
)

Expand Down Expand Up @@ -117,8 +118,20 @@ func WrapHandler(waf coraza.WAF, h http.Handler) http.Handler {
return h
}

newTX := func(*http.Request) types.Transaction {
return waf.NewTransaction()
}

if ctxwaf, ok := waf.(experimental.WAFWithOptions); ok {
newTX = func(r *http.Request) types.Transaction {
return ctxwaf.NewTransactionWithOptions(experimental.Options{
Context: r.Context(),
})
}
}

fn := func(w http.ResponseWriter, r *http.Request) {
tx := waf.NewTransaction()
tx := newTX(r)
defer func() {
// We run phase 5 rules and create audit logs (if enabled)
tx.ProcessLogging()
Expand Down
36 changes: 36 additions & 0 deletions internal/corazarules/rule_match.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package corazarules

import (
"context"
"fmt"
"strconv"
"strings"
Expand All @@ -30,6 +31,8 @@ type MatchData struct {
ChainLevel_ int
}

var _ types.MatchData = (*MatchData)(nil)

func (m *MatchData) Variable() variables.RuleVariable {
return m.Variable_
}
Expand Down Expand Up @@ -100,8 +103,12 @@ type MatchedRule struct {
MatchedDatas_ []types.MatchData

Rule_ types.RuleMetadata

Context_ context.Context
}

var _ types.MatchedRule = (*MatchedRule)(nil)

func (mr *MatchedRule) Message() string {
return mr.Message_
}
Expand Down Expand Up @@ -142,6 +149,35 @@ func (mr *MatchedRule) Rule() types.RuleMetadata {
return mr.Rule_
}

// Context returns the context associated with the transaction
// This is useful for logging purposes where you want to add
// additional information to the log.
// The context can be easily retrieved in the logger using
// an ancillary interface:
// ```
//
// type Contexter interface {
// Context() context.Context
// }
//
// ```
// and then using it like this:
//
// ```
//
// func errorLogCb(mr types.MatchedRule) {
// ctx := context.Background()
// if ctxer, ok := mr.(Contexter); ok {
// ctx = ctxer.Context()
// }
// logger.Context(ctx).Error().Msg("...")
// }
//
// ```
func (mr *MatchedRule) Context() context.Context {
return mr.Context_
}

const maxSizeLogMessage = 200

func (mr MatchedRule) writeDetails(log *strings.Builder, matchData types.MatchData) {
Expand Down
5 changes: 5 additions & 0 deletions internal/corazawaf/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package corazawaf

import (
"bufio"
"context"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -42,6 +43,9 @@ type Transaction struct {
// Transaction ID
id string

// The context associated to the transaction.
context context.Context

// Contains the list of matched rules and associated match information
matchedRules []types.MatchedRule

Expand Down Expand Up @@ -501,6 +505,7 @@ func (tx *Transaction) MatchRule(r *Rule, mds []types.MatchData) {
Rule_: &r.RuleMetadata,
Log_: r.Log,
MatchedDatas_: mds,
Context_: tx.context,
}
// Populate MatchedRule disruption related fields only if the Engine is capable of performing disruptive actions
if tx.RuleEngine == types.RuleEngineOn {
Expand Down
34 changes: 25 additions & 9 deletions internal/corazawaf/waf.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
package corazawaf

import (
"context"
"errors"
"fmt"
"io"
"io/fs"
"os"
"regexp"
"strconv"
"strings"
"time"

"github.com/corazawaf/coraza/v3/debuglog"
Expand Down Expand Up @@ -133,24 +133,40 @@ type WAF struct {
ArgumentLimit int
}

// Options is used to pass options to the WAF instance
type Options struct {
ID string
Context context.Context
}

// NewTransaction Creates a new initialized transaction for this WAF instance
func (w *WAF) NewTransaction() *Transaction {
return w.newTransactionWithID(stringutils.RandomString(19))
return w.newTransaction(Options{
ID: stringutils.RandomString(19),
Context: context.Background(),
})
}

func (w *WAF) NewTransactionWithID(id string) *Transaction {
if len(strings.TrimSpace(id)) == 0 {
id = stringutils.RandomString(19)
w.Logger.Warn().Msg("Empty ID passed for new transaction")
// NewTransactionWithOptions Creates a new initialized transaction for this WAF
// instance with the provided options
func (w *WAF) NewTransactionWithOptions(opts Options) *Transaction {
if opts.ID == "" {
opts.ID = stringutils.RandomString(19)
}
return w.newTransactionWithID(id)

if opts.Context == nil {
opts.Context = context.Background()
}

return w.newTransaction(opts)
}

// NewTransactionWithID Creates a new initialized transaction for this WAF instance
// Using the specified ID
func (w *WAF) newTransactionWithID(id string) *Transaction {
func (w *WAF) newTransaction(opts Options) *Transaction {
tx := w.txPool.Get().(*Transaction)
tx.id = id
tx.id = opts.ID
tx.context = opts.Context
tx.matchedRules = []types.MatchedRule{}
tx.interruption = nil
tx.Logdata = "" // Deprecated, this variable is not used. Logdata for each matched rule is stored in the MatchData field.
Expand Down
4 changes: 2 additions & 2 deletions internal/corazawaf/waf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func TestNewTransaction(t *testing.T) {
waf.ResponseBodyAccess = true
waf.RequestBodyLimit = 1044

tx := waf.NewTransactionWithID("test")
tx := waf.NewTransactionWithOptions(Options{ID: "test"})
if !tx.RequestBodyAccess {
t.Error("Request body access not enabled")
}
Expand All @@ -28,7 +28,7 @@ func TestNewTransaction(t *testing.T) {
if tx.id != "test" {
t.Error("ID not set")
}
tx = waf.NewTransactionWithID("")
tx = waf.NewTransactionWithOptions(Options{ID: ""})
if tx.id == "" {
t.Error("ID not set")
}
Expand Down
1 change: 1 addition & 0 deletions types/rule_match.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ type MatchedRule interface {
Rule() RuleMetadata

AuditLog() string

ErrorLog() string
}
15 changes: 14 additions & 1 deletion waf.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
package coraza

import (
"context"
"fmt"
"strings"

"github.com/corazawaf/coraza/v3/experimental"
"github.com/corazawaf/coraza/v3/internal/corazawaf"
"github.com/corazawaf/coraza/v3/internal/seclang"
"github.com/corazawaf/coraza/v3/types"
Expand Down Expand Up @@ -129,5 +132,15 @@ func (w wafWrapper) NewTransaction() types.Transaction {

// NewTransactionWithID implements the same method on WAF.
func (w wafWrapper) NewTransactionWithID(id string) types.Transaction {
return w.waf.NewTransactionWithID(id)
id = strings.TrimSpace(id)
if len(id) == 0 {
w.waf.Logger.Warn().Msg("Empty ID passed for new transaction")
}

return w.waf.NewTransactionWithOptions(corazawaf.Options{Context: context.Background(), ID: id})
}

// NewTransaction implements the same method on WAF.
func (w wafWrapper) NewTransactionWithOptions(opts experimental.Options) types.Transaction {
return w.waf.NewTransactionWithOptions(opts)
}

0 comments on commit 2f18ba0

Please sign in to comment.