@@ -8,9 +8,12 @@ import (
8
8
"crypto"
9
9
"crypto/tls"
10
10
"crypto/x509"
11
+ "encoding/json"
11
12
"errors"
12
13
"fmt"
14
+ "io/ioutil"
13
15
"net/http"
16
+ "net/url"
14
17
"strings"
15
18
16
19
"github.com/hashicorp/cap/jwt"
@@ -168,6 +171,91 @@ func (b *jwtAuthBackend) config(ctx context.Context, s logical.Storage) (*jwtCon
168
171
return config , nil
169
172
}
170
173
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
+
171
259
func (b * jwtAuthBackend ) pathConfigRead (ctx context.Context , req * logical.Request , d * framework.FieldData ) (* logical.Response , error ) {
172
260
config , err := b .config (ctx , req .Storage )
173
261
if err != nil {
@@ -457,6 +545,9 @@ type jwtConfig struct {
457
545
NamespaceInState bool `json:"namespace_in_state"`
458
546
459
547
ParsedJWTPubKeys []crypto.PublicKey `json:"-"`
548
+ // These are looked up from OIDCDiscoveryURL when needed
549
+ OIDCDeviceAuthURL string `json:"-"`
550
+ OIDCTokenURL string `json:"-"`
460
551
}
461
552
462
553
const (
0 commit comments