-
Notifications
You must be signed in to change notification settings - Fork 25
/
cli.go
171 lines (137 loc) · 4.78 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
package gcpauth
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-gcp-common/gcputil"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/parseutil"
"golang.org/x/oauth2"
"google.golang.org/api/iam/v1"
"strings"
"time"
)
type CLIHandler struct{}
func getSignedJwt(role string, m map[string]string) (string, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cleanhttp.DefaultClient())
credentials, tokenSource, err := gcputil.FindCredentials(m["credentials"], ctx, iam.CloudPlatformScope)
if err != nil {
return "", fmt.Errorf("could not obtain credentials: %v", err)
}
httpClient := oauth2.NewClient(ctx, tokenSource)
serviceAccount, ok := m["service_account"]
if !ok && credentials != nil {
serviceAccount = credentials.ClientEmail
}
if serviceAccount == "" {
return "", errors.New("could not obtain service account from credentials (are you using Application Default Credentials?). You must provide a service account to authenticate as")
}
project, ok := m["project"]
if !ok {
if credentials != nil {
project = credentials.ProjectId
} else {
project = "-"
}
}
var ttl = time.Duration(defaultIamMaxJwtExpMinutes) * time.Minute
jwtExpStr, ok := m["jwt_exp"]
if ok {
ttl, err = parseutil.ParseDurationSecond(jwtExpStr)
if err != nil {
return "", fmt.Errorf("could not parse jwt_exp '%s' into integer value", jwtExpStr)
}
}
jwtPayload := map[string]interface{}{
"aud": fmt.Sprintf("http://vault/%s", role),
"sub": serviceAccount,
"exp": time.Now().Add(ttl).Unix(),
}
payloadBytes, err := json.Marshal(jwtPayload)
if err != nil {
return "", fmt.Errorf("could not convert JWT payload to JSON string: %v", err)
}
jwtReq := &iam.SignJwtRequest{
Payload: string(payloadBytes),
}
iamClient, err := iam.New(httpClient)
if err != nil {
return "", fmt.Errorf("could not create IAM client: %v", err)
}
resourceName := fmt.Sprintf("projects/%s/serviceAccounts/%s", project, serviceAccount)
resp, err := iamClient.Projects.ServiceAccounts.SignJwt(resourceName, jwtReq).Do()
if err != nil {
return "", fmt.Errorf("unable to sign JWT for %s using given Vault credentials: %v", resourceName, err)
}
return resp.SignedJwt, nil
}
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
role, ok := m["role"]
if !ok {
return nil, errors.New("role is required")
}
mount, ok := m["mount"]
if !ok {
mount = "gcp"
}
loginToken, err := getSignedJwt(role, m)
if err != nil {
return nil, err
}
path := fmt.Sprintf("auth/%s/login", mount)
secret, err := c.Logical().Write(
path,
map[string]interface{}{
"role": role,
"jwt": loginToken,
})
if err != nil {
return nil, err
}
if secret == nil {
return nil, fmt.Errorf("empty response from credential provider")
}
return secret, nil
}
func (h *CLIHandler) Help() string {
help := `
Usage: vault login -method=gcp [CONFIG K=V...]
The GCP credential provider allows you to authenticate with
a GCP IAM service account key. To use it, you provide a valid GCP IAM
credential JSON either explicitly on the command line (not recommended),
or through any GCP Application Default Credentials.
Authenticate using Application Default Credentials:
Example: vault login -method=gcp role=my-iam-role
Authenticate using explicitly passed-in credentials:
Example:
vault login -method=gcp role=my-iam-role -credentials=@path/to/creds role=my-iam-role
This tool generates a signed JWT signed using the given credentials.
Configuration:
role
Required. The name of the role you're requesting a token for.
mount=gcp
This is usually provided via the -path flag in the "vault login" command,
but it can be specified here as well. If specified here, it takes
precedence over the value for -path.
credentials=<string>
Explicitly specified GCP credentials in JSON string format (not recommended)
jwt_exp=<minutes>
Time until the generated JWT expires in minutes.
The given IAM role will have a max_jwt_exp field, the
time in minutes that all valid authentication JWTs
must expire within (from time of authentication).
Defaults to 15 minutes, the default max_jwt_exp for a role.
Must be less than an hour.
service_account=<string>
Service account to generate a JWT for. Defaults to credentials
"client_email" if "credentials" specified and this value is not.
The actual credential must have the "iam.serviceAccounts.signJWT"
permissions on this service account.
project=<string>
Project for the service account who will be authenticating to Vault.
Defaults to the credential's "project_id" (if credentials are specified)."
`
return strings.TrimSpace(help)
}