XRootD
XrdAccSciTokens Class Reference
+ Inheritance diagram for XrdAccSciTokens:
+ Collaboration diagram for XrdAccSciTokens:

Public Member Functions

 XrdAccSciTokens (XrdSysLogger *lp, const char *parms, XrdAccAuthorize *chain, XrdOucEnv *envP)
 
virtual ~XrdAccSciTokens ()
 
virtual XrdAccPrivs Access (const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *env) override
 
virtual int Audit (const int accok, const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env=0) override
 
std::string GetConfigFile ()
 
virtual Issuers IssuerList () override
 
virtual int Test (const XrdAccPrivs priv, const Access_Operation oper) override
 
virtual bool Validate (const char *token, std::string &emsg, long long *expT, XrdSecEntity *Entity) override
 
- Public Member Functions inherited from XrdAccAuthorize
 XrdAccAuthorize ()
 Constructor. More...
 
virtual ~XrdAccAuthorize ()
 Destructor. More...
 
- Public Member Functions inherited from XrdSciTokensHelper
 XrdSciTokensHelper ()
 Constructor and Destructor. More...
 
virtual ~XrdSciTokensHelper ()
 
- Public Member Functions inherited from XrdSciTokensMon
 XrdSciTokensMon ()
 
 ~XrdSciTokensMon ()
 
bool Mon_isIO (const Access_Operation oper)
 
void Mon_Report (const XrdSecEntity &Entity, const std::string &subject, const std::string &username)
 

Additional Inherited Members

- Public Types inherited from XrdSciTokensHelper
typedef std::vector< ValidIssuerIssuers
 

Detailed Description

Definition at line 436 of file XrdSciTokensAccess.cc.

Constructor & Destructor Documentation

◆ XrdAccSciTokens()

XrdAccSciTokens::XrdAccSciTokens ( XrdSysLogger lp,
const char *  parms,
XrdAccAuthorize chain,
XrdOucEnv envP 
)
inline

Definition at line 447 of file XrdSciTokensAccess.cc.

447  :
448  m_chain(chain),
449  m_parms(parms ? parms : ""),
450  m_next_clean(monotonic_time() + m_expiry_secs),
451  m_log(lp, "scitokens_")
452  {
453  pthread_rwlock_init(&m_config_lock, nullptr);
454  m_config_lock_initialized = true;
455  m_log.Say("++++++ XrdAccSciTokens: Initialized SciTokens-based authorization.");
456  if (!Config(envP)) {
457  throw std::runtime_error("Failed to configure SciTokens authorization.");
458  }
459  }
void Say(const char *text1, const char *text2=0, const char *txt3=0, const char *text4=0, const char *text5=0, const char *txt6=0)
Definition: XrdSysError.cc:141
XrdOucEnv * envP
Definition: XrdPss.cc:109

References XrdProxy::envP, and XrdSysError::Say().

+ Here is the call graph for this function:

◆ ~XrdAccSciTokens()

virtual XrdAccSciTokens::~XrdAccSciTokens ( )
inlinevirtual

Definition at line 461 of file XrdSciTokensAccess.cc.

461  {
462  if (m_config_lock_initialized) {
463  pthread_rwlock_destroy(&m_config_lock);
464  }
465  }

Member Function Documentation

◆ Access()

virtual XrdAccPrivs XrdAccSciTokens::Access ( const XrdSecEntity Entity,
const char *  path,
const Access_Operation  oper,
XrdOucEnv Env 
)
inlineoverridevirtual

Check whether or not the client is permitted specified access to a path.

Parameters
Entity-> Authentication information
path-> The logical path which is the target of oper
oper-> The operation being attempted (see the enum above). If the oper is AOP_Any, then the actual privileges are returned and the caller may make subsequent tests using Test().
Env-> Environmental information at the time of the operation as supplied by the path CGI string. This is optional and the pointer may be zero.
Returns
Permit: a non-zero value (access is permitted) Deny: zero (access is denied)

Implements XrdAccAuthorize.

Definition at line 467 of file XrdSciTokensAccess.cc.

471  {
472  std::vector<std::string_view> authz_list;
473  authz_list.reserve(1);
474 
475  // Parse the authz environment entry as a comma-separated list of tokens.
476  // Traditionally, `authz` has been used as the parameter for XRootD; however,
477  // RFC 6750 Section 2.3 ("URI Query Parameter") specifies that access_token
478  // is correct. We support both.
479  ParseTokenString("authz", env, authz_list);
480  ParseTokenString("access_token", env, authz_list);
481 
482  if (Entity && !strcmp("ztn", Entity->prot) && Entity->creds &&
483  Entity->credslen && Entity->creds[Entity->credslen] == '\0')
484  {
485  authz_list.push_back(Entity->creds);
486  }
487 
488  if (authz_list.empty()) {
489  return OnMissing(Entity, path, oper, env);
490  }
491 
492  // A potential DoS would be providing a large number of tokens to consider for ACLs.
493  // Have a hardcoded assumption of <10 tokens per request.
494  if (authz_list.size() > 10) {
495  m_log.Log(LogMask::Warning, "Access", "Request had more than 10 tokens attached; ignoring");
496  return OnMissing(Entity, path, oper, env);
497  }
498 
499  m_log.Log(LogMask::Debug, "Access", "Trying token-based access control");
500  std::vector<std::shared_ptr<XrdAccRules>> access_rules_list;
501  uint64_t now = monotonic_time();
502  Check(now);
503  for (const auto &authz : authz_list) {
504  std::shared_ptr<XrdAccRules> access_rules;
505  {
506  std::lock_guard<std::mutex> guard(m_mutex);
507  const auto iter = m_map.find(authz);
508  if (iter != m_map.end() && !iter->second->expired()) {
509  access_rules = iter->second;
510  }
511  }
512  if (!access_rules) {
513  m_log.Log(LogMask::Debug, "Access", "Token not found in recent cache; parsing.");
514  try {
515  uint64_t cache_expiry;
516  AccessRulesRaw rules;
517  std::string username;
518  std::string token_subject;
519  std::string issuer;
520  std::vector<MapRule> map_rules;
521  std::vector<std::string> groups;
522  uint32_t authz_strategy;
523  AuthzSetting acceptable_authz;
524  if (GenerateAcls(authz, cache_expiry, rules, username, token_subject, issuer, map_rules, groups, authz_strategy, acceptable_authz)) {
525  access_rules.reset(new XrdAccRules(now + cache_expiry, username, token_subject, issuer, map_rules, groups, authz_strategy, acceptable_authz));
526  access_rules->parse(rules);
527  } else {
528  m_log.Log(LogMask::Warning, "Access", "Failed to generate ACLs for token");
529  continue;
530  }
531  if (m_log.getMsgMask() & LogMask::Debug) {
532  m_log.Log(LogMask::Debug, "Access", "New valid token", access_rules->str().c_str());
533  }
534  } catch (std::exception &exc) {
535  m_log.Log(LogMask::Warning, "Access", "Error generating ACLs for authorization", exc.what());
536  continue;
537  }
538  std::lock_guard<std::mutex> guard(m_mutex);
539  m_map[std::string(authz)] = access_rules;
540  } else if (m_log.getMsgMask() & LogMask::Debug) {
541  m_log.Log(LogMask::Debug, "Access", "Cached token", access_rules->str().c_str());
542  }
543  access_rules_list.push_back(access_rules);
544  }
545  if (access_rules_list.empty()) {
546  return OnMissing(Entity, path, oper, env);
547  }
548  std::string_view path_view(path, strlen(path));
549 
550  // Apply the logic for the required issuers.
551  if (!AuthorizesRequiredIssuers(oper, path_view, m_required_issuers, access_rules_list)) {
552  return OnMissing(Entity, path, oper, env);
553  }
554 
555  // Strategy: assuming the corresponding strategy is enabled, we populate the name in
556  // the XrdSecEntity if:
557  // 1. There are scopes present in the token that authorize the request,
558  // 2. The token is mapped by some rule in the mapfile (group or subject-based mapping).
559  // The default username for the issuer is only used in (1).
560  // If the scope-based mapping is successful, authorize immediately. Otherwise, if the
561  // mapping is successful, we potentially chain to another plugin.
562  //
563  // We always populate the issuer and the groups, if present.
564 
565  // Access may be authorized; populate XrdSecEntity
566  for (const auto &access_rules : access_rules_list) {
567  // Make sure this issuer is acceptable for the given operation.
568  if (!access_rules->acceptable_authz(oper)) {
569  m_log.Log(LogMask::Debug, "Access", "Issuer is not acceptable for given operation:", access_rules->get_issuer().c_str());
570  continue;
571  }
572 
573  XrdSecEntity new_secentity;
574  new_secentity.vorg = nullptr;
575  new_secentity.grps = nullptr;
576  new_secentity.role = nullptr;
577  new_secentity.secMon = Entity->secMon;
578  new_secentity.addrInfo = Entity->addrInfo;
579  const auto &issuer = access_rules->get_issuer();
580  if (!issuer.empty()) {
581  new_secentity.vorg = strdup(issuer.c_str());
582  }
583  bool group_success = false;
584  if ((access_rules->get_authz_strategy() & IssuerAuthz::Group) && access_rules->groups().size()) {
585  std::stringstream ss;
586  for (const auto &grp : access_rules->groups()) {
587  ss << grp << " ";
588  }
589  const auto &groups_str = ss.str();
590  new_secentity.grps = static_cast<char*>(malloc(groups_str.size() + 1));
591  if (new_secentity.grps) {
592  memcpy(new_secentity.grps, groups_str.c_str(), groups_str.size());
593  new_secentity.grps[groups_str.size()] = '\0';
594  }
595  group_success = true;
596  }
597 
598  std::string username;
599  bool mapping_success = false;
600  bool scope_success = false;
601  username = access_rules->get_username(path_view);
602 
603  mapping_success = (access_rules->get_authz_strategy() & IssuerAuthz::Mapping) && !username.empty();
604  scope_success = (access_rules->get_authz_strategy() & IssuerAuthz::Capability) && access_rules->apply(oper, path_view);
605  if (scope_success && (m_log.getMsgMask() & LogMask::Debug)) {
606  std::stringstream ss;
607  ss << "Grant authorization based on scopes for operation=" << OpToName(oper) << ", path=" << path;
608  m_log.Log(LogMask::Debug, "Access", ss.str().c_str());
609  }
610 
611  if (!scope_success && !mapping_success && !group_success) {
612  auto returned_accs = OnMissing(&new_secentity, path, oper, env);
613  // Clean up the new_secentity
614  if (new_secentity.vorg != nullptr) free(new_secentity.vorg);
615  if (new_secentity.grps != nullptr) free(new_secentity.grps);
616  if (new_secentity.role != nullptr) free(new_secentity.role);
617 
618  return returned_accs;
619  }
620 
621  // Default user only applies to scope-based mappings.
622  if (scope_success && username.empty()) {
623  username = access_rules->get_default_username();
624  }
625 
626  // Setting the request.name will pass the username to the next plugin.
627  // Ensure we do that only if map-based or scope-based authorization worked.
628  if (scope_success || mapping_success) {
629  // Set scitokens.name in the extra attribute
630  Entity->eaAPI->Add("request.name", username, true);
631  new_secentity.eaAPI->Add("request.name", username, true);
632  m_log.Log(LogMask::Debug, "Access", "Request username", username.c_str());
633  }
634 
635  // Make the token subject available. Even though it's a reasonably bad idea
636  // to use for *authorization* for file access, there may be other use cases.
637  // For example, the combination of (vorg, token.subject) is a reasonable
638  // approximation of a unique 'entity' (either person or a robot) and is
639  // more reasonable to use for resource fairshare in XrdThrottle.
640  const auto &token_subject = access_rules->get_token_subject();
641  if (!token_subject.empty()) {
642  Entity->eaAPI->Add("token.subject", token_subject, true);
643  }
644 
645  // When the scope authorized this access, allow immediately. Otherwise, chain
646  XrdAccPrivs returned_op = scope_success ? AddPriv(oper, XrdAccPriv_None) : OnMissing(&new_secentity, path, oper, env);
647 
648  // Since we are doing an early return, insert token info into the
649  // monitoring stream if monitoring is in effect and access granted
650  //
651  if (Entity->secMon && scope_success && returned_op && Mon_isIO(oper))
652  Mon_Report(new_secentity, token_subject, username);
653 
654  // Cleanup the new_secentry
655  if (new_secentity.vorg != nullptr) free(new_secentity.vorg);
656  if (new_secentity.grps != nullptr) free(new_secentity.grps);
657  if (new_secentity.role != nullptr) free(new_secentity.role);
658  return returned_op;
659  }
660 
661  // We iterated through all available credentials and none provided authorization; fall back
662  return OnMissing(Entity, path, oper, env);
663  }
XrdAccPrivs
Definition: XrdAccPrivs.hh:39
@ XrdAccPriv_None
Definition: XrdAccPrivs.hh:53
@ Warning
bool AuthorizesRequiredIssuers(Access_Operation client_oper, const std::string_view &path, const std::vector< std::pair< std::unique_ptr< SubpathMatch >, std::string >> &required_issuers, const std::vector< std::shared_ptr< XrdAccRules >> &access_rules_list)
std::vector< std::pair< Access_Operation, std::string > > AccessRulesRaw
@ Capability
@ Mapping
AuthzSetting
bool Mon_isIO(const Access_Operation oper)
void Mon_Report(const XrdSecEntity &Entity, const std::string &subject, const std::string &username)
bool Add(XrdSecAttr &attr)
char * vorg
Entity's virtual organization(s)
Definition: XrdSecEntity.hh:71
int credslen
Length of the 'creds' data.
Definition: XrdSecEntity.hh:78
XrdNetAddrInfo * addrInfo
Entity's connection details.
Definition: XrdSecEntity.hh:80
XrdSecEntityAttr * eaAPI
non-const API to attributes
Definition: XrdSecEntity.hh:92
char prot[XrdSecPROTOIDSIZE]
Auth protocol used (e.g. krb5)
Definition: XrdSecEntity.hh:67
char * creds
Raw entity credentials or cert.
Definition: XrdSecEntity.hh:77
XrdSecMonitor * secMon
If !0 security monitoring enabled.
Definition: XrdSecEntity.hh:89
char * grps
Entity's group name(s)
Definition: XrdSecEntity.hh:73
char * role
Entity's role(s)
Definition: XrdSecEntity.hh:72
int getMsgMask()
Definition: XrdSysError.hh:156
void Log(int mask, const char *esfx, const char *text1, const char *text2=0, const char *text3=0)
Definition: XrdSysError.hh:133

References XrdSecEntityAttr::Add(), XrdSecEntity::addrInfo, AuthorizesRequiredIssuers(), Capability, XrdSecEntity::creds, XrdSecEntity::credslen, Macaroons::Debug, XrdSecEntity::eaAPI, XrdSysError::getMsgMask(), Group, XrdSecEntity::grps, XrdSysError::Log(), Mapping, XrdSciTokensMon::Mon_isIO(), XrdSciTokensMon::Mon_Report(), XrdSecEntity::prot, XrdSecEntity::role, XrdSecEntity::secMon, XrdSecEntity::vorg, Warning, and XrdAccPriv_None.

+ Here is the call graph for this function:

◆ Audit()

virtual int XrdAccSciTokens::Audit ( const int  accok,
const XrdSecEntity Entity,
const char *  path,
const Access_Operation  oper,
XrdOucEnv Env = 0 
)
inlineoverridevirtual

Route an audit message to the appropriate audit exit routine. See XrdAccAudit.h for more information on how the default implementation works. Currently, this method is not called by the ofs but should be used by the implementation to record denials or grants, as warranted.

Parameters
accok-> True is access was grated; false otherwise.
Entity-> Authentication information
path-> The logical path which is the target of oper
oper-> The operation being attempted (see above)
Env-> Environmental information at the time of the operation as supplied by the path CGI string. This is optional and the pointer may be zero.
Returns
Success: !0 information recorded. Failure: 0 information could not be recorded.

Implements XrdAccAuthorize.

Definition at line 732 of file XrdSciTokensAccess.cc.

737  {
738  return 0;
739  }

◆ GetConfigFile()

std::string XrdAccSciTokens::GetConfigFile ( )
inline

Definition at line 747 of file XrdSciTokensAccess.cc.

747  {
748  return m_cfg_file;
749  }

◆ IssuerList()

virtual Issuers XrdAccSciTokens::IssuerList ( )
inlineoverridevirtual

Implements XrdSciTokensHelper.

Definition at line 665 of file XrdSciTokensAccess.cc.

666  {
667  /*
668  Convert the m_issuers into the data structure:
669  struct ValidIssuer
670  {std::string issuer_name;
671  std::string issuer_url;
672  };
673  typedef std::vector<ValidIssuer> Issuers;
674  */
675  Issuers issuers;
676  for (auto it: m_issuers) {
677  ValidIssuer issuer_info;
678  issuer_info.issuer_name = it.first;
679  issuer_info.issuer_url = it.second.m_url;
680  issuers.push_back(issuer_info);
681  }
682  return issuers;
683 
684  }
std::vector< ValidIssuer > Issuers

References XrdSciTokensHelper::ValidIssuer::issuer_name, and XrdSciTokensHelper::ValidIssuer::issuer_url.

◆ Test()

virtual int XrdAccSciTokens::Test ( const XrdAccPrivs  priv,
const Access_Operation  oper 
)
inlineoverridevirtual

Check whether the specified operation is permitted.

Parameters
priv-> the privileges as returned by Access().
oper-> The operation being attempted (see above)
Returns
Permit: a non-zero value (access is permitted) Deny: zero (access is denied)

Implements XrdAccAuthorize.

Definition at line 741 of file XrdSciTokensAccess.cc.

743  {
744  return (m_chain ? m_chain->Test(priv, oper) : 0);
745  }
virtual int Test(const XrdAccPrivs priv, const Access_Operation oper)=0

References XrdAccAuthorize::Test().

+ Here is the call graph for this function:

◆ Validate()

virtual bool XrdAccSciTokens::Validate ( const char *  token,
std::string &  emsg,
long long *  expT,
XrdSecEntity entP 
)
inlineoverridevirtual

Validate a scitoken.

Parameters
token- Pointer to the token to validate.
emsg- Reference to a string to hold the reason for rejection
expT- Pointer to where the expiry value is to be placed. If nill, the value is not returned.
entP- Pointer to the SecEntity object and when not nil requests that it be filled with any identifying information in the token. The caller assumes that all supplied fields may be released by calling free().
Returns
Return true if the token is valid; false otherwise with emsg set.

Implements XrdSciTokensHelper.

Definition at line 686 of file XrdSciTokensAccess.cc.

688  {
689  // Just check if the token is valid, no scope checking
690 
691  // Deserialize the token
692  SciToken scitoken;
693  char *err_msg;
694  if (!strncmp(token, "Bearer%20", 9)) token += 9;
695  pthread_rwlock_rdlock(&m_config_lock);
696  auto retval = scitoken_deserialize(token, &scitoken, &m_valid_issuers_array[0], &err_msg);
697  pthread_rwlock_unlock(&m_config_lock);
698  if (retval) {
699  // This originally looked like a JWT so log the failure.
700  m_log.Log(LogMask::Warning, "Validate", "Failed to deserialize SciToken:", err_msg);
701  emsg = err_msg;
702  free(err_msg);
703  return false;
704  }
705 
706  // If an entity was passed then we will fill it in with the subject
707  // name, should it exist. Note that we are gauranteed that all the
708  // settable entity fields are null so no need to worry setting them.
709  //
710  if (Entity)
711  {char *value = nullptr;
712  if (!scitoken_get_claim_string(scitoken, "sub", &value, &err_msg))
713  Entity->name = strdup(value);
714  }
715 
716  // Return the expiration time of this token if so wanted.
717  //
718  if (expT && scitoken_get_expiration(scitoken, expT, &err_msg)) {
719  emsg = err_msg;
720  free(err_msg);
721  return false;
722  }
723 
724 
725  // Delete the scitokens
726  scitoken_destroy(scitoken);
727 
728  // Deserialize checks the key, so we're good now.
729  return true;
730  }
int emsg(int rc, char *msg)

References emsg(), XrdSysError::Log(), XrdSecEntity::name, and Warning.

+ Here is the call graph for this function:

The documentation for this class was generated from the following file: