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