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 452 of file XrdSciTokensAccess.cc.

Constructor & Destructor Documentation

◆ XrdAccSciTokens()

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

Definition at line 463 of file XrdSciTokensAccess.cc.

463  :
464  m_chain(chain),
465  m_parms(parms ? parms : ""),
466  m_next_clean(monotonic_time() + m_expiry_secs),
467  m_log(lp, "scitokens_")
468  {
469  pthread_rwlock_init(&m_config_lock, nullptr);
470  m_config_lock_initialized = true;
471  m_log.Say("++++++ XrdAccSciTokens: Initialized SciTokens-based authorization.");
472  if (!Config(envP)) {
473  throw std::runtime_error("Failed to configure SciTokens authorization.");
474  }
475  }
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 477 of file XrdSciTokensAccess.cc.

477  {
478  if (m_config_lock_initialized) {
479  pthread_rwlock_destroy(&m_config_lock);
480  }
481  }

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 483 of file XrdSciTokensAccess.cc.

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

References XrdSecEntityAttr::Add(), XrdSecEntity::addrInfo, AuthorizesRequiredIssuers(), Capability, XrdSecEntity::creds, XrdSecEntity::credslen, 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, TPC::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 757 of file XrdSciTokensAccess.cc.

762  {
763  return 0;
764  }

◆ GetConfigFile()

std::string XrdAccSciTokens::GetConfigFile ( )
inline

Definition at line 772 of file XrdSciTokensAccess.cc.

772  {
773  return m_cfg_file;
774  }

◆ IssuerList()

virtual Issuers XrdAccSciTokens::IssuerList ( )
inlineoverridevirtual

Implements XrdSciTokensHelper.

Definition at line 681 of file XrdSciTokensAccess.cc.

682  {
683  /*
684  Convert the m_issuers into the data structure:
685  struct ValidIssuer
686  {std::string issuer_name;
687  std::string issuer_url;
688  };
689  typedef std::vector<ValidIssuer> Issuers;
690  */
691  Issuers issuers;
692  pthread_rwlock_rdlock(&m_config_lock);
693  try {
694  for (const auto &it: m_issuers) {
695  issuers.push_back({it.first, it.second.m_url});
696  }
697  } catch (...) {
698  pthread_rwlock_unlock(&m_config_lock);
699  throw;
700  }
701  pthread_rwlock_unlock(&m_config_lock);
702  return issuers;
703 
704  }
std::vector< ValidIssuer > Issuers

◆ 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 766 of file XrdSciTokensAccess.cc.

768  {
769  return (m_chain ? m_chain->Test(priv, oper) : 0);
770  }
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 706 of file XrdSciTokensAccess.cc.

708  {
709  // Just check if the token is valid, no scope checking
710 
711  // Deserialize the token
712  SciToken scitoken;
713  char *err_msg;
714  if (!strncmp(token, "Bearer%20", 9)) token += 9;
715  pthread_rwlock_rdlock(&m_config_lock);
716  auto retval = scitoken_deserialize(token, &scitoken, &m_valid_issuers_array[0], &err_msg);
717  pthread_rwlock_unlock(&m_config_lock);
718  if (retval) {
719  // This originally looked like a JWT so log the failure.
720  m_log.Log(LogMask::Warning, "Validate", "Failed to deserialize SciToken:", err_msg);
721  emsg = err_msg;
722  free(err_msg);
723  return false;
724  }
725 
726  // If an entity was passed then we will fill it in with the subject
727  // name, should it exist. Note that we are gauranteed that all the
728  // settable entity fields are null so no need to worry setting them.
729  //
730  if (Entity)
731  {char *value = nullptr;
732  if (!scitoken_get_claim_string(scitoken, "sub", &value, &err_msg)) {
733  Entity->name = strdup(value);
734  free(value);
735  } else {
736  free(err_msg);
737  }
738  }
739 
740  // Return the expiration time of this token if so wanted.
741  //
742  if (expT && scitoken_get_expiration(scitoken, expT, &err_msg)) {
743  emsg = err_msg;
744  free(err_msg);
745  scitoken_destroy(scitoken);
746  return false;
747  }
748 
749 
750  // Delete the scitokens
751  scitoken_destroy(scitoken);
752 
753  // Deserialize checks the key, so we're good now.
754  return true;
755  }
int emsg(int rc, char *msg)

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

+ Here is the call graph for this function:

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