@@ -9,9 +9,12 @@ import (
9
9
"crypto/tls"
10
10
"crypto/x509"
11
11
"encoding/asn1"
12
+ "encoding/json"
12
13
"errors"
13
14
"fmt"
15
+ "io/ioutil"
14
16
"net/http"
17
+ "net/url"
15
18
"strings"
16
19
17
20
"github.com/hashicorp/cap/jwt"
@@ -174,6 +177,91 @@ func (b *jwtAuthBackend) config(ctx context.Context, s logical.Storage) (*jwtCon
174
177
return config , nil
175
178
}
176
179
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
+
177
265
func (b * jwtAuthBackend ) pathConfigRead (ctx context.Context , req * logical.Request , d * framework.FieldData ) (* logical.Response , error ) {
178
266
config , err := b .config (ctx , req .Storage )
179
267
if err != nil {
@@ -502,6 +590,9 @@ type jwtConfig struct {
502
590
UnsupportedCriticalCertExtensions []string `json:"unsupported_critical_cert_extensions"`
503
591
504
592
ParsedJWTPubKeys []crypto.PublicKey `json:"-"`
593
+ // These are looked up from OIDCDiscoveryURL when needed
594
+ OIDCDeviceAuthURL string `json:"-"`
595
+ OIDCTokenURL string `json:"-"`
505
596
}
506
597
507
598
const (
0 commit comments