-
Notifications
You must be signed in to change notification settings - Fork 124
/
redact_secrets_plugin.go
123 lines (105 loc) · 3.53 KB
/
redact_secrets_plugin.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
package graphql
import (
"fmt"
"os"
"reflect"
"sort"
"github.com/99designs/gqlgen/codegen/config"
"github.com/evergreen-ci/evergreen/util"
"github.com/mongodb/grip"
"github.com/mongodb/grip/message"
)
// GenerateSecretFields generates a file that contains a list of fields that should be redacted in logs.
func GenerateSecretFields(cfg *config.Config) error {
redactedFields := make(map[string]bool)
for _, schemaType := range cfg.Schema.Types {
for _, field := range schemaType.Fields {
if field.Directives.ForName("redactSecrets") != nil {
redactedFields[field.Name] = true
}
}
}
var fields []string
for field := range redactedFields {
fields = append(fields, field)
}
// Sort the fields to ensure consistent output.
sort.Strings(fields)
return generateRedactedFieldsFile(fields)
}
// generateRedactedFieldsFile generates a file that contains a list of fields that should be redacted in logs. It is used to generate the redacted_fields_gen.go file.
// The file contains a list of fields that should be redacted in logs, a function to check if a field should be redacted, and a function to redact fields in a map.
func generateRedactedFieldsFile(fields []string) error {
file, err := os.Create("graphql/redacted_fields_gen.go")
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(`// Code generated by graphql/redact_secrets_plugin.go DO NOT EDIT.
package graphql
var redactedFields = map[string]bool{
`)
if err != nil {
return err
}
for _, field := range fields {
_, err = file.WriteString(fmt.Sprintf("\t\"%s\": true,\n", field))
if err != nil {
return err
}
}
_, err = file.WriteString("}\n")
if err != nil {
return err
}
return nil
}
func isFieldRedacted(fieldName string, fieldsToRedact map[string]bool) bool {
_, ok := fieldsToRedact[fieldName]
return ok
}
// RedactFieldsInMap recursively searches for and redacts fields in a map.
// Assumes map structure like map[string]interface{} where interface{} can be another map, a slice, or a basic datatype.
func RedactFieldsInMap(data map[string]interface{}, fieldsToRedact map[string]bool) map[string]interface{} {
dataCopy := map[string]interface{}{}
if err := util.DeepCopy(data, &dataCopy); err != nil {
// If theres an error copying the data, log it and return an empty map.
grip.Error(message.WrapError(err, message.Fields{
"message": "failed to deep copy request variables",
}))
return map[string]interface{}{}
}
recursivelyRedactFieldsInMap(dataCopy, fieldsToRedact)
return dataCopy
}
func recursivelyRedactFieldsInMap(data map[string]interface{}, fieldsToRedact map[string]bool) {
for key, value := range data {
// If the current key matches a field that should be redacted, redact it.
if isFieldRedacted(key, fieldsToRedact) {
data[key] = "REDACTED"
continue
}
// Handle nil values.
if value == nil {
continue
}
// If the value is a map, recursively redact fields within it.
if reflect.TypeOf(value).Kind() == reflect.Map {
if subMap, ok := value.(map[string]interface{}); ok {
recursivelyRedactFieldsInMap(subMap, fieldsToRedact)
}
}
// If the value is a slice, iterate over it and redact fields if elements are maps.
if reflect.TypeOf(value).Kind() == reflect.Slice {
sliceVal := reflect.ValueOf(value)
for i := 0; i < sliceVal.Len(); i++ {
elem := sliceVal.Index(i).Interface()
if reflect.TypeOf(elem).Kind() == reflect.Map {
if elemMap, ok := elem.(map[string]interface{}); ok {
recursivelyRedactFieldsInMap(elemMap, fieldsToRedact)
}
}
}
}
}
}