@@ -34,12 +34,13 @@ import (
34
34
"github.com/lestrrat-go/jwx/v2/jwa"
35
35
"github.com/lestrrat-go/jwx/v2/jwk"
36
36
"github.com/lestrrat-go/jwx/v2/jwt"
37
+ "github.com/pkg/errors"
38
+ log "github.com/sirupsen/logrus"
39
+
37
40
"github.com/pelicanplatform/pelican/config"
38
41
"github.com/pelicanplatform/pelican/param"
39
42
"github.com/pelicanplatform/pelican/token_scopes"
40
43
"github.com/pelicanplatform/pelican/utils"
41
- "github.com/pkg/errors"
42
- log "github.com/sirupsen/logrus"
43
44
)
44
45
45
46
type (
@@ -126,7 +127,7 @@ func CreateAdvertiseToken(namespace string) (string, error) {
126
127
// TODO: Need to come back and carefully consider a few naming practices.
127
128
// Here, issuerUrl is actually the registry database url, and not
128
129
// the token issuer url for this namespace
129
- issuerUrl , err := GetRegistryIssuerURL (namespace )
130
+ issuerUrl , err := GetNSIssuerURL (namespace )
130
131
if err != nil {
131
132
return "" , err
132
133
}
@@ -170,7 +171,12 @@ func CreateAdvertiseToken(namespace string) (string, error) {
170
171
// see if the entity is authorized to advertise an origin for the
171
172
// namespace
172
173
func VerifyAdvertiseToken (ctx context.Context , token , namespace string ) (bool , error ) {
173
- issuerUrl , err := GetRegistryIssuerURL (namespace )
174
+ issuerUrl , err := GetNSIssuerURL (namespace )
175
+ if err != nil {
176
+ return false , err
177
+ }
178
+
179
+ keyLoc , err := GetJWKSURLFromIssuerURL (issuerUrl )
174
180
if err != nil {
175
181
return false , err
176
182
}
@@ -201,7 +207,7 @@ func VerifyAdvertiseToken(ctx context.Context, token, namespace string) (bool, e
201
207
if ar == nil {
202
208
ar = jwk .NewCache (ctx )
203
209
client := & http.Client {Transport : config .GetTransport ()}
204
- if err = ar .Register (issuerUrl , jwk .WithMinRefreshInterval (15 * time .Minute ), jwk .WithHTTPClient (client )); err != nil {
210
+ if err = ar .Register (keyLoc , jwk .WithMinRefreshInterval (15 * time .Minute ), jwk .WithHTTPClient (client )); err != nil {
205
211
return false , err
206
212
}
207
213
namespaceKeysMutex .Lock ()
@@ -215,8 +221,8 @@ func VerifyAdvertiseToken(ctx context.Context, token, namespace string) (bool, e
215
221
}
216
222
217
223
}
218
- log .Debugln ("Attempting to fetch keys from " , issuerUrl )
219
- keyset , err := ar .Get (ctx , issuerUrl )
224
+ log .Debugln ("Attempting to fetch keys from " , keyLoc )
225
+ keyset , err := ar .Get (ctx , keyLoc )
220
226
221
227
if err != nil {
222
228
return false , err
@@ -323,18 +329,73 @@ func VerifyDirectorTestReportToken(strToken string) (bool, error) {
323
329
return false , nil
324
330
}
325
331
326
- func GetRegistryIssuerURL (prefix string ) (string , error ) {
327
- namespace_url_string := param .Federation_RegistryUrl .GetString ()
328
- if namespace_url_string == "" {
329
- return "" , errors .New ("Namespace URL is not set" )
332
+ // For a given prefix, get the prefix's issuer URL, where we consider that the openid endpoint
333
+ // we use to look up a key location. Note that this is NOT the same as the issuer key -- to
334
+ // find that, follow openid-style discovery using the issuer URL as a base.
335
+ func GetNSIssuerURL (prefix string ) (string , error ) {
336
+ if prefix == "" || ! strings .HasPrefix (prefix , "/" ) {
337
+ return "" , errors .New (fmt .Sprintf ("the prefix \" %s\" is invalid" , prefix ))
338
+ }
339
+ registryUrlStr := param .Federation_RegistryUrl .GetString ()
340
+ if registryUrlStr == "" {
341
+ return "" , errors .New ("federation registry URL is not set and was not discovered" )
330
342
}
331
- namespace_url , err := url .Parse (namespace_url_string )
343
+ registryUrl , err := url .Parse (registryUrlStr )
332
344
if err != nil {
333
345
return "" , err
334
346
}
335
- namespace_url .Path , err = url .JoinPath (namespace_url .Path , "api" , "v1.0" , "registry" , prefix , ".well-known" , "issuer.jwks" )
347
+
348
+ registryUrl .Path , err = url .JoinPath (registryUrl .Path , "api" , "v1.0" , "registry" , prefix )
349
+
336
350
if err != nil {
337
- return "" , err
351
+ return "" , errors .Wrapf (err , "failed to construct openid-configuration lookup URL for prefix %s" , prefix )
352
+ }
353
+ return registryUrl .String (), nil
354
+ }
355
+
356
+ // Given an issuer url, lookup the JWKS URL from the openid-configuration
357
+ // For example, if the issuer URL is https://registry.com:8446/api/v1.0/registry/test-namespace,
358
+ // this function will return the key indicated by the openid-configuration JSON hosted at
359
+ // https://registry.com:8446/api/v1.0/registry/test-namespace/.well-known/openid-configuration.
360
+ func GetJWKSURLFromIssuerURL (issuerUrl string ) (string , error ) {
361
+ // Get/parse the openid-configuration JSON to lookup key location
362
+ issOpenIDUrl , err := url .Parse (issuerUrl )
363
+ if err != nil {
364
+ return "" , errors .Wrap (err , "failed to parse issuer URL" )
365
+ }
366
+ issOpenIDUrl .Path , _ = url .JoinPath (issOpenIDUrl .Path , ".well-known" , "openid-configuration" )
367
+
368
+ client := & http.Client {Transport : config .GetTransport ()}
369
+ openIDCfg , err := client .Get (issOpenIDUrl .String ())
370
+ if err != nil {
371
+ return "" , errors .Wrapf (err , "failed to lookup openid-configuration for issuer %s" , issuerUrl )
372
+ }
373
+ defer openIDCfg .Body .Close ()
374
+
375
+ // If we hit an old registry, it may not have the openid-configuration. In that case, we fallback to the old
376
+ // behavior of looking for the key directly at the issuer URL.
377
+ if openIDCfg .StatusCode == http .StatusNotFound {
378
+ oldKeyLoc , err := url .JoinPath (issuerUrl , ".well-known" , "issuer.jwks" )
379
+ if err != nil {
380
+ return "" , errors .Wrapf (err , "failed to construct key lookup URL for issuer %s" , issuerUrl )
381
+ }
382
+ return oldKeyLoc , nil
383
+ }
384
+
385
+ body , err := io .ReadAll (openIDCfg .Body )
386
+ if err != nil {
387
+ return "" , errors .Wrapf (err , "failed to read response body from %s" , issuerUrl )
388
+ }
389
+
390
+ var openIDCfgMap map [string ]string
391
+ err = json .Unmarshal (body , & openIDCfgMap )
392
+ if err != nil {
393
+ return "" , errors .Wrapf (err , "failed to unmarshal openid-configuration for issuer %s" , issuerUrl )
394
+ }
395
+
396
+ if keyLoc , ok := openIDCfgMap ["jwks_uri" ]; ok {
397
+ return keyLoc , nil
398
+ } else {
399
+ return "" , errors .New (fmt .Sprintf ("no key found in openid-configuration for issuer %s" , issuerUrl ))
338
400
}
339
- return namespace_url .String (), nil
340
401
}
0 commit comments