-
Notifications
You must be signed in to change notification settings - Fork 134
/
authenticator.go
97 lines (82 loc) · 2.13 KB
/
authenticator.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
package exec
import (
"bytes"
"context"
"io"
"os"
"os/exec"
"strings"
"sync"
"github.com/pkg/errors"
grpc_credentials "google.golang.org/grpc/credentials"
)
// Authenticator wraps an external command + environment that can be used to generate an access token.
type Authenticator struct {
// Set by the config
Cmd string
Args []string
Env []string
// Stubbable for testing
stdin io.Reader
stderr io.Writer
interactive bool
environ func() []string
// Mutex guards calling the cmd. Since the cmd could be
// interactive we want to make sure it's only called once.
mu sync.Mutex
}
func NewAuthenticator(config CommandDetails) grpc_credentials.PerRPCCredentials {
a := &Authenticator{
Cmd: config.Cmd,
Args: config.Args,
stdin: os.Stdin,
stderr: os.Stderr,
interactive: config.Interactive,
environ: os.Environ,
}
for _, env := range config.Env {
a.Env = append(a.Env, env.Name+"="+env.Value)
}
return a
}
func (a *Authenticator) getCreds() (string, error) {
a.mu.Lock()
defer a.mu.Unlock()
return a.getCredsLocked()
}
// getCredsLocked executes the plugin and reads the credentials from
// stdout. It must be called while holding the Authenticator's mutex.
func (a *Authenticator) getCredsLocked() (string, error) {
env := append(a.environ(), a.Env...)
stdout := &bytes.Buffer{}
cmd := exec.Command(a.Cmd, a.Args...)
cmd.Env = env
cmd.Stderr = a.stderr
cmd.Stdout = stdout
if a.interactive {
cmd.Stdin = a.stdin
}
err := cmd.Run()
if err != nil {
err = errors.Wrapf(err, "error retrieving credentials; command stdout: %s", string(stdout.Bytes()))
return "", err
}
tok := strings.TrimSpace(string(stdout.Bytes()))
if tok == "" {
err := errors.Errorf("command didn't return a token")
return "", errors.WithStack(err)
}
return tok, nil
}
func (a *Authenticator) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
tok, err := a.getCreds()
if err != nil {
return nil, err
}
return map[string]string{
"authorization": "Bearer " + tok,
}, nil
}
func (a *Authenticator) RequireTransportSecurity() bool {
return false
}