Skip to content

pelican token create emits misleading warnings and errors concerning issuers #2941

@brianaydemir

Description

@brianaydemir

Narrative

I'm a naïve user. I just want a token:

$ pelican token create osdf://ospool/ap40/data --read
WARNING[2026-01-05T08:38:39-06:00] Unable to get JWKS from issuer URL https://osg-htc.org/ospool: Error getting JWKS URL from issuer URL: failed to unmarshal openid-configuration for issuer https://osg-htc.org/ospool: json: cannot unmarshal array into Go value of type string; skipping 
Error: unable to determine issuer for resource osdf://ospool/ap40/data; you may need to re-run with '--issuer <issuer URL>' to specify an issuer: none of the issuers discovered at the director match your signing key; issuers that were checked: https://osg-htc.org/ospool
Usage:
  pelican token create <pelican-url> [flags]

Examples:
To create a read/write token for /some/namespace/path in OSDF: pelican token create --read --write pelican://osg-htc.org/some/namespace/path

Flags:
  -a, --audience string         Specify the token's 'audience/aud' claim. If not provided, the equivalent 'any' audience for the selected profile will be used (e.g. 'https://wlcg.cern.ch/jwt/v1/any' for the 'wlcg' profile).
  -h, --help                    help for create
  -i, --issuer string           Set the token's 'issuer/iss' claim. If not provided, the issuer will be discovered via the Director.
  -l, --lifetime int            Set the token's lifetime in seconds. (default 1200)
  -m, --modify                  Indicate the requested token should provide the ability to modify/delete the specified resource.
  -k, --private-key string      Path to the private key used to sign the token. If not provided, Pelican will look for the private key in the default location pointed to by the 'IssuerKeysDirectory' config parameter.
  -p, --profile string          Create a token with a specific JWT profile. Accepted values are scitokens2 and wlcg. (default "wlcg")
      --raw-claim stringArray   Set claims to be added to the token. Format: <claim_key>=<claim_value>. 
      --raw-scope stringArray   Set non-typical values for the token's 'scope' claim. Scopes should be space-separated, e.g. 'storage.read:/ storage.create:/'.
  -r, --read                    Indicate the requested token should provide the ability to read the specified resource.
      --scope-path string       Specify the path to use when creating the token's scopes. This should generally be the object path without the namespace prefix.
  -s, --stage                   Indicate the requested token should provide the ability to stage the specified resource.
      --subject string          Set token's 'subject/sub' claim. If not provided, the current user will be used as the default subject.
  -w, --write                   Indicate the requested token should provide the ability to create/write the specified resource. Does not grant the ability to overwrite/modify existing resources.

Global Flags:
      --config string       config file (default is $HOME/.config/pelican/pelican.yaml)
  -d, --debug               Enable debug log messages
  -f, --federation string   Pelican federation to utilize
      --json                output results in JSON format
  -L, --log string          Specified log output file
      --version             Print the version and exit

ERROR[2026-01-05T08:38:39-06:00] Fatal error occurred at the start of the program. Cleanup started: unable to determine issuer for resource osdf://ospool/ap40/data; you may need to re-run with '--issuer <issuer URL>' to specify an issuer: none of the issuers discovered at the director match your signing key; issuers that were checked: https://osg-htc.org/ospool

This impressive amount of text completely obscures my actual mistake: "none of the issuers discovered at the director match your signing key".

  • "unable to determine issuer for resource osdf://ospool/ap40/data" is emitted twice, and it reads like a lie both times, by the client's own admission: "issuers that were checked: https://osg-htc.org/ospool".

  • The initial warning that "Unable to get JWKS from issuer URL https://osg-htc.org/ospool" is actually a straight up bug in the client.

Details

Pelican version:

Version: 7.22.0
Build Date: 2025-12-19T19:00:13Z
Build Commit: 074a9766f8c06e281c4fbfeb98a205f021288b56
Built By: goreleaser

Problem the First

The "Unable to get JWKS from issuer URL" warning comes from

pelican/cmd/token.go

Lines 157 to 163 in c8edb77

// Comb through the JWKS from each issuer to find which matches the signing key
for _, issuer := range directorInfo.XPelAuthHdr.Issuers {
remoteJWKS, err := server_utils.GetJWKSFromIssUrl(issuer.String())
if err != nil {
log.Warningf("Unable to get JWKS from issuer URL %s: %v; skipping", issuer, err)
continue
}

Tracing backwards through the rest of the warning message and the code, we eventually arrive at

var openIDCfgMap map[string]string
err = json.Unmarshal(body, &openIDCfgMap)
if err != nil {
return "", errors.Wrapf(err, "failed to unmarshal openid-configuration for issuer %s", issuerUrl)
}

The type of openIDCfgMap is simply incorrect. A quick check of https://osg-htc.org/ospool/.well-known/openid-configuration (or https://cilogon.org/.well-known/openid-configuration, or what have you) reveals that some of the string keys can indeed have array values:

{  
   "issuer":"https://osg-htc.org/ospool",
   "jwks_uri":"https://osg-htc.org/ospool/oauth2/certs",
   "token_endpoint": "https://osdf-ospool-issuer.osgdev.chtc.io/scitokens-server/token",
   "userinfo_endpoint": "https://osdf-ospool-issuer.osgdev.chtc.io/scitokens-server/userinfo",
   "registration_endpoint": "https://osdf-ospool-issuer.osgdev.chtc.io/scitokens-server/oidc-cm",
   "device_authorization_endpoint": "https://osdf-ospool-issuer.osgdev.chtc.io/scitokens-server/device_authorization",
   "authorization_endpoint": "https://osdf-ospool-issuer.osgdev.chtc.io/scitokens-server/authorize",
   "token_endpoint_auth_methods_supported": [
      "client_secret_post",
      "client_secret_basic"
   ],
   "grant_types_supported": [
      "authorization_code",
      "refresh_token",
      "urn:ietf:params:oauth:grant-type:device_code"
   ]
}

Problem the Second

The "unable to determine issuer for resource" error comes from

pelican/cmd/token.go

Lines 298 to 304 in c8edb77

if issuer == "" {
// If no issuer is provided, try to discover it from the info we previously obtained
// from the Director
issuer, err = getIssuer(directorInfo, kidSet)
if err != nil {
return errors.Wrapf(err, "unable to determine issuer for resource %s; you may need to re-run with '--issuer <issuer URL>' to specify an issuer", rawUrl)
}

which would seem to take as through the flow in the previous problem, ending at

pelican/cmd/token.go

Lines 184 to 185 in c8edb77

return "", errors.Errorf("none of the issuers discovered at the director match your signing key; issuers that were checked: %s",
combineUrls(directorInfo.XPelAuthHdr.Issuers))

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingclientIssue affecting the OSDF client

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions