forked from cloudposse-archives/tfmask
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
135 lines (118 loc) · 4.71 KB
/
main.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 (
"bufio"
"fmt"
"log"
"os"
"regexp"
"runtime"
"strings"
"unicode/utf8"
)
func init() {
// make sure we only have one process and that it runs on the main thread
// (so that ideally, when we Exec, we keep our user switches and stuff)
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
}
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
func main() {
log.SetFlags(0) // no timestamps on our logs
// Character used to mask sensitive output
var tfmaskChar = getEnv("TFMASK_CHAR", "*")
// Pattern representing sensitive output
var tfmaskValuesRegex = getEnv("TFMASK_VALUES_REGEX", "(?i)^.*(oauth|secret|token|password|key|result).*$")
// Pattern representing sensitive resource
var tfmaskResourceRegex = getEnv("TFMASK_RESOURCES_REGEX", "(?i)^(random_id).*$")
// stage.0.action.0.configuration.OAuthToken: "" => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
reTfPlanLine := regexp.MustCompile("^( +)([a-zA-Z0-9%._-]+):( +)([\"<])(.*?)([>\"]) +=> +([\"<])(.*?)([>\"])(.*)$")
reTfPlanLineAlt := regexp.MustCompile("^( +)([a-zA-Z0-9%._-]+):( +)([\"<])(.*?)([>\"])$")
// random_id.some_id: Refreshing state... (ID: itILf4x5lqleQV9ZwT2gH-Zg3yuXM8pdUu6VFTX...P5vqUmggDweOoxFMPY5t9thA0SJE2EZIhcHbsQ)
reTfPlanStatusLine := regexp.MustCompile("^(.*?): (.*?) +\\(ID: (.*?)\\)$")
// -/+ random_string.postgres_admin_password (tainted) (new resource required)
reTfPlanCurrentResource := regexp.MustCompile("^([~/+-]+) (.*?) +(.*)$")
reTfApplyCurrentResource := regexp.MustCompile("^([a-z].*?): (.*?)$")
reTfApplyCurrentResourceAlt := regexp.MustCompile("^( +)([a-z].*?): (.*?)$")
currentResource := ""
reTfValues := regexp.MustCompile(tfmaskValuesRegex)
reTfResource := regexp.MustCompile(tfmaskResourceRegex)
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
if reTfPlanCurrentResource.MatchString(line) {
match := reTfPlanCurrentResource.FindStringSubmatch(line)
currentResource = match[2]
} else if reTfApplyCurrentResource.MatchString(line) {
match := reTfApplyCurrentResource.FindStringSubmatch(line)
currentResource = match[1]
} else if reTfApplyCurrentResourceAlt.MatchString(line) {
match := reTfApplyCurrentResource.FindStringSubmatch(line)
if len(match) > 2 {
currentResource = match[2]
}
}
if reTfPlanStatusLine.MatchString(line) {
match := reTfPlanStatusLine.FindStringSubmatch(line)
resource := match[1]
id := match[3]
if reTfResource.MatchString(resource) {
line = strings.Replace(line, id, strings.Repeat(tfmaskChar, utf8.RuneCountInString(id)), 1)
}
fmt.Println(line)
} else if reTfPlanLine.MatchString(line) {
match := reTfPlanLine.FindStringSubmatch(line)
leadingWhitespace := match[1]
property := match[2] // something like `stage.0.action.0.configuration.OAuthToken`
trailingWhitespace := match[3]
firstQuote := match[4] // < or "
oldValue := match[5]
secondQuote := match[6] // > or "
thirdQuote := match[7] // < or "
newValue := match[8]
fourthQuote := match[9] // > or "
postfix := match[10]
if reTfValues.MatchString(property) || reTfResource.MatchString(currentResource) {
// The value inside the "..." or <...>
if oldValue != "sensitive" && oldValue != "computed" && oldValue != "<computed" {
oldValue = strings.Repeat(tfmaskChar, utf8.RuneCountInString(oldValue))
}
// The value inside the "..." or <...>
if newValue != "sensitive" && newValue != "computed" && newValue != "<computed" {
newValue = strings.Repeat(tfmaskChar, utf8.RuneCountInString(newValue))
}
fmt.Printf("%v%v:%v%v%v%v => %v%v%v%v\n",
leadingWhitespace, property, trailingWhitespace, firstQuote, oldValue, secondQuote, thirdQuote, newValue, fourthQuote, postfix)
}
} else if reTfPlanLineAlt.MatchString(line) {
match := reTfPlanLineAlt.FindStringSubmatch(line)
leadingWhitespace := match[1]
property := match[2] // something like `stage.0.action.0.configuration.OAuthToken`
trailingWhitespace := match[3]
firstQuote := match[4] // < or "
oldValue := match[5]
secondQuote := match[6] // > or "
if reTfValues.MatchString(property) || reTfResource.MatchString(currentResource) {
// The value inside the "..." or <...>
if oldValue != "sensitive" && oldValue != "computed" && oldValue != "<computed" {
oldValue = strings.Repeat(tfmaskChar, utf8.RuneCountInString(oldValue))
}
fmt.Printf("%v%v:%v%v%v%v\n",
leadingWhitespace, property, trailingWhitespace, firstQuote, oldValue, secondQuote)
} else {
fmt.Println(line)
}
} else {
// We matched nothing
fmt.Println(line)
}
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}