66 "io/ioutil"
77 "net"
88 "net/http"
9+ "net/url"
910 "os"
1011 "os/exec"
1112 "os/signal"
@@ -26,6 +27,7 @@ const (
2627 defaultPort = "8250"
2728 defaultCallbackHost = "localhost"
2829 defaultCallbackMethod = "http"
30+ defaultCallbackMode = "client"
2931 defaultSkipBrowserLaunch = false
3032)
3133
@@ -63,19 +65,42 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
6365 port = defaultPort
6466 }
6567
68+ var vaultURL * url.URL
69+ callbackMode , ok := m ["callbackmode" ]
70+ if ! ok {
71+ callbackMode = defaultCallbackMode
72+ } else if callbackMode == "direct" {
73+ vaultAddr := os .Getenv ("VAULT_ADDR" )
74+ if vaultAddr != "" {
75+ vaultURL , _ = url .Parse (vaultAddr )
76+ }
77+ }
78+
6679 callbackHost , ok := m ["callbackhost" ]
6780 if ! ok {
68- callbackHost = defaultCallbackHost
81+ if vaultURL != nil {
82+ callbackHost = vaultURL .Hostname ()
83+ } else {
84+ callbackHost = defaultCallbackHost
85+ }
6986 }
7087
7188 callbackMethod , ok := m ["callbackmethod" ]
7289 if ! ok {
73- callbackMethod = defaultCallbackMethod
90+ if vaultURL != nil {
91+ callbackMethod = vaultURL .Scheme
92+ } else {
93+ callbackMethod = defaultCallbackMethod
94+ }
7495 }
7596
7697 callbackPort , ok := m ["callbackport" ]
7798 if ! ok {
78- callbackPort = port
99+ if vaultURL != nil {
100+ callbackPort = vaultURL .Port () + "/v1/auth/" + mount
101+ } else {
102+ callbackPort = port
103+ }
79104 }
80105
81106 skipBrowserLaunch := defaultSkipBrowserLaunch
@@ -89,19 +114,47 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
89114
90115 role := m ["role" ]
91116
92- authURL , clientNonce , err := fetchAuthURL (c , role , mount , callbackPort , callbackMethod , callbackHost )
117+ authURL , clientNonce , secret , err := fetchAuthURL (c , role , mount , callbackPort , callbackMethod , callbackHost )
93118 if err != nil {
94119 return nil , err
95120 }
96121
97- // Set up callback handler
98- http .HandleFunc ("/oidc/callback" , callbackHandler (c , mount , clientNonce , doneCh ))
122+ var pollInterval string
123+ var interval int
124+ var state string
125+ var listener net.Listener
99126
100- listener , err := net .Listen ("tcp" , listenAddress + ":" + port )
101- if err != nil {
102- return nil , err
127+ if secret != nil {
128+ pollInterval , _ = secret .Data ["poll_interval" ].(string )
129+ state , _ = secret .Data ["state" ].(string )
130+ }
131+ if callbackMode == "direct" {
132+ if state == "" {
133+ return nil , errors .New ("no state returned in direct callback mode" )
134+ }
135+ if pollInterval == "" {
136+ return nil , errors .New ("no poll_interval returned in direct callback mode" )
137+ }
138+ interval , err = strconv .Atoi (pollInterval )
139+ if err != nil {
140+ return nil , errors .New ("cannot convert poll_interval " + pollInterval + " to integer" )
141+ }
142+ } else {
143+ if state != "" {
144+ return nil , errors .New ("state returned in client callback mode, try direct" )
145+ }
146+ if pollInterval != "" {
147+ return nil , errors .New ("poll_interval returned in client callback mode" )
148+ }
149+ // Set up callback handler
150+ http .HandleFunc ("/oidc/callback" , callbackHandler (c , mount , clientNonce , doneCh ))
151+
152+ listener , err := net .Listen ("tcp" , listenAddress + ":" + port )
153+ if err != nil {
154+ return nil , err
155+ }
156+ defer listener .Close ()
103157 }
104- defer listener .Close ()
105158
106159 // Open the default browser to the callback URL.
107160 if ! skipBrowserLaunch {
@@ -114,6 +167,26 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
114167 }
115168 fmt .Fprintf (os .Stderr , "Waiting for OIDC authentication to complete...\n " )
116169
170+ if callbackMode == "direct" {
171+ data := map [string ]interface {}{
172+ "state" : state ,
173+ "client_nonce" : clientNonce ,
174+ }
175+ pollUrl := fmt .Sprintf ("auth/%s/oidc/poll" , mount )
176+ for {
177+ time .Sleep (time .Duration (interval ) * time .Second )
178+
179+ secret , err := c .Logical ().Write (pollUrl , data )
180+ if err == nil {
181+ return secret , nil
182+ }
183+ if ! strings .HasSuffix (err .Error (), "authorization_pending" ) {
184+ return nil , err
185+ }
186+ // authorization is pending, try again
187+ }
188+ }
189+
117190 // Start local server
118191 go func () {
119192 err := http .Serve (listener , nil )
@@ -180,12 +253,12 @@ func callbackHandler(c *api.Client, mount string, clientNonce string, doneCh cha
180253 }
181254}
182255
183- func fetchAuthURL (c * api.Client , role , mount , callbackport string , callbackMethod string , callbackHost string ) (string , string , error ) {
256+ func fetchAuthURL (c * api.Client , role , mount , callbackport string , callbackMethod string , callbackHost string ) (string , string , * api. Secret , error ) {
184257 var authURL string
185258
186259 clientNonce , err := base62 .Random (20 )
187260 if err != nil {
188- return "" , "" , err
261+ return "" , "" , nil , err
189262 }
190263
191264 redirectURI := fmt .Sprintf ("%s://%s:%s/oidc/callback" , callbackMethod , callbackHost , callbackport )
@@ -197,18 +270,18 @@ func fetchAuthURL(c *api.Client, role, mount, callbackport string, callbackMetho
197270
198271 secret , err := c .Logical ().Write (fmt .Sprintf ("auth/%s/oidc/auth_url" , mount ), data )
199272 if err != nil {
200- return "" , "" , err
273+ return "" , "" , nil , err
201274 }
202275
203276 if secret != nil {
204277 authURL = secret .Data ["auth_url" ].(string )
205278 }
206279
207280 if authURL == "" {
208- return "" , "" , fmt .Errorf ("Unable to authorize role %q with redirect_uri %q. Check Vault logs for more information." , role , redirectURI )
281+ return "" , "" , nil , fmt .Errorf ("Unable to authorize role %q with redirect_uri %q. Check Vault logs for more information." , role , redirectURI )
209282 }
210283
211- return authURL , clientNonce , nil
284+ return authURL , clientNonce , secret , nil
212285}
213286
214287// isWSL tests if the binary is being run in Windows Subsystem for Linux
@@ -295,28 +368,38 @@ Usage: vault login -method=oidc [CONFIG K=V...]
295368
296369 https://accounts.google.com/o/oauth2/v2/...
297370
298- The default browser will be opened for the user to complete the login. Alternatively,
299- the user may visit the provided URL directly.
371+ The default browser will be opened for the user to complete the login.
372+ Alternatively, the user may visit the provided URL directly.
300373
301374Configuration:
302375
303376 role=<string>
304377 Vault role of type "OIDC" to use for authentication.
305378
379+ callbackmode=<string>
380+ Mode of callback: "direct" for direct connection to Vault or "client"
381+ for connection to command line client (default: client).
382+
306383 listenaddress=<string>
307- Optional address to bind the OIDC callback listener to (default: localhost).
384+ Optional address to bind the OIDC callback listener to in client callback
385+ mode (default: localhost).
308386
309387 port=<string>
310- Optional localhost port to use for OIDC callback (default: 8250).
388+ Optional localhost port to use for OIDC callback in client callback mode
389+ (default: 8250).
311390
312391 callbackmethod=<string>
313- Optional method to to use in OIDC redirect_uri (default: http).
392+ Optional method to use in OIDC redirect_uri (default: the method from
393+ $VAULT_ADDR in direct callback mode, else http)
314394
315395 callbackhost=<string>
316- Optional callback host address to use in OIDC redirect_uri (default: localhost).
396+ Optional callback host address to use in OIDC redirect_uri (default:
397+ the host from $VAULT_ADDR in direct callback mode, else localhost).
317398
318399 callbackport=<string>
319- Optional port to to use in OIDC redirect_uri (default: the value set for port).
400+ Optional port to use in OIDC redirect_uri (default: the value set for
401+ port in client callback mode, else the port from $VAULT_ADDR with an
402+ added /v1/auth/<path> where <path> is from the login -path option).
320403
321404 skip_browser=<bool>
322405 Toggle the automatic launching of the default browser to the login URL. (default: false).
0 commit comments