-
Notifications
You must be signed in to change notification settings - Fork 1
/
app.go
211 lines (182 loc) · 5.3 KB
/
app.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package sesstok
import (
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"syscall"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/go-ini/ini"
"github.com/moznion/sesstok/internal"
"golang.org/x/crypto/ssh/terminal"
)
const awsAccessKeyIDKey = "aws_access_key_id"
const awsSecretAccessKeyKey = "aws_secret_access_key"
const awsSessionTokenKey = "aws_session_token"
var (
// ErrTokenCodeMissing is an error to indicate the mandatory argument `TokenCode` is missing.
ErrTokenCodeMissing = errors.New("the required argument `TokenCode` was not provided")
)
// Run is the entry point of this application.
func Run(opts *internal.Options) error {
if opts.Version {
return internal.ShowVersion()
}
rcFilePath, err := opts.GetRCFileFullPath()
if err != nil {
return fmt.Errorf("failed to get rc file path: %w", err)
}
if _, err := os.Stat(rcFilePath); opts.Init || err != nil {
fmt.Printf("rc file path: %s\n", rcFilePath)
if err != nil {
fmt.Printf("rc file doesn't exist\n")
}
err = initialize(rcFilePath)
if err != nil {
return fmt.Errorf("failed to initialize (rc file: %s): %w", rcFilePath, err)
}
fmt.Print("OK, please retry this command with token code :)\n")
return nil
}
if opts.DumpRCFile {
return dumpRCFile(opts, rcFilePath)
}
tokenCode := opts.Args.TokenCode
if tokenCode == "" {
return ErrTokenCodeMissing
}
pswd, err := internal.LoadMasterPassword(opts)
if err != nil {
return fmt.Errorf("failed to read master password: %w", err)
}
conf, err := internal.ReadRCFile(pswd, rcFilePath)
if err != nil {
return fmt.Errorf("failed to read rc file: %w", err)
}
sessToken, err := internal.RetrieveSessionToken(tokenCode, conf.AccessKeyID, conf.SecretAccessKey, opts.Duration, conf.MFASerial)
if err != nil {
return fmt.Errorf("failed to get STS session token: %w", err)
}
if !opts.Silent {
fmt.Printf("%v\n", sessToken)
}
err = updateCredentials(opts, conf, sessToken)
if err != nil {
return fmt.Errorf("failed to update credentials file: %w", err)
}
return nil
}
func updateCredentials(opts *internal.Options, conf *internal.Config, sessToken *sts.GetSessionTokenOutput) error {
if !opts.TokenOnly {
credentialsFilePath, err := opts.GetCredentialsFileFullPath()
if err != nil {
return err
}
allCreds, err := ini.Load(credentialsFilePath)
if err != nil {
return err
}
creds := allCreds.Section(conf.ProfileName)
creds.Comment = fmt.Sprintf(`# {"sessionTokenExpiryDateTime":"%s"}`, sessToken.Credentials.Expiration.String())
creds.Key(awsAccessKeyIDKey).SetValue(*sessToken.Credentials.AccessKeyId)
creds.Key(awsSecretAccessKeyKey).SetValue(*sessToken.Credentials.SecretAccessKey)
creds.Key(awsSessionTokenKey).SetValue(*sessToken.Credentials.SessionToken)
err = allCreds.SaveTo(credentialsFilePath)
if err != nil {
return err
}
}
return nil
}
func initialize(rcFilePath string) error {
fmt.Printf("would you like initialize? [N/y] ")
var shouldInit string
_, err := fmt.Scanf("%s", &shouldInit)
if err != nil {
return errors.New("abort")
}
shouldInit = strings.ToLower(shouldInit)
if shouldInit != "y" && shouldInit != "yes" {
return errors.New("abort")
}
fmt.Printf("would you like to set a master password? [N/y] ")
var shouldSetMasterPswd string
_, err = fmt.Scanf("%s", &shouldSetMasterPswd)
if err != nil {
return errors.New("abort")
}
shouldSetMasterPswd = strings.ToLower(shouldSetMasterPswd)
pswd := make([]byte, 0)
if shouldSetMasterPswd == "y" || shouldSetMasterPswd == "yes" {
fmt.Print("master password: ")
pswd, err = terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return err
}
fmt.Print("\nmaster password (confirm): ")
confirmPswd, err := terminal.ReadPassword(int(syscall.Stdin))
fmt.Printf("\n")
if err != nil {
return err
}
if string(pswd) != string(confirmPswd) {
return errors.New("invalid password has come")
}
if string(pswd) == "" {
return errors.New("empty password is not allowed")
}
}
fmt.Print("access key ID for assume role: ")
var accessKeyID string
_, err = fmt.Scanf("%s", &accessKeyID)
if err != nil {
return err
}
fmt.Print("secret access key for assume role: ")
var secretAccessKey string
_, err = fmt.Scanf("%s", &secretAccessKey)
if err != nil {
return err
}
fmt.Print("MFA serial (ARN): ")
var mfaSerial string
_, err = fmt.Scanf("%s", &mfaSerial)
if err != nil {
return err
}
fmt.Print("profile name for assume role: ")
var profileName string
_, err = fmt.Scanf("%s", &profileName)
if err != nil {
return err
}
conf := &internal.Config{
AccessKeyID: accessKeyID,
SecretAccessKey: secretAccessKey,
MFASerial: mfaSerial,
ProfileName: profileName,
}
err = conf.WriteRCFile(pswd, rcFilePath)
if err != nil {
return err
}
fmt.Printf("initialized (created rc file: %s)\n", rcFilePath)
return nil
}
func dumpRCFile(opts *internal.Options, rcFilePath string) error {
pswd, err := internal.LoadMasterPassword(opts)
if err != nil {
return fmt.Errorf("failed to read master password: %w", err)
}
conf, err := internal.ReadRCFile(pswd, rcFilePath)
if err != nil {
return fmt.Errorf("failed to read rc file: %w", err)
}
serializedConf, err := json.Marshal(conf)
if err != nil {
return fmt.Errorf("failed to serialize the contents of rc file: %w", err)
}
fmt.Printf("%s\n", serializedConf)
return nil
}