/
cli.go
212 lines (166 loc) · 5.44 KB
/
cli.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
212
package main
import (
"fmt"
"regexp"
"strings"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/spf13/viper"
)
const (
// AWSRegionFlag is the generic AWS Region Flag
AWSRegionFlag string = "aws-region"
// VaultAWSKeychainNameFlag is the aws-vault keychain name Flag
VaultAWSKeychainNameFlag string = "aws-vault-keychain-name"
// VaultAWSKeychainNameDefault is the aws-vault default keychain name
VaultAWSKeychainNameDefault string = "login"
// AWSProfileAccountFlag is the combined AWS profile name and account ID Flag
AWSProfileAccountFlag string = "aws-profile-account"
// AWSBaseProfileFlag is the AWS base profile name Flag
AWSBaseProfileFlag string = "aws-base-profile"
// AWSProfileFlag is the AWS Profile flag
AWSProfileFlag string = "aws-profile"
// IAMUserFlag is the IAM User name Flag
IAMUserFlag string = "iam-user"
// IAMRoleFlag is the IAM Role name Flag
IAMRoleFlag string = "iam-role"
// OutputFlag is the Output Flag
OutputFlag string = "output"
// NoMFAFlag indicates that no MFA device should be configured as one exists
NoMFAFlag string = "no-mfa"
// VerboseFlag is the Verbose Flag
VerboseFlag string = "verbose"
)
func stringSliceContains(stringSlice []string, value string) bool {
for _, x := range stringSlice {
if value == x {
return true
}
}
return false
}
type errInvalidKeychainName struct {
KeychainName string
}
func (e *errInvalidKeychainName) Error() string {
return fmt.Sprintf("invalid keychain name '%s'", e.KeychainName)
}
func checkVault(v *viper.Viper) error {
// Both keychain name and profile are required or both must be missing
keychainName := v.GetString(VaultAWSKeychainNameFlag)
keychainNames := []string{
VaultAWSKeychainNameDefault,
}
if len(keychainName) > 0 && !stringSliceContains(keychainNames, keychainName) {
return fmt.Errorf("%s is invalid, expected %v: %w", VaultAWSKeychainNameFlag, keychainNames, &errInvalidKeychainName{KeychainName: keychainName})
}
return nil
}
type errInvalidRegion struct {
Region string
}
func (e *errInvalidRegion) Error() string {
return fmt.Sprintf("invalid region %q", e.Region)
}
// Note: Testing the partition is not really the best check here, but its sufficient
func checkRegion(v *viper.Viper) error {
r := v.GetString(AWSRegionFlag)
if _, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), r); !ok {
return fmt.Errorf("%s is invalid: %w", AWSRegionFlag, &errInvalidRegion{Region: r})
}
return nil
}
type errInvalidAccountID struct {
AccountID string
}
func (e *errInvalidAccountID) Error() string {
return fmt.Sprintf("invalid Account ID %q", e.AccountID)
}
func checkAccountID(id string) error {
if matched, err := regexp.Match(`^\d{12}$`, []byte(id)); !matched || err != nil {
return fmt.Errorf("AWS Account ID must be a 12 digit number: %w", &errInvalidAccountID{AccountID: id})
}
return nil
}
type errInvalidProfileName struct {
ProfileName string
}
func (e *errInvalidProfileName) Error() string {
return fmt.Sprintf("invalid Account ID %q", e.ProfileName)
}
func checkProfileName(profileName string) error {
if matched, err := regexp.Match(`[A-Za-z0-9\-\_]+`, []byte(profileName)); !matched || err != nil {
return fmt.Errorf("AWS Profile Name must be can only contain letters, numbers, hyphens, and underscores: %w", &errInvalidProfileName{ProfileName: profileName})
}
return nil
}
type errInvalidProfileAccount struct {
ProfileAccount string
}
func (e *errInvalidProfileAccount) Error() string {
return fmt.Sprintf("invalid Profile Name and Account ID %q", e.ProfileAccount)
}
func checkProfileAccount(v *viper.Viper) error {
profileAccounts := v.GetStringSlice(AWSProfileAccountFlag)
for _, profileAccount := range profileAccounts {
// Validate the profile name and account are separated by a colon
if !strings.Contains(profileAccount, ":") {
return fmt.Errorf("Each Profile Name and Account ID must be separated by a colon ':': %w", &errInvalidProfileAccount{ProfileAccount: profileAccount})
}
// Split out the profile name and account ID
profileAccountParts := strings.Split(profileAccount, ":")
profileName := profileAccountParts[0]
accountID := profileAccountParts[1]
if err := checkProfileName(profileName); err != nil {
return err
}
if err := checkAccountID(accountID); err != nil {
return err
}
}
return nil
}
type errInvalidIAMUser struct {
IAMUser string
}
func (e *errInvalidIAMUser) Error() string {
return fmt.Sprintf("invalid output %q", e.IAMUser)
}
func checkIAMUser(v *viper.Viper) error {
user := v.GetString(IAMUserFlag)
if len(user) == 0 {
return fmt.Errorf("%s is invalid: %w", IAMUserFlag, &errInvalidIAMUser{IAMUser: user})
}
return nil
}
type errInvalidIAMRole struct {
IAMRole string
}
func (e *errInvalidIAMRole) Error() string {
return fmt.Sprintf("invalid output %q", e.IAMRole)
}
func checkIAMRole(v *viper.Viper) error {
role := v.GetString(IAMRoleFlag)
if len(role) == 0 {
return fmt.Errorf("%s is invalid: %w", IAMRoleFlag, &errInvalidIAMRole{IAMRole: role})
}
return nil
}
type errInvalidOutput struct {
Output string
}
func (e *errInvalidOutput) Error() string {
return fmt.Sprintf("invalid output %q", e.Output)
}
func checkOutput(v *viper.Viper) error {
o := v.GetString(OutputFlag)
outputTypes := []string{
"text",
"json",
"yaml",
"table",
}
if len(o) > 0 && !stringSliceContains(outputTypes, o) {
return fmt.Errorf("%s is invalid, expected one of %v: %w", OutputFlag, outputTypes, &errInvalidOutput{Output: o})
}
return nil
}