-
-
Notifications
You must be signed in to change notification settings - Fork 41
/
audit.go
135 lines (108 loc) · 3.01 KB
/
audit.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
package main
import (
"encoding/json"
"io"
"net/http"
"net/url"
"os"
"path"
"strings"
"sync"
"time"
"github.com/pkg/errors"
"github.com/Luzifer/go_helpers/v2/str"
)
type auditEvent string
const (
auditEventAccessDenied = "access_denied"
auditEventLoginFailure = "login_failure"
auditEventLoginSuccess auditEvent = "login_success"
auditEventLogout = "logout"
auditEventValidate = "validate"
)
type auditLogger struct {
Targets []string `yaml:"targets"`
Events []string `yaml:"events"`
Headers []string `yaml:"headers"`
TrustedIPHeaders []string `yaml:"trusted_ip_headers"`
lock sync.Mutex
}
func (a *auditLogger) Log(event auditEvent, r *http.Request, extraFields map[string]string) error {
if len(a.Targets) == 0 {
return nil
}
if !str.StringInSlice(string(event), a.Events) {
return nil
}
// Ensure order of logs, prevent file operation collisions
a.lock.Lock()
defer a.lock.Unlock()
// Compile log event
evt := map[string]interface{}{}
evt["timestamp"] = time.Now().Format(time.RFC3339)
evt["event_type"] = event
evt["remote_addr"] = a.findIP(r)
for k, v := range extraFields {
evt[k] = v
}
headers := map[string]string{}
for _, k := range a.Headers {
if v := r.Header.Get(k); v != "" {
headers[k] = v
}
}
evt["headers"] = headers
// Submit event to all specified targets
for _, target := range a.Targets {
if err := a.submitLog(target, evt); err != nil {
return errors.Wrapf(err, "Could not submit log to target %q", target)
}
}
return nil
}
func (a *auditLogger) findIP(r *http.Request) string {
remoteAddr := strings.SplitN(r.RemoteAddr, ":", 2)[0]
for _, hdr := range a.TrustedIPHeaders {
if value := r.Header.Get(hdr); value != "" {
return strings.SplitN(value, ",", 2)[0]
}
}
return remoteAddr
}
func (a *auditLogger) submitLog(target string, event map[string]interface{}) error {
u, err := url.Parse(target)
if err != nil {
return errors.Wrap(err, "Unable to parse target")
}
switch u.Scheme {
case "fd":
return a.submitLogFileDescriptor(u.Host, event)
case "file":
return a.submitLogFile(u.Path, event)
default:
return errors.Errorf("Unsupported target scheme %q", u.Scheme)
}
}
func (a *auditLogger) submitLogFile(filename string, event map[string]interface{}) error {
if err := os.MkdirAll(path.Dir(filename), 0600); err != nil {
return errors.Wrap(err, "Unable to create required paths")
}
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return errors.Wrap(err, "Unable to open audit file")
}
defer f.Close()
return json.NewEncoder(f).Encode(event)
}
func (a *auditLogger) submitLogFileDescriptor(descriptor string, event map[string]interface{}) error {
var w io.Writer
switch descriptor {
case "stdout":
w = os.Stdout
case "stderr":
w = os.Stderr
default:
return errors.Errorf("Unsupported file descriptor %q", descriptor)
}
return errors.Wrap(json.NewEncoder(w).Encode(event), "Unable to marshal event")
}