]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/aws/aws-sdk-go/aws/credentials/processcreds/provider.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / aws / aws-sdk-go / aws / credentials / processcreds / provider.go
1 /*
2 Package processcreds is a credential Provider to retrieve `credential_process`
3 credentials.
4
5 WARNING: The following describes a method of sourcing credentials from an external
6 process. This can potentially be dangerous, so proceed with caution. Other
7 credential providers should be preferred if at all possible. If using this
8 option, you should make sure that the config file is as locked down as possible
9 using security best practices for your operating system.
10
11 You can use credentials from a `credential_process` in a variety of ways.
12
13 One way is to setup your shared config file, located in the default
14 location, with the `credential_process` key and the command you want to be
15 called. You also need to set the AWS_SDK_LOAD_CONFIG environment variable
16 (e.g., `export AWS_SDK_LOAD_CONFIG=1`) to use the shared config file.
17
18 [default]
19 credential_process = /command/to/call
20
21 Creating a new session will use the credential process to retrieve credentials.
22 NOTE: If there are credentials in the profile you are using, the credential
23 process will not be used.
24
25 // Initialize a session to load credentials.
26 sess, _ := session.NewSession(&aws.Config{
27 Region: aws.String("us-east-1")},
28 )
29
30 // Create S3 service client to use the credentials.
31 svc := s3.New(sess)
32
33 Another way to use the `credential_process` method is by using
34 `credentials.NewCredentials()` and providing a command to be executed to
35 retrieve credentials:
36
37 // Create credentials using the ProcessProvider.
38 creds := processcreds.NewCredentials("/path/to/command")
39
40 // Create service client value configured for credentials.
41 svc := s3.New(sess, &aws.Config{Credentials: creds})
42
43 You can set a non-default timeout for the `credential_process` with another
44 constructor, `credentials.NewCredentialsTimeout()`, providing the timeout. To
45 set a one minute timeout:
46
47 // Create credentials using the ProcessProvider.
48 creds := processcreds.NewCredentialsTimeout(
49 "/path/to/command",
50 time.Duration(500) * time.Millisecond)
51
52 If you need more control, you can set any configurable options in the
53 credentials using one or more option functions. For example, you can set a two
54 minute timeout, a credential duration of 60 minutes, and a maximum stdout
55 buffer size of 2k.
56
57 creds := processcreds.NewCredentials(
58 "/path/to/command",
59 func(opt *ProcessProvider) {
60 opt.Timeout = time.Duration(2) * time.Minute
61 opt.Duration = time.Duration(60) * time.Minute
62 opt.MaxBufSize = 2048
63 })
64
65 You can also use your own `exec.Cmd`:
66
67 // Create an exec.Cmd
68 myCommand := exec.Command("/path/to/command")
69
70 // Create credentials using your exec.Cmd and custom timeout
71 creds := processcreds.NewCredentialsCommand(
72 myCommand,
73 func(opt *processcreds.ProcessProvider) {
74 opt.Timeout = time.Duration(1) * time.Second
75 })
76 */
77 package processcreds
78
79 import (
80 "bytes"
81 "encoding/json"
82 "fmt"
83 "io"
84 "io/ioutil"
85 "os"
86 "os/exec"
87 "runtime"
88 "strings"
89 "time"
90
91 "github.com/aws/aws-sdk-go/aws/awserr"
92 "github.com/aws/aws-sdk-go/aws/credentials"
93 )
94
95 const (
96 // ProviderName is the name this credentials provider will label any
97 // returned credentials Value with.
98 ProviderName = `ProcessProvider`
99
100 // ErrCodeProcessProviderParse error parsing process output
101 ErrCodeProcessProviderParse = "ProcessProviderParseError"
102
103 // ErrCodeProcessProviderVersion version error in output
104 ErrCodeProcessProviderVersion = "ProcessProviderVersionError"
105
106 // ErrCodeProcessProviderRequired required attribute missing in output
107 ErrCodeProcessProviderRequired = "ProcessProviderRequiredError"
108
109 // ErrCodeProcessProviderExecution execution of command failed
110 ErrCodeProcessProviderExecution = "ProcessProviderExecutionError"
111
112 // errMsgProcessProviderTimeout process took longer than allowed
113 errMsgProcessProviderTimeout = "credential process timed out"
114
115 // errMsgProcessProviderProcess process error
116 errMsgProcessProviderProcess = "error in credential_process"
117
118 // errMsgProcessProviderParse problem parsing output
119 errMsgProcessProviderParse = "parse failed of credential_process output"
120
121 // errMsgProcessProviderVersion version error in output
122 errMsgProcessProviderVersion = "wrong version in process output (not 1)"
123
124 // errMsgProcessProviderMissKey missing access key id in output
125 errMsgProcessProviderMissKey = "missing AccessKeyId in process output"
126
127 // errMsgProcessProviderMissSecret missing secret acess key in output
128 errMsgProcessProviderMissSecret = "missing SecretAccessKey in process output"
129
130 // errMsgProcessProviderPrepareCmd prepare of command failed
131 errMsgProcessProviderPrepareCmd = "failed to prepare command"
132
133 // errMsgProcessProviderEmptyCmd command must not be empty
134 errMsgProcessProviderEmptyCmd = "command must not be empty"
135
136 // errMsgProcessProviderPipe failed to initialize pipe
137 errMsgProcessProviderPipe = "failed to initialize pipe"
138
139 // DefaultDuration is the default amount of time in minutes that the
140 // credentials will be valid for.
141 DefaultDuration = time.Duration(15) * time.Minute
142
143 // DefaultBufSize limits buffer size from growing to an enormous
144 // amount due to a faulty process.
145 DefaultBufSize = 1024
146
147 // DefaultTimeout default limit on time a process can run.
148 DefaultTimeout = time.Duration(1) * time.Minute
149 )
150
151 // ProcessProvider satisfies the credentials.Provider interface, and is a
152 // client to retrieve credentials from a process.
153 type ProcessProvider struct {
154 staticCreds bool
155 credentials.Expiry
156 originalCommand []string
157
158 // Expiry duration of the credentials. Defaults to 15 minutes if not set.
159 Duration time.Duration
160
161 // ExpiryWindow will allow the credentials to trigger refreshing prior to
162 // the credentials actually expiring. This is beneficial so race conditions
163 // with expiring credentials do not cause request to fail unexpectedly
164 // due to ExpiredTokenException exceptions.
165 //
166 // So a ExpiryWindow of 10s would cause calls to IsExpired() to return true
167 // 10 seconds before the credentials are actually expired.
168 //
169 // If ExpiryWindow is 0 or less it will be ignored.
170 ExpiryWindow time.Duration
171
172 // A string representing an os command that should return a JSON with
173 // credential information.
174 command *exec.Cmd
175
176 // MaxBufSize limits memory usage from growing to an enormous
177 // amount due to a faulty process.
178 MaxBufSize int
179
180 // Timeout limits the time a process can run.
181 Timeout time.Duration
182 }
183
184 // NewCredentials returns a pointer to a new Credentials object wrapping the
185 // ProcessProvider. The credentials will expire every 15 minutes by default.
186 func NewCredentials(command string, options ...func(*ProcessProvider)) *credentials.Credentials {
187 p := &ProcessProvider{
188 command: exec.Command(command),
189 Duration: DefaultDuration,
190 Timeout: DefaultTimeout,
191 MaxBufSize: DefaultBufSize,
192 }
193
194 for _, option := range options {
195 option(p)
196 }
197
198 return credentials.NewCredentials(p)
199 }
200
201 // NewCredentialsTimeout returns a pointer to a new Credentials object with
202 // the specified command and timeout, and default duration and max buffer size.
203 func NewCredentialsTimeout(command string, timeout time.Duration) *credentials.Credentials {
204 p := NewCredentials(command, func(opt *ProcessProvider) {
205 opt.Timeout = timeout
206 })
207
208 return p
209 }
210
211 // NewCredentialsCommand returns a pointer to a new Credentials object with
212 // the specified command, and default timeout, duration and max buffer size.
213 func NewCredentialsCommand(command *exec.Cmd, options ...func(*ProcessProvider)) *credentials.Credentials {
214 p := &ProcessProvider{
215 command: command,
216 Duration: DefaultDuration,
217 Timeout: DefaultTimeout,
218 MaxBufSize: DefaultBufSize,
219 }
220
221 for _, option := range options {
222 option(p)
223 }
224
225 return credentials.NewCredentials(p)
226 }
227
228 type credentialProcessResponse struct {
229 Version int
230 AccessKeyID string `json:"AccessKeyId"`
231 SecretAccessKey string
232 SessionToken string
233 Expiration *time.Time
234 }
235
236 // Retrieve executes the 'credential_process' and returns the credentials.
237 func (p *ProcessProvider) Retrieve() (credentials.Value, error) {
238 out, err := p.executeCredentialProcess()
239 if err != nil {
240 return credentials.Value{ProviderName: ProviderName}, err
241 }
242
243 // Serialize and validate response
244 resp := &credentialProcessResponse{}
245 if err = json.Unmarshal(out, resp); err != nil {
246 return credentials.Value{ProviderName: ProviderName}, awserr.New(
247 ErrCodeProcessProviderParse,
248 fmt.Sprintf("%s: %s", errMsgProcessProviderParse, string(out)),
249 err)
250 }
251
252 if resp.Version != 1 {
253 return credentials.Value{ProviderName: ProviderName}, awserr.New(
254 ErrCodeProcessProviderVersion,
255 errMsgProcessProviderVersion,
256 nil)
257 }
258
259 if len(resp.AccessKeyID) == 0 {
260 return credentials.Value{ProviderName: ProviderName}, awserr.New(
261 ErrCodeProcessProviderRequired,
262 errMsgProcessProviderMissKey,
263 nil)
264 }
265
266 if len(resp.SecretAccessKey) == 0 {
267 return credentials.Value{ProviderName: ProviderName}, awserr.New(
268 ErrCodeProcessProviderRequired,
269 errMsgProcessProviderMissSecret,
270 nil)
271 }
272
273 // Handle expiration
274 p.staticCreds = resp.Expiration == nil
275 if resp.Expiration != nil {
276 p.SetExpiration(*resp.Expiration, p.ExpiryWindow)
277 }
278
279 return credentials.Value{
280 ProviderName: ProviderName,
281 AccessKeyID: resp.AccessKeyID,
282 SecretAccessKey: resp.SecretAccessKey,
283 SessionToken: resp.SessionToken,
284 }, nil
285 }
286
287 // IsExpired returns true if the credentials retrieved are expired, or not yet
288 // retrieved.
289 func (p *ProcessProvider) IsExpired() bool {
290 if p.staticCreds {
291 return false
292 }
293 return p.Expiry.IsExpired()
294 }
295
296 // prepareCommand prepares the command to be executed.
297 func (p *ProcessProvider) prepareCommand() error {
298
299 var cmdArgs []string
300 if runtime.GOOS == "windows" {
301 cmdArgs = []string{"cmd.exe", "/C"}
302 } else {
303 cmdArgs = []string{"sh", "-c"}
304 }
305
306 if len(p.originalCommand) == 0 {
307 p.originalCommand = make([]string, len(p.command.Args))
308 copy(p.originalCommand, p.command.Args)
309
310 // check for empty command because it succeeds
311 if len(strings.TrimSpace(p.originalCommand[0])) < 1 {
312 return awserr.New(
313 ErrCodeProcessProviderExecution,
314 fmt.Sprintf(
315 "%s: %s",
316 errMsgProcessProviderPrepareCmd,
317 errMsgProcessProviderEmptyCmd),
318 nil)
319 }
320 }
321
322 cmdArgs = append(cmdArgs, p.originalCommand...)
323 p.command = exec.Command(cmdArgs[0], cmdArgs[1:]...)
324 p.command.Env = os.Environ()
325
326 return nil
327 }
328
329 // executeCredentialProcess starts the credential process on the OS and
330 // returns the results or an error.
331 func (p *ProcessProvider) executeCredentialProcess() ([]byte, error) {
332
333 if err := p.prepareCommand(); err != nil {
334 return nil, err
335 }
336
337 // Setup the pipes
338 outReadPipe, outWritePipe, err := os.Pipe()
339 if err != nil {
340 return nil, awserr.New(
341 ErrCodeProcessProviderExecution,
342 errMsgProcessProviderPipe,
343 err)
344 }
345
346 p.command.Stderr = os.Stderr // display stderr on console for MFA
347 p.command.Stdout = outWritePipe // get creds json on process's stdout
348 p.command.Stdin = os.Stdin // enable stdin for MFA
349
350 output := bytes.NewBuffer(make([]byte, 0, p.MaxBufSize))
351
352 stdoutCh := make(chan error, 1)
353 go readInput(
354 io.LimitReader(outReadPipe, int64(p.MaxBufSize)),
355 output,
356 stdoutCh)
357
358 execCh := make(chan error, 1)
359 go executeCommand(*p.command, execCh)
360
361 finished := false
362 var errors []error
363 for !finished {
364 select {
365 case readError := <-stdoutCh:
366 errors = appendError(errors, readError)
367 finished = true
368 case execError := <-execCh:
369 err := outWritePipe.Close()
370 errors = appendError(errors, err)
371 errors = appendError(errors, execError)
372 if errors != nil {
373 return output.Bytes(), awserr.NewBatchError(
374 ErrCodeProcessProviderExecution,
375 errMsgProcessProviderProcess,
376 errors)
377 }
378 case <-time.After(p.Timeout):
379 finished = true
380 return output.Bytes(), awserr.NewBatchError(
381 ErrCodeProcessProviderExecution,
382 errMsgProcessProviderTimeout,
383 errors) // errors can be nil
384 }
385 }
386
387 out := output.Bytes()
388
389 if runtime.GOOS == "windows" {
390 // windows adds slashes to quotes
391 out = []byte(strings.Replace(string(out), `\"`, `"`, -1))
392 }
393
394 return out, nil
395 }
396
397 // appendError conveniently checks for nil before appending slice
398 func appendError(errors []error, err error) []error {
399 if err != nil {
400 return append(errors, err)
401 }
402 return errors
403 }
404
405 func executeCommand(cmd exec.Cmd, exec chan error) {
406 // Start the command
407 err := cmd.Start()
408 if err == nil {
409 err = cmd.Wait()
410 }
411
412 exec <- err
413 }
414
415 func readInput(r io.Reader, w io.Writer, read chan error) {
416 tee := io.TeeReader(r, w)
417
418 _, err := ioutil.ReadAll(tee)
419
420 if err == io.EOF {
421 err = nil
422 }
423
424 read <- err // will only arrive here when write end of pipe is closed
425 }