@@ -11,6 +11,28 @@ import (
11
11
"unicode/utf8"
12
12
)
13
13
14
+ type match struct {
15
+ leadingWhitespace string
16
+ property string // something like `stage.0.action.0.configuration.OAuthToken`
17
+ trailingWhitespace string
18
+ firstQuote string // < or "
19
+ oldValue string
20
+ secondQuote string // > or "
21
+ thirdQuote string // < or " or (
22
+ newValue string
23
+ fourthQuote string // > or " or )
24
+ postfix string
25
+ }
26
+
27
+ type expression struct {
28
+ planStatusRegex * regexp.Regexp
29
+ reTfPlanLine * regexp.Regexp
30
+ reTfPlanCurrentResource * regexp.Regexp
31
+ resourceIndex int
32
+ assign string
33
+ operator string
34
+ }
35
+
14
36
func init () {
15
37
// make sure we only have one process and that it runs on the main thread
16
38
// (so that ideally, when we Exec, we keep our user switches and stuff)
@@ -25,86 +47,162 @@ func getEnv(key, fallback string) string {
25
47
return fallback
26
48
}
27
49
50
+ var versionedExpressions = map [string ]expression {
51
+ "0.11" : expression {
52
+ planStatusRegex : regexp .MustCompile (
53
+ "^(.*?): (.*?) +\\ (ID: (.*?)\\ )$" ,
54
+ ),
55
+ reTfPlanLine : regexp .MustCompile (
56
+ "^( +)([a-zA-Z0-9%._-]+):( +)([\" <])(.*?)([>\" ]) +=> +([\" <])(.*)([>\" ])(.*)$" ,
57
+ ),
58
+ reTfPlanCurrentResource : regexp .MustCompile (
59
+ "^([~/+-]+) (.*?) +(.*)$" ,
60
+ ),
61
+ resourceIndex : 2 ,
62
+ assign : ":" ,
63
+ operator : "=>" ,
64
+ },
65
+ "0.12" : expression {
66
+ planStatusRegex : regexp .MustCompile (
67
+ "^(.*?): (.*?) +\\ [id=(.*?)\\ ]$" ,
68
+ ),
69
+ reTfPlanLine : regexp .MustCompile (
70
+ "^( +)([ ~a-zA-Z0-9%._-]+)=( +)([\" <])(.*?)([>\" ]) +-> +(\\ ()(.*)(\\ ))(.*)$" ,
71
+ ),
72
+ reTfPlanCurrentResource : regexp .MustCompile (
73
+ "^([~/+-]+) (.*?) +(.*) (.*) (.*)$" ,
74
+ ),
75
+ resourceIndex : 3 ,
76
+ assign : "=" ,
77
+ operator : "->" ,
78
+ },
79
+ }
80
+
28
81
func main () {
29
82
log .SetFlags (0 ) // no timestamps on our logs
30
83
31
84
// Character used to mask sensitive output
32
85
var tfmaskChar = getEnv ("TFMASK_CHAR" , "*" )
33
-
34
86
// Pattern representing sensitive output
35
- var tfmaskValuesRegex = getEnv ("TFMASK_VALUES_REGEX" , "(?i)^.*(oauth|secret|token|password|key|result).*$" )
36
-
87
+ var tfmaskValuesRegex = getEnv ("TFMASK_VALUES_REGEX" ,
88
+ "(?i)^.*(oauth|secret|token|password|key|result|id).*$" )
37
89
// Pattern representing sensitive resource
38
- var tfmaskResourceRegex = getEnv ("TFMASK_RESOURCES_REGEX" , "(?i)^(random_id).*$" )
39
-
40
- // stage.0.action.0.configuration.OAuthToken: "" => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
41
- reTfPlanLine := regexp .MustCompile ("^( +)([a-zA-Z0-9%._-]+):( +)([\" <])(.*?)([>\" ]) +=> +([\" <])(.*)([>\" ])(.*)$" )
42
-
43
- // random_id.some_id: Refreshing state... (ID: itILf4x5lqleQV9ZwT2gH-Zg3yuXM8pdUu6VFTX...P5vqUmggDweOoxFMPY5t9thA0SJE2EZIhcHbsQ)
44
- reTfPlanStatusLine := regexp .MustCompile ("^(.*?): (.*?) +\\ (ID: (.*?)\\ )$" )
45
-
90
+ var tfmaskResourceRegex = getEnv ("TFMASK_RESOURCES_REGEX" ,
91
+ "(?i)^(random_id|random_string).*$" )
46
92
47
- // -/+ random_string.postgres_admin_password (tainted) (new resource required)
48
- reTfPlanCurrentResource := regexp .MustCompile ("^([~/+-]+) (.*?) +(.*)$" )
49
- reTfApplyCurrentResource := regexp .MustCompile ("^([a-z].*?): (.*?)$" )
50
- currentResource := ""
93
+ // Default to tf 0.11, but users can override
94
+ var tfenv = getEnv ("TFENV" , "0.12" )
51
95
52
96
reTfValues := regexp .MustCompile (tfmaskValuesRegex )
53
97
reTfResource := regexp .MustCompile (tfmaskResourceRegex )
54
98
scanner := bufio .NewScanner (os .Stdin )
99
+ versionedExpressions := versionedExpressions [tfenv ]
100
+ // initialise currentResource once before scanning
101
+ currentResource := ""
55
102
for scanner .Scan () {
56
103
line := scanner .Text ()
57
- if reTfPlanCurrentResource .MatchString (line ) {
58
- match := reTfPlanCurrentResource .FindStringSubmatch (line )
59
- currentResource = match [2 ]
60
- } else if reTfApplyCurrentResource .MatchString (line ) {
61
- match := reTfApplyCurrentResource .FindStringSubmatch (line )
62
- currentResource = match [1 ]
63
- }
64
-
65
- if reTfPlanStatusLine .MatchString (line ) {
66
- match := reTfPlanStatusLine .FindStringSubmatch (line )
67
- resource := match [1 ]
68
- id := match [3 ]
69
- if reTfResource .MatchString (resource ) {
70
- line = strings .Replace (line , id , strings .Repeat (tfmaskChar , utf8 .RuneCountInString (id )), 1 )
71
- }
72
- fmt .Println (line )
73
- } else if reTfPlanLine .MatchString (line ) {
74
- match := reTfPlanLine .FindStringSubmatch (line )
75
- leadingWhitespace := match [1 ]
76
- property := match [2 ] // something like `stage.0.action.0.configuration.OAuthToken`
77
- trailingWhitespace := match [3 ]
78
- firstQuote := match [4 ] // < or "
79
- oldValue := match [5 ]
80
- secondQuote := match [6 ] // > or "
81
- thirdQuote := match [7 ] // < or "
82
- newValue := match [8 ]
83
- fourthQuote := match [9 ] // > or "
84
- postfix := match [10 ]
85
-
86
- if reTfValues .MatchString (property ) || reTfResource .MatchString (currentResource ) {
87
- // The value inside the "..." or <...>
88
- if oldValue != "sensitive" && oldValue != "computed" && oldValue != "<computed" {
89
- oldValue = strings .Repeat (tfmaskChar , utf8 .RuneCountInString (oldValue ))
90
- }
91
- // The value inside the "..." or <...>
92
- if newValue != "sensitive" && newValue != "computed" && newValue != "<computed" {
93
- newValue = strings .Repeat (tfmaskChar , utf8 .RuneCountInString (newValue ))
94
- }
95
- fmt .Printf ("%v%v:%v%v%v%v => %v%v%v%v\n " ,
96
- leadingWhitespace , property , trailingWhitespace , firstQuote , oldValue , secondQuote , thirdQuote , newValue , fourthQuote , postfix )
97
- } else {
98
- fmt .Println (line )
99
- }
100
- } else {
101
- // We matched nothing
102
- fmt .Println (line )
103
- }
104
+ currentResource = getCurrentResource (versionedExpressions ,
105
+ currentResource , line )
106
+ fmt .Println (processLine (versionedExpressions , reTfResource , reTfValues ,
107
+ tfmaskChar , currentResource , line ))
104
108
}
105
109
106
110
if err := scanner .Err (); err != nil {
107
111
fmt .Fprintln (os .Stderr , "error:" , err )
108
112
os .Exit (1 )
109
113
}
110
114
}
115
+
116
+ func getCurrentResource (expression expression , currentResource , line string ) string {
117
+ reTfApplyCurrentResource := regexp .MustCompile ("^([a-z].*?): (.*?)$" )
118
+ if expression .reTfPlanCurrentResource .MatchString (line ) {
119
+ match := expression .reTfPlanCurrentResource .FindStringSubmatch (line )
120
+ // for tf 0.12 the resource is wrapped in quotes, so remove them
121
+ strippedResource := strings .Replace (match [expression .resourceIndex ],
122
+ "\" " , "" , - 1 )
123
+ currentResource = strippedResource
124
+ } else if reTfApplyCurrentResource .MatchString (line ) {
125
+ match := reTfApplyCurrentResource .FindStringSubmatch (line )
126
+ currentResource = match [1 ]
127
+ }
128
+ return currentResource
129
+ }
130
+
131
+ func processLine (expression expression , reTfResource ,
132
+ reTfValues * regexp.Regexp , tfmaskChar , currentResource ,
133
+ line string ) string {
134
+ if expression .planStatusRegex .MatchString (line ) {
135
+ line = planStatus (expression .planStatusRegex , reTfResource , tfmaskChar ,
136
+ line )
137
+ } else if expression .reTfPlanLine .MatchString (line ) {
138
+ line = planLine (expression .reTfPlanLine , reTfResource , reTfValues ,
139
+ currentResource , tfmaskChar , expression .assign ,
140
+ expression .operator , line )
141
+ }
142
+ return line
143
+ }
144
+
145
+ func planStatus (planStatusRegex , reTfResource * regexp.Regexp , tfmaskChar ,
146
+ line string ) string {
147
+ match := planStatusRegex .FindStringSubmatch (line )
148
+ resource := match [1 ]
149
+ id := match [3 ]
150
+ if reTfResource .MatchString (resource ) {
151
+ line = strings .Replace (line , id , strings .Repeat (tfmaskChar ,
152
+ utf8 .RuneCountInString (id )), 1 )
153
+ }
154
+ return line
155
+ }
156
+
157
+ func matchFromLine (reTfPlanLine * regexp.Regexp , line string ) match {
158
+ subMatch := reTfPlanLine .FindStringSubmatch (line )
159
+ return match {
160
+ leadingWhitespace : subMatch [1 ],
161
+ property : subMatch [2 ], // something like `stage.0.action.0.configuration.OAuthToken`
162
+ trailingWhitespace : subMatch [3 ],
163
+ firstQuote : subMatch [4 ],
164
+ oldValue : subMatch [5 ],
165
+ secondQuote : subMatch [6 ], // > or "
166
+ thirdQuote : subMatch [7 ], // < or " or (
167
+ newValue : subMatch [8 ],
168
+ fourthQuote : subMatch [9 ], // > or " or )
169
+ postfix : subMatch [10 ],
170
+ }
171
+ }
172
+
173
+ func planLine (reTfPlanLine , reTfResource , reTfValues * regexp.Regexp ,
174
+ currentResource , tfmaskChar , assign , operator , line string ) string {
175
+ match := matchFromLine (reTfPlanLine , line )
176
+ if reTfValues .MatchString (match .property ) ||
177
+ reTfResource .MatchString (currentResource ) {
178
+ // The value inside the "...", <...> or (...)
179
+ oldValue := maskValue (match .oldValue , tfmaskChar )
180
+ // The value inside the "...", <...> or (...)
181
+ newValue := maskValue (match .newValue , tfmaskChar )
182
+ line = fmt .Sprintf ("%v%v%v%v%v%v%v %v %v%v%v%v" ,
183
+ match .leadingWhitespace , match .property , assign ,
184
+ match .trailingWhitespace , match .firstQuote , oldValue ,
185
+ match .secondQuote , operator , match .thirdQuote ,
186
+ newValue , match .fourthQuote , match .postfix )
187
+ }
188
+ return line
189
+ }
190
+
191
+ func maskValue (value , tfmaskChar string ) string {
192
+ exclusions := []string {"sensitive" , "computed" , "<computed" ,
193
+ "known after apply" }
194
+ if ! contains (exclusions , value ) {
195
+ return strings .Repeat (tfmaskChar ,
196
+ utf8 .RuneCountInString (value ))
197
+ }
198
+ return value
199
+ }
200
+
201
+ func contains (s []string , e string ) bool {
202
+ for _ , a := range s {
203
+ if a == e {
204
+ return true
205
+ }
206
+ }
207
+ return false
208
+ }
0 commit comments