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...
 
virtual XrdAccPrivs Access (const XrdSecEntity *Entity, const char *path, const Access_Operation oper, std::string &eInfo, XrdOucEnv *Env=0)
 
- 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 459 of file XrdSciTokensAccess.cc.

Constructor & Destructor Documentation

◆ XrdAccSciTokens()

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

Definition at line 470 of file XrdSciTokensAccess.cc.

470  :
471  m_chain(chain),
472  m_parms(parms ? parms : ""),
473  m_next_clean(monotonic_time() + m_expiry_secs),
474  m_log(lp, "scitokens_")
475  {
476  pthread_rwlock_init(&m_config_lock, nullptr);
477  m_config_lock_initialized = true;
478  m_log.Say("++++++ XrdAccSciTokens: Initialized SciTokens-based authorization.");
479  if (!Config(envP)) {
480  throw std::runtime_error("Failed to configure SciTokens authorization.");
481  }
482  }
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:162
XrdOucEnv * envP
Definition: XrdPss.cc:110

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

+ Here is the call graph for this function:

◆ ~XrdAccSciTokens()

virtual XrdAccSciTokens::~XrdAccSciTokens ( )
inlinevirtual

Definition at line 484 of file XrdSciTokensAccess.cc.

484  {
485  if (m_config_lock_initialized) {
486  pthread_rwlock_destroy(&m_config_lock);
487  }
488  }

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

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

769  {
770  return 0;
771  }

◆ GetConfigFile()

std::string XrdAccSciTokens::GetConfigFile ( )
inline

Definition at line 779 of file XrdSciTokensAccess.cc.

779  {
780  return m_cfg_file;
781  }

◆ IssuerList()

virtual Issuers XrdAccSciTokens::IssuerList ( )
inlineoverridevirtual

Implements XrdSciTokensHelper.

Definition at line 688 of file XrdSciTokensAccess.cc.

689  {
690  /*
691  Convert the m_issuers into the data structure:
692  struct ValidIssuer
693  {std::string issuer_name;
694  std::string issuer_url;
695  };
696  typedef std::vector<ValidIssuer> Issuers;
697  */
698  Issuers issuers;
699  pthread_rwlock_rdlock(&m_config_lock);
700  try {
701  for (const auto &it: m_issuers) {
702  issuers.push_back({it.first, it.second.m_url});
703  }
704  } catch (...) {
705  pthread_rwlock_unlock(&m_config_lock);
706  throw;
707  }
708  pthread_rwlock_unlock(&m_config_lock);
709  return issuers;
710 
711  }
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 773 of file XrdSciTokensAccess.cc.

775  {
776  return (m_chain ? m_chain->Test(priv, oper) : 0);
777  }
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 713 of file XrdSciTokensAccess.cc.

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