-
Notifications
You must be signed in to change notification settings - Fork 5
/
mfa.go
146 lines (129 loc) · 3.47 KB
/
mfa.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
package creds
import (
"bufio"
"fmt"
"os"
"regexp"
"strings"
)
const (
mfaCodeRegexString = `^\d{6}$`
)
var mfaCodeRegex = regexp.MustCompile(mfaCodeRegexString)
// MfaPrompt defines an object which recieves an Mfa ARN and returns an Mfa code
type MfaPrompt interface {
Prompt(string) (string, error)
}
// WritableMfaPrompt defines an MFA Prompt which can store a new secret
type WritableMfaPrompt interface {
Store(string, string) error
RetryText(string) string
}
// revive:disable-next-line:flag-parameter
func (c Creds) handleMfa(useMfa bool, mfaCode string, mfaPrompt MfaPrompt) (string, string, error) {
logger.InfoMsg("handling mfa options")
if !useMfa && mfaCode == "" {
logger.InfoMsg("mfa is disabled")
return "", "", nil
}
mfaSerial, err := c.MfaArn()
if err != nil {
return "", "", err
}
if mfaCode != "" {
logger.InfoMsg("mfa code already provided")
err := validateMfa(mfaCode)
if err != nil {
return "", "", err
}
return mfaCode, mfaSerial, nil
}
if mfaPrompt == nil {
logger.InfoMsg("using default mfa prompt")
// revive:disable-next-line:modifies-parameter
mfaPrompt = &DefaultMfaPrompt{}
}
logger.InfoMsg("prompting for mfa code")
// revive:disable-next-line:modifies-parameter
mfaCode, err = mfaPrompt.Prompt(mfaSerial)
if err != nil {
return "", "", err
}
return mfaCode, mfaSerial, nil
}
// DefaultMfaPrompt defines the standard CLI-based MFA prompt
type DefaultMfaPrompt struct {
PromptTextFunc func(string) string
}
func defaultPromptTextFunc(_ string) string {
return "MFA Code: "
}
// Prompt asks the user for their MFA token
func (p *DefaultMfaPrompt) Prompt(arn string) (string, error) {
mfaReader := bufio.NewReader(os.Stdin)
pf := p.PromptTextFunc
if pf == nil {
pf = defaultPromptTextFunc
}
fmt.Fprint(os.Stderr, pf(arn))
mfa, err := mfaReader.ReadString('\n')
if err != nil {
return "", err
}
trimmed := strings.TrimSpace(mfa)
err = validateMfa(trimmed)
if err != nil {
return "", err
}
return trimmed, nil
}
func validateMfa(code string) error {
logger.InfoMsg("validating mfa")
if mfaCodeRegex.MatchString(code) {
return nil
}
return fmt.Errorf("provided mfa code does not match the necessary format")
}
// MultiMfaPrompt allows a slice of sequential backends to check for Mfa
type MultiMfaPrompt struct {
Backends []MfaPrompt
}
// Prompt iterates through the backends to find an Mfa code
func (m *MultiMfaPrompt) Prompt(arn string) (string, error) {
logger.InfoMsgf("looking up %s in multi mfa prompt", arn)
for index, item := range m.Backends {
res, err := item.Prompt(arn)
if err == nil {
return res, nil
}
logger.InfoMsgf("failed backend lookup %d with %s", index, err)
}
return "", fmt.Errorf("all backends failed to return mfa code")
}
// Store attempts to store the Mfa in a backend
func (m *MultiMfaPrompt) Store(arn, seed string) error {
logger.InfoMsgf("attempting to store %s in multi mfa object", arn)
writer := m.getWriter()
if writer == nil {
return fmt.Errorf("no writable mfa backends found")
}
return writer.Store(arn, seed)
}
// RetryText returns helper text for retrying a failed mfa storage
func (m *MultiMfaPrompt) RetryText(arn string) string {
writer := m.getWriter()
if writer == nil {
return ""
}
return writer.RetryText(arn)
}
func (m *MultiMfaPrompt) getWriter() WritableMfaPrompt {
for index, item := range m.Backends {
writer, ok := item.(WritableMfaPrompt)
if ok {
logger.InfoMsgf("found mfa writer with index %d", index)
return writer
}
}
return nil
}