-
Notifications
You must be signed in to change notification settings - Fork 34
/
get.go
229 lines (198 loc) · 8.61 KB
/
get.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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package main
import (
"fmt"
"os"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/spf13/cobra"
)
var (
FlagRegion = "region"
FlagRoleName = "role"
FlagTimeRemaining = "time-remaining"
FlagTimeToLive = "ttl"
FlagBypassCache = "bypass-cache"
FlagLogin = "login"
)
var (
// outputTypeEnvironmentVariable indicates that keyconjurer will dump the credentials to stdout in Bash environment variable format
outputTypeEnvironmentVariable = "env"
// outputTypeAWSCredentialsFile indicates that keyconjurer will dump the credentials into the ~/.aws/credentials file.
outputTypeAWSCredentialsFile = "awscli"
// outputTypeTencentCredentialsFile indicates that keyconjurer will dump the credentials into the ~/.tencent/credentials file.
outputTypeTencentCredentialsFile = "tencentcli"
permittedOutputTypes = []string{outputTypeAWSCredentialsFile, outputTypeEnvironmentVariable, outputTypeTencentCredentialsFile}
permittedShellTypes = []string{shellTypePowershell, shellTypeBash, shellTypeBasic, shellTypeInfer}
)
func init() {
getCmd.Flags().String(FlagRegion, "us-west-2", "The AWS region to use")
getCmd.Flags().Uint(FlagTimeToLive, 1, "The key timeout in hours from 1 to 8.")
getCmd.Flags().UintP(FlagTimeRemaining, "t", DefaultTimeRemaining, "Request new keys if there are no keys in the environment or the current keys expire within <time-remaining> minutes. Defaults to 60.")
getCmd.Flags().StringP(FlagRoleName, "r", "", "The name of the role to assume.")
getCmd.Flags().String(FlagRoleSessionName, "KeyConjurer-AssumeRole", "the name of the role session name that will show up in CloudTrail logs")
getCmd.Flags().StringP(FlagOutputType, "o", outputTypeEnvironmentVariable, "Format to save new credentials in. Supported outputs: env, awscli,tencentcli")
getCmd.Flags().String(FlagShellType, shellTypeInfer, "If output type is env, determines which format to output credentials in - by default, the format is inferred based on the execution environment. WSL users may wish to overwrite this to `bash`")
getCmd.Flags().String(FlagTencentCLIPath, "~/.tencent/", "Path for directory used by the tencent-cli tool. Default is \"~/.tencent\".")
getCmd.Flags().String(FlagCloudType, "aws", "Choose a cloud vendor. Default is aws. Can choose aws or tencent")
getCmd.Flags().Bool(FlagBypassCache, false, "Do not check the cache for accounts and send the application ID as-is to Okta. This is useful if you have an ID you know is an Okta application ID and it is not stored in your local account cache.")
getCmd.Flags().Bool(FlagLogin, false, "Login to Okta before running the command")
getCmd.Flags().String(FlagAWSCLIPath, "~/.aws/", "Path for directory used by the aws CLI")
getCmd.Flags().BoolP(FlagURLOnly, "u", false, "Print only the URL to visit rather than a user-friendly message")
getCmd.Flags().BoolP(FlagNoBrowser, "b", false, "Do not open a browser window, printing the URL instead")
}
func isMemberOfSlice(slice []string, val string) bool {
for _, member := range slice {
if member == val {
return true
}
}
return false
}
func resolveApplicationInfo(cfg *Config, bypassCache bool, nameOrID string) (*Account, bool) {
if bypassCache {
return &Account{ID: nameOrID, Name: nameOrID}, true
}
return cfg.FindAccount(nameOrID)
}
var getCmd = &cobra.Command{
Use: "get <accountName/alias>",
Short: "Retrieves temporary cloud API credentials.",
Long: `Retrieves temporary cloud API credentials for the specified account. It sends a push request to the first Duo device it finds associated with your account.
A role must be specified when using this command through the --role flag. You may list the roles you can assume through the roles command.`,
RunE: func(cmd *cobra.Command, args []string) error {
config := ConfigFromCommand(cmd)
ctx := cmd.Context()
oidcDomain, _ := cmd.Flags().GetString(FlagOIDCDomain)
clientID, _ := cmd.Flags().GetString(FlagClientID)
if HasTokenExpired(config.Tokens) {
if ok, _ := cmd.Flags().GetBool(FlagLogin); ok {
urlOnly, _ := cmd.Flags().GetBool(FlagURLOnly)
noBrowser, _ := cmd.Flags().GetBool(FlagNoBrowser)
login := LoginCommand{
Config: config,
OIDCDomain: oidcDomain,
ClientID: clientID,
MachineOutput: ShouldUseMachineOutput(cmd.Flags()) || urlOnly,
NoBrowser: noBrowser,
}
if err := login.Execute(cmd.Context()); err != nil {
return err
}
} else {
return ErrTokensExpiredOrAbsent
}
return nil
}
ttl, _ := cmd.Flags().GetUint(FlagTimeToLive)
timeRemaining, _ := cmd.Flags().GetUint(FlagTimeRemaining)
outputType, _ := cmd.Flags().GetString(FlagOutputType)
shellType, _ := cmd.Flags().GetString(FlagShellType)
roleName, _ := cmd.Flags().GetString(FlagRoleName)
cloudType, _ := cmd.Flags().GetString(FlagCloudType)
awsCliPath, _ := cmd.Flags().GetString(FlagAWSCLIPath)
tencentCliPath, _ := cmd.Flags().GetString(FlagTencentCLIPath)
if !isMemberOfSlice(permittedOutputTypes, outputType) {
return ValueError{Value: outputType, ValidValues: permittedOutputTypes}
}
if !isMemberOfSlice(permittedShellTypes, shellType) {
return ValueError{Value: shellType, ValidValues: permittedShellTypes}
}
var accountID string
if len(args) > 0 {
accountID = args[0]
} else if config.LastUsedAccount != nil {
// No account specified. Can we use the most recent one?
accountID = *config.LastUsedAccount
} else {
return cmd.Usage()
}
bypassCache, _ := cmd.Flags().GetBool(FlagBypassCache)
account, ok := resolveApplicationInfo(config, bypassCache, accountID)
if !ok {
return UnknownAccountError(args[0], FlagBypassCache)
}
if roleName == "" {
if account.MostRecentRole == "" {
cmd.PrintErrln("You must specify the --role flag with this command")
return nil
}
roleName = account.MostRecentRole
}
if config.TimeRemaining != 0 && timeRemaining == DefaultTimeRemaining {
timeRemaining = config.TimeRemaining
}
var credentials CloudCredentials
if cloudType == cloudAws {
credentials = LoadAWSCredentialsFromEnvironment()
} else if cloudType == cloudTencent {
credentials = LoadTencentCredentialsFromEnvironment()
}
if credentials.ValidUntil(account, time.Duration(timeRemaining)*time.Minute) {
return echoCredentials(accountID, accountID, credentials, outputType, shellType, awsCliPath, tencentCliPath)
}
samlResponse, assertionStr, err := DiscoverConfigAndExchangeTokenForAssertion(cmd.Context(), NewHTTPClient(), config.Tokens, oidcDomain, clientID, account.ID)
if err != nil {
return err
}
pair, ok := FindRoleInSAML(roleName, samlResponse)
if !ok {
return UnknownRoleError(roleName, args[0])
}
if ttl == 1 && config.TTL != 0 {
ttl = config.TTL
}
if cloudType == cloudAws {
region, _ := cmd.Flags().GetString(FlagRegion)
session, _ := session.NewSession(&aws.Config{Region: aws.String(region)})
stsClient := sts.New(session)
timeoutInSeconds := int64(3600 * ttl)
resp, err := stsClient.AssumeRoleWithSAMLWithContext(ctx, &sts.AssumeRoleWithSAMLInput{
DurationSeconds: &timeoutInSeconds,
PrincipalArn: &pair.ProviderARN,
RoleArn: &pair.RoleARN,
SAMLAssertion: &assertionStr,
})
if err, ok := tryParseTimeToLiveError(err); ok {
return err
}
if err != nil {
return AWSError{
InnerError: err,
Message: "failed to exchange credentials",
}
}
credentials = CloudCredentials{
AccessKeyID: *resp.Credentials.AccessKeyId,
Expiration: resp.Credentials.Expiration.Format(time.RFC3339),
SecretAccessKey: *resp.Credentials.SecretAccessKey,
SessionToken: *resp.Credentials.SessionToken,
credentialsType: cloudType,
}
} else {
panic("not yet implemented")
}
if account != nil {
account.MostRecentRole = roleName
}
config.LastUsedAccount = &accountID
return echoCredentials(accountID, accountID, credentials, outputType, shellType, awsCliPath, tencentCliPath)
}}
func echoCredentials(id, name string, credentials CloudCredentials, outputType, shellType, awsCliPath, tencentCliPath string) error {
switch outputType {
case outputTypeEnvironmentVariable:
credentials.WriteFormat(os.Stdout, shellType)
return nil
case outputTypeAWSCredentialsFile, outputTypeTencentCredentialsFile:
acc := Account{ID: id, Name: name}
newCliEntry := NewCloudCliEntry(credentials, &acc)
cliPath := awsCliPath
if outputType == outputTypeTencentCredentialsFile {
cliPath = tencentCliPath
}
return SaveCloudCredentialInCLI(cliPath, newCliEntry)
default:
return fmt.Errorf("%s is an invalid output type", outputType)
}
}