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

Constructor & Destructor Documentation

◆ XrdAccSciTokens()

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

Definition at line 448 of file XrdSciTokensAccess.cc.

448  :
449  m_chain(chain),
450  m_parms(parms ? parms : ""),
451  m_next_clean(monotonic_time() + m_expiry_secs),
452  m_log(lp, "scitokens_")
453  {
454  pthread_rwlock_init(&m_config_lock, nullptr);
455  m_config_lock_initialized = true;
456  m_log.Say("++++++ XrdAccSciTokens: Initialized SciTokens-based authorization.");
457  if (!Config(envP)) {
458  throw std::runtime_error("Failed to configure SciTokens authorization.");
459  }
460  }
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 462 of file XrdSciTokensAccess.cc.

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

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

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

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, Macaroons::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 733 of file XrdSciTokensAccess.cc.

738  {
739  return 0;
740  }

◆ GetConfigFile()

std::string XrdAccSciTokens::GetConfigFile ( )
inline

Definition at line 748 of file XrdSciTokensAccess.cc.

748  {
749  return m_cfg_file;
750  }

◆ IssuerList()

virtual Issuers XrdAccSciTokens::IssuerList ( )
inlineoverridevirtual

Implements XrdSciTokensHelper.

Definition at line 666 of file XrdSciTokensAccess.cc.

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

744  {
745  return (m_chain ? m_chain->Test(priv, oper) : 0);
746  }
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 687 of file XrdSciTokensAccess.cc.

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

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

+ Here is the call graph for this function:

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