@@ -5,8 +5,12 @@ import (
55 "crypto"
66 "crypto/tls"
77 "crypto/x509"
8+ "encoding/json"
89 "errors"
10+ "fmt"
11+ "io/ioutil"
912 "net/http"
13+ "net/url"
1014 "strings"
1115
1216 "github.com/hashicorp/cap/jwt"
@@ -151,6 +155,91 @@ func (b *jwtAuthBackend) config(ctx context.Context, s logical.Storage) (*jwtCon
151155 return config , nil
152156}
153157
158+ func contactIssuer (ctx context.Context , uri string , data * url.Values , ignoreBad bool ) ([]byte , error ) {
159+ var req * http.Request
160+ var err error
161+ if data == nil {
162+ req , err = http .NewRequest ("GET" , uri , nil )
163+ } else {
164+ req , err = http .NewRequest ("POST" , uri , strings .NewReader (data .Encode ()))
165+ }
166+ if err != nil {
167+ return nil , nil
168+ }
169+ if data != nil {
170+ req .Header .Add ("Content-Type" , "application/x-www-form-urlencoded" )
171+ }
172+
173+ client , ok := ctx .Value (oauth2 .HTTPClient ).(* http.Client )
174+ if ! ok {
175+ client = http .DefaultClient
176+ }
177+ resp , err := client .Do (req .WithContext (ctx ))
178+ if err != nil {
179+ return nil , nil
180+ }
181+ defer resp .Body .Close ()
182+
183+ body , err := ioutil .ReadAll (resp .Body )
184+ if err != nil {
185+ return nil , nil
186+ }
187+
188+ if resp .StatusCode != http .StatusOK && (! ignoreBad || resp .StatusCode != http .StatusBadRequest ) {
189+ return nil , fmt .Errorf ("%s: %s" , resp .Status , body )
190+ }
191+
192+ return body , nil
193+ }
194+
195+ // Discover the device_authorization_endpoint URL and store it in the config
196+ // This should be in coreos/go-oidc but they don't yet support device flow
197+ // At the same time, look up token_endpoint and store it as well
198+ // Returns nil on success, otherwise returns an error
199+ func (b * jwtAuthBackend ) configDeviceAuthURL (ctx context.Context , s logical.Storage ) (error ) {
200+ config , err := b .config (ctx , s )
201+ if err != nil {
202+ return err
203+ }
204+
205+ b .l .Lock ()
206+ defer b .l .Unlock ()
207+
208+ if config .OIDCDeviceAuthURL != "" {
209+ if config .OIDCDeviceAuthURL == "N/A" {
210+ return fmt .Errorf ("no device auth endpoint url discovered" )
211+ }
212+ return nil
213+ }
214+
215+ caCtx , err := b .createCAContext (b .providerCtx , config .OIDCDiscoveryCAPEM )
216+ if err != nil {
217+ return errwrap .Wrapf ("error creating context for device auth: {{err}}" , err )
218+ }
219+
220+ issuer := config .OIDCDiscoveryURL
221+
222+ wellKnown := strings .TrimSuffix (issuer , "/" ) + "/.well-known/openid-configuration"
223+ body , err := contactIssuer (caCtx , wellKnown , nil , false )
224+ if err != nil {
225+ return errwrap .Wrapf ("error reading issuer config: {{err}}" , err )
226+ }
227+
228+ var daj struct {
229+ DeviceAuthURL string `json:"device_authorization_endpoint"`
230+ TokenURL string `json:"token_endpoint"`
231+ }
232+ err = json .Unmarshal (body , & daj )
233+ if err != nil || daj .DeviceAuthURL == "" {
234+ b .cachedConfig .OIDCDeviceAuthURL = "N/A"
235+ return fmt .Errorf ("no device auth endpoint url discovered" )
236+ }
237+
238+ b .cachedConfig .OIDCDeviceAuthURL = daj .DeviceAuthURL
239+ b .cachedConfig .OIDCTokenURL = daj .TokenURL
240+ return nil
241+ }
242+
154243func (b * jwtAuthBackend ) pathConfigRead (ctx context.Context , req * logical.Request , d * framework.FieldData ) (* logical.Response , error ) {
155244 config , err := b .config (ctx , req .Storage )
156245 if err != nil {
@@ -405,6 +494,9 @@ type jwtConfig struct {
405494 NamespaceInState bool `json:"namespace_in_state"`
406495
407496 ParsedJWTPubKeys []crypto.PublicKey `json:"-"`
497+ // These are looked up from OIDCDiscoveryURL when needed
498+ OIDCDeviceAuthURL string `json:"-"`
499+ OIDCTokenURL string `json:"-"`
408500}
409501
410502const (
0 commit comments