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