Skip to content

Commit

Permalink
feat: decode all fields from WAF diagnostics (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
RomainMuller authored Nov 8, 2023
1 parent 643bed9 commit f4a6958
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 139 deletions.
14 changes: 14 additions & 0 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,20 @@ func decodeDiagnostics(obj *wafObject) (*Diagnostics, error) {
objElem := castWithOffset[wafObject](obj.value, i)
key := gostringSized(cast[byte](objElem.parameterName), objElem.parameterNameLength)
switch key {
case "custom_rules":
diags.CustomRules, err = decodeDiagnosticsEntry(objElem)
case "exclusions":
diags.Exclusions, err = decodeDiagnosticsEntry(objElem)
case "rules":
diags.Rules, err = decodeDiagnosticsEntry(objElem)
case "rules_data":
diags.RulesData, err = decodeDiagnosticsEntry(objElem)
case "rules_override":
diags.RulesOverrides, err = decodeDiagnosticsEntry(objElem)
case "processors":
diags.Processors, err = decodeDiagnosticsEntry(objElem)
case "scanners":
diags.Scanners, err = decodeDiagnosticsEntry(objElem)
case "ruleset_version":
diags.Version = gostringSized(cast[byte](objElem.value), objElem.nbEntries)
default:
Expand Down Expand Up @@ -80,6 +92,8 @@ func decodeDiagnosticsEntry(obj *wafObject) (*DiagnosticEntry, error) {
switch key {
case "addresses":
entry.Addresses, err = decodeDiagnosticAddresses(objElem)
case "error":
entry.Error = gostringSized(cast[byte](objElem.value), objElem.nbEntries)
case "errors":
entry.Errors, err = decodeErrors(objElem)
case "failed":
Expand Down
150 changes: 150 additions & 0 deletions decoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

package waf

import (
"reflect"
"testing"

"github.com/stretchr/testify/require"
)

// This test needs a working encoder to function properly, as it first encodes the objects before decoding them
func TestDecoder(t *testing.T) {
t.Run("Errors", func(t *testing.T) {
e := newMaxEncoder()
objBuilder := func(value any) *wafObject {
encoded, err := e.Encode(value)
require.NoError(t, err, "Encoding object failed")
return encoded
}

for _, tc := range []struct {
Name string
ExpectedValue map[string][]string
}{
{
Name: "empty",
ExpectedValue: map[string][]string{},
},
{
Name: "one-empty-entry",
ExpectedValue: map[string][]string{
"afasdfafs": nil,
},
},
{
Name: "one-filled-entry",
ExpectedValue: map[string][]string{
"afasdfafs": {"rule1", "rule2"},
},
},
{
Name: "multiple-entries",
ExpectedValue: map[string][]string{
"afasdfafs": {"rule1", "rule2"},
"sfdsafdsa": {"rule3", "rule4"},
},
},
} {
t.Run(tc.Name, func(t *testing.T) {
val, err := decodeErrors(objBuilder(tc.ExpectedValue))
require.NoErrorf(t, err, "Error decoding the object: %v", err)
require.Equal(t, tc.ExpectedValue, val)
})
}

keepAlive(e.cgoRefs.arrayRefs)
keepAlive(e.cgoRefs.stringRefs)
})
t.Run("Values", func(t *testing.T) {

for _, tc := range []struct {
name string
data any
}{
{
name: "int64",
data: int64(-4),
},
{
name: "uint64",
data: uint64(4),
},
{
name: "float64",
data: 4.4444,
},
{
name: "bool",
data: true,
},
{
name: "bool",
data: false,
},
{
name: "string",
data: "",
},
{
name: "string",
data: "EliottAndFrancoisLoveDecoding",
},
{
name: "string",
data: "123",
},
{
name: "slice",
data: []any{int64(1), int64(2), int64(3), int64(4)},
},
{
name: "slice",
data: []any{"1", "2", "3", "4"},
},
{
name: "slice",
data: []any{true, false, false, true, true, true},
},
{
name: "slice-nested",
data: []any{[]any{true, false}, []any{false, false}, []any{true, true, true}},
},
{
name: "map",
data: map[string]any{"1": int64(1), "2": int64(2), "3": int64(3)},
},
{
name: "map",
data: map[string]any{"1": uint64(1), "2": uint64(2), "3": uint64(3)},
},
{
name: "map",
data: map[string]any{"1": 1.111, "2": 2.222, "3": 3.333},
},
{
name: "map",
data: map[string]any{"1": "1", "2": "2", "3": "3"},
},
{
name: "map-nested",
data: map[string]any{"1": map[string]any{"1": int64(1), "2": int64(2)}, "2": map[string]any{"3": int64(3), "4": int64(4)}},
},
} {
t.Run(tc.name, func(t *testing.T) {
e := newMaxEncoder()
obj, err := e.Encode(tc.data)
require.NoError(t, err)

val, err := decodeObject(obj)
require.NoError(t, err)
require.True(t, reflect.DeepEqual(tc.data, val))
})
}

})
}
5 changes: 4 additions & 1 deletion waf.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Diagnostics struct {
Exclusions *DiagnosticEntry
RulesOverrides *DiagnosticEntry
RulesData *DiagnosticEntry
Processors *DiagnosticEntry
Scanners *DiagnosticEntry
}

// DiagnosticEntry stores the information - provided by the WAF - about loaded and failed rules
Expand All @@ -39,7 +41,8 @@ type DiagnosticEntry struct {
Addresses DiagnosticAddresses
Loaded []string
Failed []string
Errors map[string][]string
Error string // If the entire entry was in error (e.g: invalid format)
Errors map[string][]string // Item-level errors
}

// DiagnosticAddresses stores the information - provided by the WAF - about the known addresses and
Expand Down
138 changes: 0 additions & 138 deletions waf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"encoding/json"
"fmt"
"math/rand"
"reflect"
"sort"
"sync"
"testing"
Expand Down Expand Up @@ -907,143 +906,6 @@ func TestMetrics(t *testing.T) {
})
}

// This test needs a working encoder to function properly, as it first encodes the objects before decoding them
func TestDecoder(t *testing.T) {
t.Run("Errors", func(t *testing.T) {
e := newMaxEncoder()
objBuilder := func(value any) *wafObject {
encoded, err := e.Encode(value)
require.NoError(t, err, "Encoding object failed")
return encoded
}

for _, tc := range []struct {
Name string
ExpectedValue map[string][]string
}{
{
Name: "empty",
ExpectedValue: map[string][]string{},
},
{
Name: "one-empty-entry",
ExpectedValue: map[string][]string{
"afasdfafs": nil,
},
},
{
Name: "one-filled-entry",
ExpectedValue: map[string][]string{
"afasdfafs": {"rule1", "rule2"},
},
},
{
Name: "multiple-entries",
ExpectedValue: map[string][]string{
"afasdfafs": {"rule1", "rule2"},
"sfdsafdsa": {"rule3", "rule4"},
},
},
} {
t.Run(tc.Name, func(t *testing.T) {
val, err := decodeErrors(objBuilder(tc.ExpectedValue))
require.NoErrorf(t, err, "Error decoding the object: %v", err)
require.Equal(t, tc.ExpectedValue, val)
})
}

keepAlive(e.cgoRefs.arrayRefs)
keepAlive(e.cgoRefs.stringRefs)
})
t.Run("Values", func(t *testing.T) {

for _, tc := range []struct {
name string
data any
}{
{
name: "int64",
data: int64(-4),
},
{
name: "uint64",
data: uint64(4),
},
{
name: "float64",
data: 4.4444,
},
{
name: "bool",
data: true,
},
{
name: "bool",
data: false,
},
{
name: "string",
data: "",
},
{
name: "string",
data: "EliottAndFrancoisLoveDecoding",
},
{
name: "string",
data: "123",
},
{
name: "slice",
data: []any{int64(1), int64(2), int64(3), int64(4)},
},
{
name: "slice",
data: []any{"1", "2", "3", "4"},
},
{
name: "slice",
data: []any{true, false, false, true, true, true},
},
{
name: "slice-nested",
data: []any{[]any{true, false}, []any{false, false}, []any{true, true, true}},
},
{
name: "map",
data: map[string]any{"1": int64(1), "2": int64(2), "3": int64(3)},
},
{
name: "map",
data: map[string]any{"1": uint64(1), "2": uint64(2), "3": uint64(3)},
},
{
name: "map",
data: map[string]any{"1": 1.111, "2": 2.222, "3": 3.333},
},
{
name: "map",
data: map[string]any{"1": "1", "2": "2", "3": "3"},
},
{
name: "map-nested",
data: map[string]any{"1": map[string]any{"1": int64(1), "2": int64(2)}, "2": map[string]any{"3": int64(3), "4": int64(4)}},
},
} {
t.Run(tc.name, func(t *testing.T) {
e := newMaxEncoder()
obj, err := e.Encode(tc.data)
require.NoError(t, err)

val, err := decodeObject(obj)
require.NoError(t, err)
require.True(t, reflect.DeepEqual(tc.data, val))
})
}

})
}

func TestObfuscatorConfig(t *testing.T) {
rule := newArachniTestRule([]ruleInput{{Address: "my.addr", KeyPath: []string{"key"}}}, nil)
t.Run("key", func(t *testing.T) {
Expand Down

0 comments on commit f4a6958

Please sign in to comment.