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