forked from gopasspw/gopass
-
Notifications
You must be signed in to change notification settings - Fork 0
/
audit.go
166 lines (136 loc) · 4.05 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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package action
import (
"context"
"errors"
"fmt"
"github.com/fatih/color"
"github.com/justwatchcom/gopass/utils/out"
"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(ctx context.Context, c *cli.Context) error {
t, err := s.Store.Tree()
if err != nil {
return s.exitError(ctx, ExitList, err, "failed to get store tree: %s", 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(ctx, 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
out.Cyan(ctx, "Detected a shared secret for:")
for _, secret := range secrets {
out.Cyan(ctx, "\t- %s", secret)
}
}
}
if !foundDuplicates {
out.Green(ctx, "No shared secrets found.")
}
foundWeakPasswords := printAuditResults(ctx, messages, "%s:\n", color.CyanString)
if !foundWeakPasswords {
out.Green(ctx, "No weak secrets detected.")
}
foundErrors := printAuditResults(ctx, errors, "%s:\n", color.RedString)
if foundWeakPasswords || foundDuplicates || foundErrors {
return s.exitError(ctx, ExitAudit, nil, "found weak passwords or duplicates")
}
return nil
}
func (s *Action) audit(ctx context.Context, validator *crunchy.Validator, secrets <-chan string, checked chan<- auditedSecret) {
for secret := range secrets {
// check for context cancelation
select {
case <-ctx.Done():
checked <- auditedSecret{name: secret, content: "", err: errors.New("user aborted")}
continue
default:
}
sec, err := s.Store.Get(ctx, secret)
if err != nil {
pw := ""
if sec != nil {
pw = sec.Password()
}
checked <- auditedSecret{name: secret, content: pw, err: err}
continue
}
if err := validator.Check(sec.Password()); err != nil {
checked <- auditedSecret{name: secret, content: sec.Password(), message: err.Error()}
continue
}
checked <- auditedSecret{name: secret, content: sec.Password()}
}
}
func printAuditResults(ctx context.Context, 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
}
func printAuditResult(ctx context.Context, pw string) {
validator := crunchy.NewValidator()
if err := validator.Check(pw); err != nil {
out.Cyan(ctx, fmt.Sprintf("Warning: %s", err))
}
}