Skip to content

Commit 6ffded8

Browse files
authored
Added config option to extract raw request Header (#35)
Replaced custom header extractor config option with raw header extractor Simplify code
1 parent 9c9764c commit 6ffded8

File tree

6 files changed

+86
-173
lines changed

6 files changed

+86
-173
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Golang Module Release Notes
22

3+
## 1.12.0 2023-01-10
4+
5+
* Replaced internal custom header extractor function with raw header extractor function
6+
37
## 1.11.0 2022-01-18
48

59
* Improved `Content-Type` header inspection

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.11.0
1+
1.12.0

config.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ var (
3838
DefaultServerFlavor = ""
3939
)
4040

41-
// HeaderExtractorFunc is a header extraction function
42-
type HeaderExtractorFunc func(*http.Request) (http.Header, error)
41+
// RawHeaderExtractorFunc is a header extraction function
42+
type RawHeaderExtractorFunc func(*http.Request) [][2]string
4343

4444
// ModuleConfig is a configuration object for a Module
4545
type ModuleConfig struct {
@@ -48,7 +48,7 @@ type ModuleConfig struct {
4848
anomalySize int64
4949
expectedContentTypes []string
5050
debug bool
51-
headerExtractor HeaderExtractorFunc
51+
rawHeaderExtractor RawHeaderExtractorFunc
5252
inspector Inspector
5353
inspInit InspectorInitFunc
5454
inspFini InspectorFiniFunc
@@ -69,7 +69,6 @@ func NewModuleConfig(options ...ModuleConfigOption) (*ModuleConfig, error) {
6969
anomalySize: DefaultAnomalySize,
7070
expectedContentTypes: make([]string, 0),
7171
debug: DefaultDebug,
72-
headerExtractor: nil,
7372
inspector: DefaultInspector,
7473
inspInit: nil,
7574
inspFini: nil,
@@ -158,9 +157,9 @@ func (c *ModuleConfig) Debug() bool {
158157
return c.debug
159158
}
160159

161-
// HeaderExtractor returns the configuration value
162-
func (c *ModuleConfig) HeaderExtractor() func(r *http.Request) (http.Header, error) {
163-
return c.headerExtractor
160+
// RawHeaderExtractor returns the configuration value
161+
func (c *ModuleConfig) RawHeaderExtractor() RawHeaderExtractorFunc {
162+
return c.rawHeaderExtractor
164163
}
165164

166165
// Inspector returns the inspector
@@ -228,10 +227,11 @@ type ModuleConfigOption func(*ModuleConfig) error
228227
// to read the body when the content length is not specified.
229228
//
230229
// NOTE: This can be dangerous (fill RAM) if set when the max content
231-
// length is not limited by the server itself. This is intended
232-
// for use with gRPC where the max message receive length is limited.
233-
// Do NOT enable this if there is no limit set on the request
234-
// content length!
230+
//
231+
// length is not limited by the server itself. This is intended
232+
// for use with gRPC where the max message receive length is limited.
233+
// Do NOT enable this if there is no limit set on the request
234+
// content length!
235235
func AllowUnknownContentLength(allow bool) ModuleConfigOption {
236236
return func(c *ModuleConfig) error {
237237
c.allowUnknownContentLength = allow
@@ -290,12 +290,12 @@ func CustomInspector(insp Inspector, init InspectorInitFunc, fini InspectorFiniF
290290
}
291291
}
292292

293-
// CustomHeaderExtractor is a function argument that sets a function to extract
293+
// RawHeaderExtractor is a function argument that sets a function to extract
294294
// an alternative header object from the request. It is primarily intended only
295295
// for internal use.
296-
func CustomHeaderExtractor(fn func(r *http.Request) (http.Header, error)) ModuleConfigOption {
296+
func RawHeaderExtractor(fn RawHeaderExtractorFunc) ModuleConfigOption {
297297
return func(c *ModuleConfig) error {
298-
c.headerExtractor = fn
298+
c.rawHeaderExtractor = fn
299299
return nil
300300
}
301301
}

config_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ func TestDefaultModuleConfig(t *testing.T) {
2929
if c.Debug() != DefaultDebug {
3030
t.Errorf("Unexpected Debug: %v", c.Debug())
3131
}
32-
if c.HeaderExtractor() != nil {
33-
t.Errorf("Unexpected HeaderExtractor: %p", c.HeaderExtractor())
32+
if c.RawHeaderExtractor() != nil {
33+
t.Errorf("Unexpected RawHeaderExtractor: %p", c.RawHeaderExtractor())
3434
}
3535
if c.Inspector() != DefaultInspector {
3636
t.Errorf("Unexpected Inspector: %v", c.Inspector())
@@ -90,7 +90,7 @@ func TestConfiguredModuleConfig(t *testing.T) {
9090
AnomalyDuration(10*time.Second),
9191
AnomalySize(8192),
9292
CustomInspector(&RPCInspector{}, func(_ *http.Request) bool { return true }, func(_ *http.Request) {}),
93-
CustomHeaderExtractor(func(_ *http.Request) (http.Header, error) { return nil, nil }),
93+
RawHeaderExtractor(func(_ *http.Request) [][2]string { return nil }),
9494
ExpectedContentType("application/foobar"),
9595
ExpectedContentType("application/fizzbuzz"),
9696
Debug(true),
@@ -119,8 +119,8 @@ func TestConfiguredModuleConfig(t *testing.T) {
119119
if c.Debug() != true {
120120
t.Errorf("Unexpected Debug: %v", c.Debug())
121121
}
122-
if c.HeaderExtractor() == nil {
123-
t.Errorf("Unexpected HeaderExtractor: %p", c.HeaderExtractor())
122+
if c.RawHeaderExtractor() == nil {
123+
t.Errorf("Unexpected HeaderExtractor: %p", c.RawHeaderExtractor())
124124
}
125125
if c.Inspector() == DefaultInspector {
126126
t.Errorf("Unexpected Inspector: %v", c.Inspector())
@@ -182,7 +182,7 @@ func TestFromModuleConfig(t *testing.T) {
182182
ExpectedContentType("application/foobar"),
183183
ExpectedContentType("application/fizzbuzz"),
184184
CustomInspector(&RPCInspector{}, func(_ *http.Request) bool { return true }, func(_ *http.Request) {}),
185-
CustomHeaderExtractor(func(_ *http.Request) (http.Header, error) { return nil, nil }),
185+
RawHeaderExtractor(func(_ *http.Request) [][2]string { return nil }),
186186
Debug(true),
187187
MaxContentLength(500000),
188188
Socket("tcp", "0.0.0.0:1234"),
@@ -216,8 +216,8 @@ func TestFromModuleConfig(t *testing.T) {
216216
if c.Debug() != true {
217217
t.Errorf("Unexpected Debug: %v", c.Debug())
218218
}
219-
if c.HeaderExtractor() == nil {
220-
t.Errorf("Unexpected HeaderExtractor: %p", c.HeaderExtractor())
219+
if c.RawHeaderExtractor() == nil {
220+
t.Errorf("Unexpected HeaderExtractor: %p", c.RawHeaderExtractor())
221221
}
222222
if c.Inspector() == DefaultInspector {
223223
t.Errorf("Unexpected Inspector: %v", c.Inspector())

module.go

Lines changed: 45 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,11 @@ import (
1919
// data collection and sends it to the Signal Sciences Agent for
2020
// inspection.
2121
type Module struct {
22-
config *ModuleConfig
23-
handler http.Handler
24-
inspector Inspector
25-
inspInit InspectorInitFunc
26-
inspFini InspectorFiniFunc
27-
headerExtractor HeaderExtractorFunc
22+
config *ModuleConfig
23+
handler http.Handler
24+
inspector Inspector
25+
inspInit InspectorInitFunc
26+
inspFini InspectorFiniFunc
2827
}
2928

3029
// NewModule wraps an existing http.Handler with one that extracts data and
@@ -39,12 +38,11 @@ func NewModule(h http.Handler, options ...ModuleConfigOption) (*Module, error) {
3938

4039
// The following are the defaults, overridden by passing in functional options
4140
m := Module{
42-
handler: h,
43-
config: config,
44-
inspector: config.Inspector(),
45-
inspInit: config.InspectorInit(),
46-
inspFini: config.InspectorFini(),
47-
headerExtractor: config.HeaderExtractor(),
41+
handler: h,
42+
config: config,
43+
inspector: config.Inspector(),
44+
inspInit: config.InspectorInit(),
45+
inspFini: config.InspectorFini(),
4846
}
4947

5048
// By default, use an RPC based inspector if not configured externally
@@ -169,8 +167,7 @@ func (m *Module) ServeHTTP(w http.ResponseWriter, req *http.Request) {
169167
if m.config.Debug() {
170168
log.Printf("DEBUG: calling 'RPC.PostRequest' due to anomaly: method=%s host=%s url=%s code=%d size=%d duration=%s", req.Method, req.Host, req.URL, code, size, duration)
171169
}
172-
inspin := NewRPCMsgIn(req, nil, code, size, duration, m.config.ModuleIdentifier(), m.config.ServerIdentifier())
173-
m.extractHeaders(req, inspin)
170+
inspin := NewRPCMsgIn(m.config, req, nil, code, size, duration)
174171
inspin.WAFResponse = wafresponse
175172
inspin.HeadersOut = convertHeaders(rw.Header())
176173

@@ -225,8 +222,7 @@ func (m *Module) inspectorPreRequest(req *http.Request) (inspin2 RPCMsgIn2, out
225222
req.Body = ioutil.NopCloser(bytes.NewBuffer(reqbody))
226223
}
227224

228-
inspin := NewRPCMsgInWithModuleConfig(m.config, req, reqbody)
229-
m.extractHeaders(req, inspin)
225+
inspin := NewRPCMsgIn(m.config, req, reqbody, -1, -1, 0)
230226

231227
if m.config.Debug() {
232228
log.Printf("DEBUG: Making PreRequest call to inspector: %s %s", inspin.Method, inspin.URI)
@@ -276,20 +272,6 @@ func (m *Module) inspectorPreRequest(req *http.Request) (inspin2 RPCMsgIn2, out
276272
return
277273
}
278274

279-
func (m *Module) extractHeaders(req *http.Request, inspin *RPCMsgIn) {
280-
// If the user supplied a custom header extractor, use it to unpack the
281-
// headers. If there no custom header extractor or it returns an error,
282-
// fallback to the native headers on the request.
283-
if m.headerExtractor != nil {
284-
hin, err := m.headerExtractor(req)
285-
if err == nil {
286-
inspin.HeadersIn = convertHeaders(hin)
287-
} else if m.config.Debug() {
288-
log.Printf("DEBUG: Error extracting custom headers, using native headers: %s", err)
289-
}
290-
}
291-
}
292-
293275
// inspectorPostRequest makes a postrequest call to the inspector
294276
func (m *Module) inspectorPostRequest(inspin *RPCMsgIn) error {
295277
// Create message to agent from the input request
@@ -329,93 +311,42 @@ func (m *Module) inspectorUpdateRequest(inspin RPCMsgIn2) error {
329311
// NewRPCMsgIn creates a message from a go http.Request object
330312
// End-users of the golang module never need to use this
331313
// directly and it is only exposed for performance testing
332-
func NewRPCMsgIn(r *http.Request, postbody []byte, code int, size int64, dur time.Duration, module, server string) *RPCMsgIn {
314+
func NewRPCMsgIn(mcfg *ModuleConfig, r *http.Request, postbody []byte, code int, size int64, dur time.Duration) *RPCMsgIn {
333315
now := time.Now()
334316

335-
// assemble a message to send to inspector
336-
tlsProtocol := ""
337-
tlsCipher := ""
338-
scheme := "http"
339-
if r.TLS != nil {
340-
// convert golang/spec integers into something human readable
341-
scheme = "https"
342-
tlsProtocol = tlstext.Version(r.TLS.Version)
343-
tlsCipher = tlstext.CipherSuite(r.TLS.CipherSuite)
344-
}
345-
346-
// golang removes Host header from req.Header map and
347-
// promotes it to r.Host field. Add it back as the first header.
348-
hin := convertHeaders(r.Header)
349-
if len(r.Host) > 0 {
350-
hin = append([][2]string{{"Host", r.Host}}, hin...)
351-
}
352-
353-
return &RPCMsgIn{
354-
ModuleVersion: module,
355-
ServerVersion: server,
317+
msgIn := RPCMsgIn{
318+
ModuleVersion: mcfg.ModuleIdentifier(),
319+
ServerVersion: mcfg.ServerIdentifier(),
320+
ServerFlavor: mcfg.ServerFlavor(),
356321
ServerName: r.Host,
357322
Timestamp: now.Unix(),
358-
NowMillis: now.UnixNano() / 1e6,
323+
NowMillis: now.UnixMilli(),
359324
RemoteAddr: stripPort(r.RemoteAddr),
360325
Method: r.Method,
361-
Scheme: scheme,
362326
URI: r.RequestURI,
363327
Protocol: r.Proto,
364-
TLSProtocol: tlsProtocol,
365-
TLSCipher: tlsCipher,
366328
ResponseCode: int32(code),
367-
ResponseMillis: int64(dur / time.Millisecond),
329+
ResponseMillis: dur.Milliseconds(),
368330
ResponseSize: size,
369331
PostBody: string(postbody),
370-
HeadersIn: hin,
371332
}
372-
}
373-
374-
// NewRPCMsgInWithModuleConfig creates a message from a ModuleConfig object
375-
// End-users of the golang module never need to use this
376-
// directly and it is only exposed for performance testing
377-
func NewRPCMsgInWithModuleConfig(mcfg *ModuleConfig, r *http.Request, postbody []byte) *RPCMsgIn {
378-
379-
now := time.Now()
380333

381-
// assemble a message to send to inspector
382-
tlsProtocol := ""
383-
tlsCipher := ""
384-
scheme := "http"
385334
if r.TLS != nil {
386335
// convert golang/spec integers into something human readable
387-
scheme = "https"
388-
tlsProtocol = tlstext.Version(r.TLS.Version)
389-
tlsCipher = tlstext.CipherSuite(r.TLS.CipherSuite)
336+
msgIn.Scheme = "https"
337+
msgIn.TLSProtocol = tlstext.Version(r.TLS.Version)
338+
msgIn.TLSCipher = tlstext.CipherSuite(r.TLS.CipherSuite)
339+
} else {
340+
msgIn.Scheme = "http"
390341
}
391342

392-
// golang removes Host header from req.Header map and
393-
// promotes it to r.Host field. Add it back as the first header.
394-
hin := convertHeaders(r.Header)
395-
if len(r.Host) > 0 {
396-
hin = append([][2]string{{"Host", r.Host}}, hin...)
343+
if hdrs := mcfg.RawHeaderExtractor(); hdrs != nil {
344+
msgIn.HeadersIn = hdrs(r)
397345
}
398-
399-
return &RPCMsgIn{
400-
ModuleVersion: mcfg.ModuleIdentifier(),
401-
ServerVersion: mcfg.ServerIdentifier(),
402-
ServerFlavor: mcfg.ServerFlavor(),
403-
ServerName: r.Host,
404-
Timestamp: now.Unix(),
405-
NowMillis: now.UnixNano() / 1e6,
406-
RemoteAddr: stripPort(r.RemoteAddr),
407-
Method: r.Method,
408-
Scheme: scheme,
409-
URI: r.RequestURI,
410-
Protocol: r.Proto,
411-
TLSProtocol: tlsProtocol,
412-
TLSCipher: tlsCipher,
413-
ResponseCode: -1,
414-
ResponseMillis: 0,
415-
ResponseSize: -1,
416-
PostBody: string(postbody),
417-
HeadersIn: hin,
346+
if msgIn.HeadersIn == nil {
347+
msgIn.HeadersIn = requestHeader(r)
418348
}
349+
return &msgIn
419350
}
420351

421352
// stripPort removes any port from an address (e.g., the client port from the RemoteAddr)
@@ -505,6 +436,22 @@ func inspectableContentType(s string) bool {
505436
return false
506437
}
507438

439+
// requestHeader returns request headers with host header
440+
func requestHeader(r *http.Request) [][2]string {
441+
out := make([][2]string, 0, len(r.Header)+1)
442+
// golang removes Host header from req.Header map and
443+
// promotes it to r.Host field. Add it back as the first header.
444+
if len(r.Host) > 0 {
445+
out = append(out, [2]string{"Host", r.Host})
446+
}
447+
for key, values := range r.Header {
448+
for _, value := range values {
449+
out = append(out, [2]string{key, value})
450+
}
451+
}
452+
return out
453+
}
454+
508455
// converts a http.Header map to a [][2]string
509456
func convertHeaders(h http.Header) [][2]string {
510457
// get headers

0 commit comments

Comments
 (0)