/
http.go
245 lines (215 loc) · 9.04 KB
/
http.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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// 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 Datadog, Inc.
// Package httpsec defines is the HTTP instrumentation API and contract for
// AppSec. It defines an abstract representation of HTTP handlers, along with
// helper functions to wrap (aka. instrument) standard net/http handlers.
// HTTP integrations must use this package to enable AppSec features for HTTP,
// which listens to this package's operation events.
package httpsec
import (
"encoding/json"
"net"
"net/http"
"reflect"
"strings"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo"
)
// Abstract HTTP handler operation definition.
type (
// HandlerOperationArgs is the HTTP handler operation arguments.
HandlerOperationArgs struct {
// RequestURI corresponds to the address `server.request.uri.raw`
RequestURI string
// Headers corresponds to the address `server.request.headers.no_cookies`
Headers map[string][]string
// Cookies corresponds to the address `server.request.cookies`
Cookies []string
// Query corresponds to the address `server.request.query`
Query map[string][]string
// PathParams corresponds to the address `server.request.path_params`
PathParams map[string]string
}
// HandlerOperationRes is the HTTP handler operation results.
HandlerOperationRes struct {
// Status corresponds to the address `server.response.status`.
Status int
}
SDKBodyOperationArgs struct {
Body interface{}
}
SDKBodyOperationRes struct {
}
)
// map used to keep track of ongoing operations per span. This is used as a
// means to keep track of parent operations when creating new operations.
// An example where this comes in handy is when users use an SDK function to
// instrument a specific address which results in a new operation start/finish.
var spanOpMap = make(map[ddtrace.Span]dyngo.Operation)
func MonitorParsedBody(span ddtrace.Span, body interface{}) {
op := StartSDKBodyOperation(SDKBodyOperationArgs{Body: body}, span)
op.Finish(span)
}
// WrapHandler wraps the given HTTP handler with the abstract HTTP operation defined by HandlerOperationArgs and
// HandlerOperationRes.
func WrapHandler(handler http.Handler, span ddtrace.Span, pathParams map[string]string) http.Handler {
SetAppSecTags(span)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
args := MakeHandlerOperationArgs(r, pathParams)
op := StartOperation(args, span)
defer func() {
var status int
if mw, ok := w.(interface{ Status() int }); ok {
status = mw.Status()
}
events := op.Finish(HandlerOperationRes{Status: status}, span)
if len(events) == 0 {
return
}
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
remoteIP = r.RemoteAddr
}
SetSecurityEventTags(span, events, remoteIP, args.Headers, w.Header())
}()
handler.ServeHTTP(w, r)
})
}
// MakeHandlerOperationArgs creates the HandlerOperationArgs out of a standard
// http.Request along with the given current span. It returns an empty structure
// when appsec is disabled.
func MakeHandlerOperationArgs(r *http.Request, pathParams map[string]string) HandlerOperationArgs {
headers := make(http.Header, len(r.Header))
var cookies []string
for k, v := range r.Header {
k := strings.ToLower(k)
if k == "cookie" {
// Do not include cookies in the request headers
cookies = v
continue
}
headers[k] = v
}
headers["host"] = []string{r.Host}
return HandlerOperationArgs{
RequestURI: r.RequestURI,
Headers: headers,
Cookies: cookies,
// TODO(Julio-Guerra): avoid actively parsing the query string and move to a lazy monitoring of this value with
// the dynamic instrumentation of the Query() method.
Query: r.URL.Query(),
PathParams: pathParams,
}
}
// TODO(Julio-Guerra): create a go-generate tool to generate the types, vars and methods below
// Operation type representing an HTTP operation. It must be created with
// StartOperation() and finished with its Finish().
type (
Operation struct {
dyngo.Operation
events json.RawMessage
}
SDKBodyOperation struct {
dyngo.Operation
events json.RawMessage
}
)
// StartOperation starts an HTTP handler operation, along with the given
// arguments and current span, emits a start event up in the operation stack
// and update the span operation map.
// When parent is nil, the operation is linked to the global root operation.
func StartOperation(args HandlerOperationArgs, span ddtrace.Span) *Operation {
op := &Operation{Operation: dyngo.NewOperation(spanOpMap[span])}
spanOpMap[span] = op
dyngo.StartOperation(op, args)
return op
}
// Finish the HTTP handler operation, along with the given results, emits a
// finish event up in the operation stack and update the span operation map.
func (op *Operation) Finish(res HandlerOperationRes, span ddtrace.Span) json.RawMessage {
dyngo.FinishOperation(op, res)
if parent := op.Parent(); parent != nil {
spanOpMap[span] = parent
} else {
delete(spanOpMap, span)
}
return op.events
}
// StartSDKBodyOperation starts the SDKBody operation, emits a start event and
// update the span operation map.
func StartSDKBodyOperation(args SDKBodyOperationArgs, span ddtrace.Span) *SDKBodyOperation {
op := &SDKBodyOperation{Operation: dyngo.NewOperation(spanOpMap[span])}
spanOpMap[span] = op
dyngo.StartOperation(op, args)
return op
}
// Finish the SDKBody operation, emits a finish event and update the span
// operation map
func (op *SDKBodyOperation) Finish(span ddtrace.Span) json.RawMessage {
dyngo.FinishOperation(op, SDKBodyOperationRes{})
spanOpMap[span] = op.Parent()
return op.events
}
// AddSecurityEvent adds the security event to the list of events observed
// during the operation lifetime.
func (op *Operation) AddSecurityEvent(event json.RawMessage) {
// TODO(Julio-Guerra): the current situation involves only one event per
// operation. In the future, multiple events per operation will become
// possible and the append operation should be made thread-safe.
op.events = event
}
// HTTP handler operation's start and finish event callback function types.
type (
// OnHandlerOperationStart function type, called when an HTTP handler
// operation starts.
OnHandlerOperationStart func(*Operation, HandlerOperationArgs)
// OnHandlerOperationFinish function type, called when an HTTP handler
// operation finishes.
OnHandlerOperationFinish func(*Operation, HandlerOperationRes)
// OnSDKBodyOperationStart function type, called when an SDK body
// operation starts.
OnSDKBodyOperationStart func(*SDKBodyOperation, SDKBodyOperationArgs)
// OnSDKBodyOperationFinishfunction type, called when an SDK body
// operation finishes.
OnSDKBodyOperationFinish func(*SDKBodyOperation, SDKBodyOperationRes)
)
var (
handlerOperationArgsType = reflect.TypeOf((*HandlerOperationArgs)(nil)).Elem()
handlerOperationResType = reflect.TypeOf((*HandlerOperationRes)(nil)).Elem()
sdkBodyOperationArgsType = reflect.TypeOf((*SDKBodyOperationArgs)(nil)).Elem()
sdkBodyOperationResType = reflect.TypeOf((*SDKBodyOperationRes)(nil)).Elem()
)
// ListenedType returns the type a OnHandlerOperationStart event listener
// listens to, which is the HandlerOperationArgs type.
func (OnHandlerOperationStart) ListenedType() reflect.Type { return handlerOperationArgsType }
// Call the underlying event listener function by performing the type-assertion
// on v whose type is the one returned by ListenedType().
func (f OnHandlerOperationStart) Call(op dyngo.Operation, v interface{}) {
f(op.(*Operation), v.(HandlerOperationArgs))
}
// ListenedType returns the type a OnHandlerOperationFinish event listener
// listens to, which is the HandlerOperationRes type.
func (OnHandlerOperationFinish) ListenedType() reflect.Type { return handlerOperationResType }
// Call the underlying event listener function by performing the type-assertion
// on v whose type is the one returned by ListenedType().
func (f OnHandlerOperationFinish) Call(op dyngo.Operation, v interface{}) {
f(op.(*Operation), v.(HandlerOperationRes))
}
// ListenedType returns the type a OnSDKBodyOperationStart event listener
// listens to, which is the SDKBodyOperationStartArgs type.
func (OnSDKBodyOperationStart) ListenedType() reflect.Type { return sdkBodyOperationArgsType }
// Call the underlying event listener function by performing the type-assertion
// on v whose type is the one returned by ListenedType().
func (f OnSDKBodyOperationStart) Call(op dyngo.Operation, v interface{}) {
f(op.(*SDKBodyOperation), v.(SDKBodyOperationArgs))
}
// ListenedType returns the type a OnSDKBodyOperationFinish event listener
// listens to, which is the SDKBodyOperationRes type.
func (OnSDKBodyOperationFinish) ListenedType() reflect.Type { return sdkBodyOperationResType }
// Call the underlying event listener function by performing the type-assertion
// on v whose type is the one returned by ListenedType().
func (f OnSDKBodyOperationFinish) Call(op dyngo.Operation, v interface{}) {
f(op.(*SDKBodyOperation), v.(SDKBodyOperationRes))
}