@@ -9,9 +9,12 @@ import (
99 "crypto/tls"
1010 "crypto/x509"
1111 "encoding/asn1"
12+ "encoding/json"
1213 "errors"
1314 "fmt"
15+ "io/ioutil"
1416 "net/http"
17+ "net/url"
1518 "strings"
1619
1720 "github.com/hashicorp/cap/jwt"
@@ -174,6 +177,91 @@ func (b *jwtAuthBackend) config(ctx context.Context, s logical.Storage) (*jwtCon
174177 return config , nil
175178}
176179
180+ func contactIssuer (ctx context.Context , uri string , data * url.Values , ignoreBad bool ) ([]byte , error ) {
181+ var req * http.Request
182+ var err error
183+ if data == nil {
184+ req , err = http .NewRequest ("GET" , uri , nil )
185+ } else {
186+ req , err = http .NewRequest ("POST" , uri , strings .NewReader (data .Encode ()))
187+ }
188+ if err != nil {
189+ return nil , err
190+ }
191+ if data != nil {
192+ req .Header .Add ("Content-Type" , "application/x-www-form-urlencoded" )
193+ }
194+
195+ client , ok := ctx .Value (oauth2 .HTTPClient ).(* http.Client )
196+ if ! ok {
197+ client = http .DefaultClient
198+ }
199+ resp , err := client .Do (req .WithContext (ctx ))
200+ if err != nil {
201+ return nil , nil
202+ }
203+ defer resp .Body .Close ()
204+
205+ body , err := ioutil .ReadAll (resp .Body )
206+ if err != nil {
207+ return nil , nil
208+ }
209+
210+ if resp .StatusCode != http .StatusOK && (! ignoreBad || resp .StatusCode != http .StatusBadRequest ) {
211+ return nil , fmt .Errorf ("%s: %s" , resp .Status , body )
212+ }
213+
214+ return body , nil
215+ }
216+
217+ // Discover the device_authorization_endpoint URL and store it in the config
218+ // This should be in coreos/go-oidc but they don't yet support device flow
219+ // At the same time, look up token_endpoint and store it as well
220+ // Returns nil on success, otherwise returns an error
221+ func (b * jwtAuthBackend ) configDeviceAuthURL (ctx context.Context , s logical.Storage ) error {
222+ config , err := b .config (ctx , s )
223+ if err != nil {
224+ return err
225+ }
226+
227+ b .l .Lock ()
228+ defer b .l .Unlock ()
229+
230+ if config .OIDCDeviceAuthURL != "" {
231+ if config .OIDCDeviceAuthURL == "N/A" {
232+ return fmt .Errorf ("no device auth endpoint url discovered" )
233+ }
234+ return nil
235+ }
236+
237+ caCtx , err := b .createCAContext (b .providerCtx , config .OIDCDiscoveryCAPEM )
238+ if err != nil {
239+ return errwrap .Wrapf ("error creating context for device auth: {{err}}" , err )
240+ }
241+
242+ issuer := config .OIDCDiscoveryURL
243+
244+ wellKnown := strings .TrimSuffix (issuer , "/" ) + "/.well-known/openid-configuration"
245+ body , err := contactIssuer (caCtx , wellKnown , nil , false )
246+ if err != nil {
247+ return errwrap .Wrapf ("error reading issuer config: {{err}}" , err )
248+ }
249+
250+ var daj struct {
251+ DeviceAuthURL string `json:"device_authorization_endpoint"`
252+ TokenURL string `json:"token_endpoint"`
253+ }
254+ err = json .Unmarshal (body , & daj )
255+ if err != nil || daj .DeviceAuthURL == "" {
256+ b .cachedConfig .OIDCDeviceAuthURL = "N/A"
257+ return fmt .Errorf ("no device auth endpoint url discovered" )
258+ }
259+
260+ b .cachedConfig .OIDCDeviceAuthURL = daj .DeviceAuthURL
261+ b .cachedConfig .OIDCTokenURL = daj .TokenURL
262+ return nil
263+ }
264+
177265func (b * jwtAuthBackend ) pathConfigRead (ctx context.Context , req * logical.Request , d * framework.FieldData ) (* logical.Response , error ) {
178266 config , err := b .config (ctx , req .Storage )
179267 if err != nil {
@@ -502,6 +590,9 @@ type jwtConfig struct {
502590 UnsupportedCriticalCertExtensions []string `json:"unsupported_critical_cert_extensions"`
503591
504592 ParsedJWTPubKeys []crypto.PublicKey `json:"-"`
593+ // These are looked up from OIDCDiscoveryURL when needed
594+ OIDCDeviceAuthURL string `json:"-"`
595+ OIDCTokenURL string `json:"-"`
505596}
506597
507598const (
0 commit comments