8
8
"fmt"
9
9
"net"
10
10
"net/http"
11
+ "net/url"
11
12
"os"
12
13
"os/signal"
13
14
"path"
@@ -27,9 +28,11 @@ const (
27
28
defaultPort = "8250"
28
29
defaultCallbackHost = "localhost"
29
30
defaultCallbackMethod = "http"
31
+ defaultCallbackMode = "client"
30
32
31
33
FieldCallbackHost = "callbackhost"
32
34
FieldCallbackMethod = "callbackmethod"
35
+ FieldCallbackMode = "callbackmode"
33
36
FieldListenAddress = "listenaddress"
34
37
FieldPort = "port"
35
38
FieldCallbackPort = "callbackport"
@@ -69,19 +72,42 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
69
72
port = defaultPort
70
73
}
71
74
75
+ var vaultURL * url.URL
76
+ callbackMode , ok := m [FieldCallbackMode ]
77
+ if ! ok {
78
+ callbackMode = defaultCallbackMode
79
+ } else if callbackMode == "direct" {
80
+ vaultAddr := os .Getenv ("VAULT_ADDR" )
81
+ if vaultAddr != "" {
82
+ vaultURL , _ = url .Parse (vaultAddr )
83
+ }
84
+ }
85
+
72
86
callbackHost , ok := m [FieldCallbackHost ]
73
87
if ! ok {
74
- callbackHost = defaultCallbackHost
88
+ if vaultURL != nil {
89
+ callbackHost = vaultURL .Hostname ()
90
+ } else {
91
+ callbackHost = defaultCallbackHost
92
+ }
75
93
}
76
94
77
95
callbackMethod , ok := m [FieldCallbackMethod ]
78
96
if ! ok {
79
- callbackMethod = defaultCallbackMethod
97
+ if vaultURL != nil {
98
+ callbackMethod = vaultURL .Scheme
99
+ } else {
100
+ callbackMethod = defaultCallbackMethod
101
+ }
80
102
}
81
103
82
104
callbackPort , ok := m [FieldCallbackPort ]
83
105
if ! ok {
84
- callbackPort = port
106
+ if vaultURL != nil {
107
+ callbackPort = vaultURL .Port () + "/v1/auth/" + mount
108
+ } else {
109
+ callbackPort = port
110
+ }
85
111
}
86
112
87
113
parseBool := func (f string , d bool ) (bool , error ) {
@@ -115,20 +141,49 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
115
141
116
142
role := m ["role" ]
117
143
118
- authURL , clientNonce , err := fetchAuthURL (c , role , mount , callbackPort , callbackMethod , callbackHost )
144
+ authURL , clientNonce , secret , err := fetchAuthURL (c , role , mount , callbackPort , callbackMethod , callbackHost )
119
145
if err != nil {
120
146
return nil , err
121
147
}
122
148
123
- // Set up callback handler
124
149
doneCh := make (chan loginResp )
125
- http .HandleFunc ("/oidc/callback" , callbackHandler (c , mount , clientNonce , doneCh ))
126
150
127
- listener , err := net .Listen ("tcp" , listenAddress + ":" + port )
128
- if err != nil {
129
- return nil , err
151
+ var pollInterval string
152
+ var interval int
153
+ var state string
154
+ var listener net.Listener
155
+
156
+ if secret != nil {
157
+ pollInterval , _ = secret .Data ["poll_interval" ].(string )
158
+ state , _ = secret .Data ["state" ].(string )
159
+ }
160
+ if callbackMode == "direct" {
161
+ if state == "" {
162
+ return nil , errors .New ("no state returned in direct callback mode" )
163
+ }
164
+ if pollInterval == "" {
165
+ return nil , errors .New ("no poll_interval returned in direct callback mode" )
166
+ }
167
+ interval , err = strconv .Atoi (pollInterval )
168
+ if err != nil {
169
+ return nil , errors .New ("cannot convert poll_interval " + pollInterval + " to integer" )
170
+ }
171
+ } else {
172
+ if state != "" {
173
+ return nil , errors .New ("state returned in client callback mode, try direct" )
174
+ }
175
+ if pollInterval != "" {
176
+ return nil , errors .New ("poll_interval returned in client callback mode" )
177
+ }
178
+ // Set up callback handler
179
+ http .HandleFunc ("/oidc/callback" , callbackHandler (c , mount , clientNonce , doneCh ))
180
+
181
+ listener , err = net .Listen ("tcp" , listenAddress + ":" + port )
182
+ if err != nil {
183
+ return nil , err
184
+ }
185
+ defer listener .Close ()
130
186
}
131
- defer listener .Close ()
132
187
133
188
// Open the default browser to the callback URL.
134
189
if ! skipBrowserLaunch {
@@ -144,6 +199,26 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
144
199
}
145
200
fmt .Fprintf (os .Stderr , "Waiting for OIDC authentication to complete...\n " )
146
201
202
+ if callbackMode == "direct" {
203
+ data := map [string ]interface {}{
204
+ "state" : state ,
205
+ "client_nonce" : clientNonce ,
206
+ }
207
+ pollUrl := fmt .Sprintf ("auth/%s/oidc/poll" , mount )
208
+ for {
209
+ time .Sleep (time .Duration (interval ) * time .Second )
210
+
211
+ secret , err := c .Logical ().Write (pollUrl , data )
212
+ if err == nil {
213
+ return secret , nil
214
+ }
215
+ if ! strings .HasSuffix (err .Error (), "authorization_pending" ) {
216
+ return nil , err
217
+ }
218
+ // authorization is pending, try again
219
+ }
220
+ }
221
+
147
222
// Start local server
148
223
go func () {
149
224
err := http .Serve (listener , nil )
@@ -210,12 +285,12 @@ func callbackHandler(c *api.Client, mount string, clientNonce string, doneCh cha
210
285
}
211
286
}
212
287
213
- func fetchAuthURL (c * api.Client , role , mount , callbackPort string , callbackMethod string , callbackHost string ) (string , string , error ) {
288
+ func fetchAuthURL (c * api.Client , role , mount , callbackPort string , callbackMethod string , callbackHost string ) (string , string , * api. Secret , error ) {
214
289
var authURL string
215
290
216
291
clientNonce , err := base62 .Random (20 )
217
292
if err != nil {
218
- return "" , "" , err
293
+ return "" , "" , nil , err
219
294
}
220
295
221
296
redirectURI := fmt .Sprintf ("%s://%s:%s/oidc/callback" , callbackMethod , callbackHost , callbackPort )
@@ -227,18 +302,18 @@ func fetchAuthURL(c *api.Client, role, mount, callbackPort string, callbackMetho
227
302
228
303
secret , err := c .Logical ().Write (fmt .Sprintf ("auth/%s/oidc/auth_url" , mount ), data )
229
304
if err != nil {
230
- return "" , "" , err
305
+ return "" , "" , nil , err
231
306
}
232
307
233
308
if secret != nil {
234
309
authURL = secret .Data ["auth_url" ].(string )
235
310
}
236
311
237
312
if authURL == "" {
238
- return "" , "" , fmt .Errorf ("Unable to authorize role %q with redirect_uri %q. Check Vault logs for more information." , role , redirectURI )
313
+ return "" , "" , nil , fmt .Errorf ("Unable to authorize role %q with redirect_uri %q. Check Vault logs for more information." , role , redirectURI )
239
314
}
240
315
241
- return authURL , clientNonce , nil
316
+ return authURL , clientNonce , secret , nil
242
317
}
243
318
244
319
// parseError converts error from the API into summary and detailed portions.
@@ -292,35 +367,46 @@ Usage: vault login -method=oidc [CONFIG K=V...]
292
367
293
368
https://accounts.google.com/o/oauth2/v2/...
294
369
295
- The default browser will be opened for the user to complete the login. Alternatively,
296
- the user may visit the provided URL directly.
370
+ The default browser will be opened for the user to complete the login.
371
+ Alternatively, the user may visit the provided URL directly.
297
372
298
373
Configuration:
299
374
300
375
role=<string>
301
376
Vault role of type "OIDC" to use for authentication.
302
377
303
378
%s=<string>
304
- Optional address to bind the OIDC callback listener to (default: localhost).
379
+ Mode of callback: "direct" for direct connection to Vault or "client"
380
+ for connection to command line client (default: client).
381
+
382
+ %s=<string>
383
+ Optional address to bind the OIDC callback listener to in client callback
384
+ mode (default: localhost).
305
385
306
386
%s=<string>
307
- Optional localhost port to use for OIDC callback (default: 8250).
387
+ Optional localhost port to use for OIDC callback in client callback mode
388
+ (default: 8250).
308
389
309
390
%s=<string>
310
- Optional method to to use in OIDC redirect_uri (default: http).
391
+ Optional method to use in OIDC redirect_uri (default: the method from
392
+ $VAULT_ADDR in direct callback mode, else http)
311
393
312
394
%s=<string>
313
- Optional callback host address to use in OIDC redirect_uri (default: localhost).
395
+ Optional callback host address to use in OIDC redirect_uri (default:
396
+ the host from $VAULT_ADDR in direct callback mode, else localhost).
314
397
315
398
%s=<string>
316
- Optional port to to use in OIDC redirect_uri (default: the value set for port).
399
+ Optional port to use in OIDC redirect_uri (default: the value set for
400
+ port in client callback mode, else the port from $VAULT_ADDR with an
401
+ added /v1/auth/<path> where <path> is from the login -path option).
317
402
318
403
%s=<bool>
319
404
Toggle the automatic launching of the default browser to the login URL. (default: false).
320
405
321
406
%s=<bool>
322
407
Abort on any error. (default: false).
323
408
` ,
409
+ FieldCallbackMode ,
324
410
FieldListenAddress , FieldPort , FieldCallbackMethod ,
325
411
FieldCallbackHost , FieldCallbackPort , FieldSkipBrowser ,
326
412
FieldAbortOnError ,
0 commit comments