1package lager
2
3import (
4	"encoding/json"
5	"regexp"
6)
7
8const awsAccessKeyIDPattern = `AKIA[A-Z0-9]{16}`
9const awsSecretAccessKeyPattern = `KEY["']?\s*(?::|=>|=)\s*["']?[A-Z0-9/\+=]{40}["']?`
10const cryptMD5Pattern = `\$1\$[A-Z0-9./]{1,16}\$[A-Z0-9./]{22}`
11const cryptSHA256Pattern = `\$5\$[A-Z0-9./]{1,16}\$[A-Z0-9./]{43}`
12const cryptSHA512Pattern = `\$6\$[A-Z0-9./]{1,16}\$[A-Z0-9./]{86}`
13const privateKeyHeaderPattern = `-----BEGIN(.*)PRIVATE KEY-----`
14
15type JSONRedacter struct {
16	keyMatchers   []*regexp.Regexp
17	valueMatchers []*regexp.Regexp
18}
19
20func NewJSONRedacter(keyPatterns []string, valuePatterns []string) (*JSONRedacter, error) {
21	if keyPatterns == nil {
22		keyPatterns = []string{"[Pp]wd", "[Pp]ass"}
23	}
24	if valuePatterns == nil {
25		valuePatterns = []string{awsAccessKeyIDPattern, awsSecretAccessKeyPattern, cryptMD5Pattern, cryptSHA256Pattern, cryptSHA512Pattern, privateKeyHeaderPattern}
26	}
27	ret := &JSONRedacter{}
28	for _, v := range keyPatterns {
29		r, err := regexp.Compile(v)
30		if err != nil {
31			return nil, err
32		}
33		ret.keyMatchers = append(ret.keyMatchers, r)
34	}
35	for _, v := range valuePatterns {
36		r, err := regexp.Compile(v)
37		if err != nil {
38			return nil, err
39		}
40		ret.valueMatchers = append(ret.valueMatchers, r)
41	}
42	return ret, nil
43}
44
45func (r JSONRedacter) Redact(data []byte) []byte {
46	var jsonBlob interface{}
47	err := json.Unmarshal(data, &jsonBlob)
48	if err != nil {
49		return handleError(err)
50	}
51	r.redactValue(&jsonBlob)
52
53	data, err = json.Marshal(jsonBlob)
54	if err != nil {
55		return handleError(err)
56	}
57
58	return data
59}
60
61func (r JSONRedacter) redactValue(data *interface{}) interface{} {
62	if data == nil {
63		return data
64	}
65
66	if a, ok := (*data).([]interface{}); ok {
67		r.redactArray(&a)
68	} else if m, ok := (*data).(map[string]interface{}); ok {
69		r.redactObject(&m)
70	} else if s, ok := (*data).(string); ok {
71		for _, m := range r.valueMatchers {
72			if m.MatchString(s) {
73				(*data) = "*REDACTED*"
74				break
75			}
76		}
77	}
78	return (*data)
79}
80
81func (r JSONRedacter) redactArray(data *[]interface{}) {
82	for i, _ := range *data {
83		r.redactValue(&((*data)[i]))
84	}
85}
86
87func (r JSONRedacter) redactObject(data *map[string]interface{}) {
88	for k, v := range *data {
89		for _, m := range r.keyMatchers {
90			if m.MatchString(k) {
91				(*data)[k] = "*REDACTED*"
92				break
93			}
94		}
95		if (*data)[k] != "*REDACTED*" {
96			(*data)[k] = r.redactValue(&v)
97		}
98	}
99}
100
101func handleError(err error) []byte {
102	var content []byte
103	if _, ok := err.(*json.UnsupportedTypeError); ok {
104		data := map[string]interface{}{"lager serialisation error": err.Error()}
105		content, err = json.Marshal(data)
106	}
107	if err != nil {
108		panic(err)
109	}
110	return content
111}
112