-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathwaf.go
186 lines (160 loc) · 6.9 KB
/
waf.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// 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 (
"errors"
"fmt"
"sync"
"time"
wafErrors "github.com/DataDog/go-libddwaf/v3/errors"
"github.com/DataDog/go-libddwaf/v3/internal/bindings"
"github.com/DataDog/go-libddwaf/v3/internal/support"
)
// ErrTimeout is the error returned when the WAF times out while processing a request.
// Deprecated: use github.com/DataDog/go-libddwaf/errors.ErrTimeout instead.
const ErrTimeout = wafErrors.ErrTimeout
// Diagnostics stores the information - provided by the WAF - about WAF rules initialization.
type Diagnostics struct {
Rules *DiagnosticEntry
CustomRules *DiagnosticEntry
Actions *DiagnosticEntry
Exclusions *DiagnosticEntry
RulesOverrides *DiagnosticEntry
RulesData *DiagnosticEntry
ExclusionData *DiagnosticEntry
Processors *DiagnosticEntry
Scanners *DiagnosticEntry
Version string
}
// TopLevelError returns the list of top-level errors reported by the WAF on any of the Diagnostics
// entries, rolled up into a single error value. Returns nil if no top-level errors were reported.
// Individual, item-level errors might still exist.
func (d *Diagnostics) TopLevelError() error {
fields := map[string]*DiagnosticEntry{
"rules": d.Rules,
"actions": d.Actions,
"custom_rules": d.CustomRules,
"exclusions": d.Exclusions,
"rules_override": d.RulesOverrides,
"rules_data": d.RulesData,
"exclusion_data": d.ExclusionData,
"processors": d.Processors,
"scanners": d.Scanners,
}
var errs []error
for field, entry := range fields {
if entry == nil || entry.Error == "" {
// No entry or no error => we're all good.
continue
}
errs = append(errs, fmt.Errorf("in %#v: %s", field, entry.Error))
}
return errors.Join(errs...)
}
// DiagnosticEntry stores the information - provided by the WAF - about loaded and failed rules
// for a specific entry in the WAF ruleset
type DiagnosticEntry struct {
Addresses *DiagnosticAddresses
Errors map[string][]string // Item-level errors (map of error message to entity identifiers or index:#)
Error string // If the entire entry was in error (e.g: invalid format)
Loaded []string // Successfully loaded entity identifiers (or index:#)
Failed []string // Failed entity identifiers (or index:#)
Skipped []string // Skipped entity identifiers (or index:#)
}
// DiagnosticAddresses stores the information - provided by the WAF - about the known addresses and
// whether they are required or optional. Addresses used by WAF rules are always required. Addresses
// used by WAF exclusion filters may be required or (rarely) optional. Addresses used by WAF
// processors may be required or optional.
type DiagnosticAddresses struct {
Required []string
Optional []string
}
// Result stores the multiple values returned by a call to ddwaf_run
type Result struct {
// Events is the list of events the WAF detected, together with any relevant
// details.
Events []any
// Derivatives is the set of key-value pairs generated by the WAF, and which
// need to be reported on the trace to provide additional data to the backend.
Derivatives map[string]any
// Actions is the set of actions the WAF decided on when evaluating rules
// against the provided address data. It maps action types to their dynamic parameter values
Actions map[string]any
// TimeSpent is the time the WAF self-reported as spent processing the call to ddwaf_run
TimeSpent time.Duration
}
// Globally dlopen() libddwaf only once because several dlopens (eg. in tests)
// aren't supported by macOS.
var (
// libddwaf's dynamic library handle and entrypoints
wafLib *bindings.WafDl
// libddwaf's dlopen error if any
wafLoadErr error
openWafOnce sync.Once
)
// Load loads libddwaf's dynamic library. The dynamic library is opened only
// once by the first call to this function and internally stored globally, and
// no function is currently provided in this API to close the opened handle.
// Calling this function is not mandatory and is automatically performed by
// calls to NewHandle, the entrypoint of libddwaf, but Load is useful in order
// to explicitly check libddwaf's general health where calling NewHandle doesn't
// necessarily apply nor is doable.
// The function returns ok when libddwaf was successfully loaded, along with a
// non-nil error if any. Note that both ok and err can be set, meaning that
// libddwaf is usable but some non-critical errors happened, such as failures
// to remove temporary files. It is safe to continue using libddwaf in such
// case.
func Load() (ok bool, err error) {
if ok, err = Health(); !ok {
return false, err
}
openWafOnce.Do(func() {
wafLib, wafLoadErr = bindings.NewWafDl()
if wafLoadErr != nil {
return
}
wafVersion = wafLib.WafGetVersion()
})
return wafLib != nil, wafLoadErr
}
var wafVersion string
// Version returns the version returned by libddwaf.
// It relies on the dynamic loading of the library, which can fail and return
// an empty string or the previously loaded version, if any.
func Version() string {
Load()
return wafVersion
}
// HasEvents return true if the result holds at least 1 event
func (r *Result) HasEvents() bool {
return len(r.Events) > 0
}
// HasDerivatives return true if the result holds at least 1 derivative
func (r *Result) HasDerivatives() bool {
return len(r.Derivatives) > 0
}
// HasActions return true if the result holds at least 1 action
func (r *Result) HasActions() bool {
return len(r.Actions) > 0
}
// SupportsTarget returns true and a nil error when the target host environment
// is supported by this package and can be further used.
// Otherwise, it returns false along with an error detailing why.
func SupportsTarget() (bool, error) {
wafSupportErrors := support.WafSupportErrors()
return wafSupportErrors == nil, errors.Join(wafSupportErrors...)
}
// Health returns true if the waf is usable, false otherwise. At the same time it can return an error
// if the waf is not usable, but the error is not blocking if true is returned, otherwise it is.
// The following conditions are checked:
// - The Waf library has been loaded successfully (you need to call `Load()` first for this case to be taken into account)
// - The Waf library has not been manually disabled with the `datadog.no_waf` go build tag
// - The Waf library is not in an unsupported OS/Arch
// - The Waf library is not in an unsupported Go version
func Health() (bool, error) {
wafSupportErrors := errors.Join(support.WafSupportErrors()...)
wafManuallyDisabledErr := support.WafManuallyDisabledError()
return (wafLib != nil || wafLoadErr == nil) && wafSupportErrors == nil && wafManuallyDisabledErr == nil, errors.Join(wafLoadErr, wafSupportErrors, wafManuallyDisabledErr)
}