XRootD
Loading...
Searching...
No Matches
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.
 
virtual ~XrdAccAuthorize ()
 Destructor.
 
- Public Member Functions inherited from XrdSciTokensHelper
 XrdSciTokensHelper ()
 Constructor and Destructor.
 
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 }

References XrdAccAuthorize::XrdAccAuthorize().

+ 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
@ XrdAccPriv_None
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
if(ec< 0) ec
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)
int credslen
Length of the 'creds' data.
XrdNetAddrInfo * addrInfo
Entity's connection details.
XrdSecEntityAttr * eaAPI
non-const API to attributes
char prot[XrdSecPROTOIDSIZE]
Auth protocol used (e.g. krb5)
char * creds
Raw entity credentials or cert.
XrdSecMonitor * secMon
If !0 security monitoring enabled.
char * grps
Entity's group name(s)
char * role
Entity's role(s)

References XrdSecEntityAttr::Add(), XrdSecEntity::addrInfo, AuthorizesRequiredIssuers(), Capability, XrdSecEntity::creds, XrdSecEntity::credslen, XrdSecEntity::eaAPI, Group, XrdSecEntity::grps, Mapping, XrdSciTokensMon::Mon_isIO(), XrdSciTokensMon::Mon_Report(), XrdSecEntity::prot, XrdSecEntity::role, XrdSecEntity::secMon, XrdSecEntity::vorg, 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 }

◆ 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(), and XrdSecEntity::name.

+ Here is the call graph for this function:

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