forked from gopasspw/gopass
-
Notifications
You must be signed in to change notification settings - Fork 0
/
audit.go
145 lines (117 loc) · 3.44 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
136
137
138
139
140
141
142
143
144
145
package action
import (
"fmt"
"os"
"github.com/fatih/color"
"github.com/muesli/crunchy"
"github.com/muesli/goprogressbar"
"github.com/urfave/cli"
)
// auditedSecret with its name, content a warning message and a pipeline error.
type auditedSecret struct {
name string
// the secret's content as a string. Needed for checking for duplicates.
content string
// message to the user about some flaw in the secret
message string
// real error that something in the pipeline went wrong
err error
}
// Audit validates passwords against common flaws
func (s *Action) Audit(c *cli.Context) error {
t, err := s.Store.Tree()
if err != nil {
return err
}
list := t.List(0)
fmt.Printf("Checking %d secrets. This may take some time ...\n", len(list))
// Secrets that still need auditing.
secrets := make(chan string)
// Secrets that have been audited.
checked := make(chan auditedSecret)
// Spawn workers that run the auditing of all secrets concurrently.
validator := crunchy.NewValidator()
for jobs := 0; jobs < c.Int("jobs"); jobs++ {
go s.audit(validator, secrets, checked)
}
go func() {
for _, secret := range list {
secrets <- secret
}
close(secrets)
}()
duplicates := make(map[string][]string)
messages := make(map[string][]string)
errors := make(map[string][]string)
bar := &goprogressbar.ProgressBar{
Total: int64(len(list)),
Width: 120,
}
i := 0
for secret := range checked {
if secret.err != nil {
errors[secret.err.Error()] = append(errors[secret.err.Error()], secret.name)
} else {
duplicates[secret.content] = append(duplicates[secret.content], secret.name)
}
if secret.message != "" {
messages[secret.message] = append(messages[secret.message], secret.name)
}
i++
bar.Current = int64(i)
bar.Text = fmt.Sprintf("%d of %d secrets checked", bar.Current, bar.Total)
bar.LazyPrint()
if i == len(list) {
break
}
}
close(checked)
fmt.Println() // Print empty line after the progressbar.
foundDuplicates := false
for _, secrets := range duplicates {
if len(secrets) > 1 {
foundDuplicates = true
fmt.Println(color.CyanString("Detected a shared secret for:"))
for _, secret := range secrets {
fmt.Println(color.CyanString("\t- %s", secret))
}
}
}
if !foundDuplicates {
fmt.Println(color.GreenString("No shared secrets found."))
}
foundWeakPasswords := printAuditResults(messages, "%s:\n", color.CyanString)
if !foundWeakPasswords {
fmt.Println(color.GreenString("No weak secrets detected."))
}
foundErrors := printAuditResults(errors, "%s:\n", color.RedString)
if foundWeakPasswords || foundDuplicates || foundErrors {
os.Exit(1)
}
return nil
}
func (s *Action) audit(validator *crunchy.Validator, secrets <-chan string, checked chan<- auditedSecret) {
for secret := range secrets {
content, err := s.Store.GetFirstLine(secret)
if err != nil {
checked <- auditedSecret{name: secret, content: string(content), err: err}
continue
}
if err := validator.Check(string(content)); err != nil {
checked <- auditedSecret{name: secret, content: string(content), message: err.Error()}
continue
}
checked <- auditedSecret{name: secret, content: string(content)}
}
}
func printAuditResults(m map[string][]string, format string, color func(format string, a ...interface{}) string) bool {
b := false
for msg, secrets := range m {
b = true
fmt.Print(color(format, msg))
for _, secret := range secrets {
fmt.Print(color("\t- %s\n", secret))
}
}
return b
}