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.
 
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.
 
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 }

References XrdAccAuthorize::XrdAccAuthorize().

+ 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
@ 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 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 }

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

+ Here is the call graph for this function:

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