XRootD
Loading...
Searching...
No Matches
XrdSciTokensAccess.cc
Go to the documentation of this file.
1
3#include "XrdOuc/XrdOucEnv.hh"
10#include "XrdVersion.hh"
11
12#include <map>
13#include <memory>
14#include <mutex>
15#include <string>
16#include <vector>
17#include <sstream>
18#include <fstream>
19#include <unordered_map>
20#include <tuple>
21
22#include "fcntl.h"
23
24#include "INIReader.h"
25#include "picojson.h"
26
27#include "scitokens/scitokens.h"
30
31// The status-quo to retrieve the default object is to copy/paste the
32// linker definition and invoke directly.
35
36namespace {
37
38enum LogMask {
39 Debug = 0x01,
40 Info = 0x02,
41 Warning = 0x04,
42 Error = 0x08,
43 All = 0xff
44};
45
46enum IssuerAuthz {
47 Capability = 0x01,
48 Group = 0x02,
49 Mapping = 0x04,
50 Default = 0x07
51};
52
53std::string LogMaskToString(int mask) {
54 if (mask == LogMask::All) {return "all";}
55
56 bool has_entry = false;
57 std::stringstream ss;
58 if (mask & LogMask::Debug) {
59 ss << "debug";
60 has_entry = true;
61 }
62 if (mask & LogMask::Info) {
63 ss << (has_entry ? ", " : "") << "info";
64 has_entry = true;
65 }
66 if (mask & LogMask::Warning) {
67 ss << (has_entry ? ", " : "") << "warning";
68 has_entry = true;
69 }
70 if (mask & LogMask::Error) {
71 ss << (has_entry ? ", " : "") << "error";
72 has_entry = true;
73 }
74 return ss.str();
75}
76
77typedef std::vector<std::pair<Access_Operation, std::string>> AccessRulesRaw;
78
79inline uint64_t monotonic_time() {
80 struct timespec tp;
81#ifdef CLOCK_MONOTONIC_COARSE
82 clock_gettime(CLOCK_MONOTONIC_COARSE, &tp);
83#else
84 clock_gettime(CLOCK_MONOTONIC, &tp);
85#endif
86 return tp.tv_sec + (tp.tv_nsec >= 500000000);
87}
88
90{
91 int new_privs = privs;
92 switch (op) {
93 case AOP_Any:
94 break;
95 case AOP_Chmod:
96 new_privs |= static_cast<int>(XrdAccPriv_Chmod);
97 break;
98 case AOP_Chown:
99 new_privs |= static_cast<int>(XrdAccPriv_Chown);
100 break;
101 case AOP_Excl_Create: // fallthrough
102 case AOP_Create:
103 new_privs |= static_cast<int>(XrdAccPriv_Create);
104 break;
105 case AOP_Delete:
106 new_privs |= static_cast<int>(XrdAccPriv_Delete);
107 break;
108 case AOP_Excl_Insert: // fallthrough
109 case AOP_Insert:
110 new_privs |= static_cast<int>(XrdAccPriv_Insert);
111 break;
112 case AOP_Lock:
113 new_privs |= static_cast<int>(XrdAccPriv_Lock);
114 break;
115 case AOP_Mkdir:
116 new_privs |= static_cast<int>(XrdAccPriv_Mkdir);
117 break;
118 case AOP_Read:
119 new_privs |= static_cast<int>(XrdAccPriv_Read);
120 break;
121 case AOP_Readdir:
122 new_privs |= static_cast<int>(XrdAccPriv_Readdir);
123 break;
124 case AOP_Rename:
125 new_privs |= static_cast<int>(XrdAccPriv_Rename);
126 break;
127 case AOP_Stat:
128 new_privs |= static_cast<int>(XrdAccPriv_Lookup);
129 break;
130 case AOP_Update:
131 new_privs |= static_cast<int>(XrdAccPriv_Update);
132 break;
133 };
134 return static_cast<XrdAccPrivs>(new_privs);
135}
136
137const std::string OpToName(Access_Operation op) {
138 switch (op) {
139 case AOP_Any: return "any";
140 case AOP_Chmod: return "chmod";
141 case AOP_Chown: return "chown";
142 case AOP_Create: return "create";
143 case AOP_Excl_Create: return "excl_create";
144 case AOP_Delete: return "del";
145 case AOP_Excl_Insert: return "excl_insert";
146 case AOP_Insert: return "insert";
147 case AOP_Lock: return "lock";
148 case AOP_Mkdir: return "mkdir";
149 case AOP_Read: return "read";
150 case AOP_Readdir: return "dir";
151 case AOP_Rename: return "mv";
152 case AOP_Stat: return "stat";
153 case AOP_Update: return "update";
154 };
155 return "unknown";
156}
157
158std::string AccessRuleStr(const AccessRulesRaw &rules) {
159 std::unordered_map<std::string, std::unique_ptr<std::stringstream>> rule_map;
160 for (const auto &rule : rules) {
161 auto iter = rule_map.find(rule.second);
162 if (iter == rule_map.end()) {
163 auto result = rule_map.insert(std::make_pair(rule.second, std::make_unique<std::stringstream>()));
164 iter = result.first;
165 *(iter->second) << OpToName(rule.first);
166 } else {
167 *(iter->second) << "," << OpToName(rule.first);
168 }
169 }
170 std::stringstream ss;
171 bool first = true;
172 for (const auto &val : rule_map) {
173 ss << (first ? "" : ";") << val.first << ":" << val.second->str();
174 first = false;
175 }
176 return ss.str();
177}
178
179bool MakeCanonical(const std::string &path, std::string &result)
180{
181 if (path.empty() || path[0] != '/') {return false;}
182
183 size_t pos = 0;
184 std::vector<std::string> components;
185 do {
186 while (path.size() > pos && path[pos] == '/') {pos++;}
187 auto next_pos = path.find_first_of("/", pos);
188 auto next_component = path.substr(pos, next_pos - pos);
189 pos = next_pos;
190 if (next_component.empty() || next_component == ".") {continue;}
191 else if (next_component == "..") {
192 if (!components.empty()) {
193 components.pop_back();
194 }
195 } else {
196 components.emplace_back(next_component);
197 }
198 } while (pos != std::string::npos);
199 if (components.empty()) {
200 result = "/";
201 return true;
202 }
203 std::stringstream ss;
204 for (const auto &comp : components) {
205 ss << "/" << comp;
206 }
207 result = ss.str();
208 return true;
209}
210
211void ParseCanonicalPaths(const std::string &path, std::vector<std::string> &results)
212{
213 size_t pos = 0;
214 do {
215 while (path.size() > pos && (path[pos] == ',' || path[pos] == ' ')) {pos++;}
216 auto next_pos = path.find_first_of(", ", pos);
217 auto next_path = path.substr(pos, next_pos - pos);
218 pos = next_pos;
219 if (!next_path.empty()) {
220 std::string canonical_path;
221 if (MakeCanonical(next_path, canonical_path)) {
222 results.emplace_back(std::move(canonical_path));
223 }
224 }
225 } while (pos != std::string::npos);
226}
227
228struct MapRule
229{
230 MapRule(const std::string &sub,
231 const std::string &username,
232 const std::string &path_prefix,
233 const std::string &group,
234 const std::string &result)
235 : m_sub(sub),
236 m_username(username),
237 m_path_prefix(path_prefix),
238 m_group(group),
239 m_result(result)
240 {
241 //std::cerr << "Making a rule {sub=" << sub << ", username=" << username << ", path=" << path_prefix << ", group=" << group << ", result=" << name << "}" << std::endl;
242 }
243
244 const std::string match(const std::string &sub,
245 const std::string &username,
246 const std::string &req_path,
247 const std::vector<std::string> &groups) const
248 {
249 if (!m_sub.empty() && sub != m_sub) {return "";}
250
251 if (!m_username.empty() && username != m_username) {return "";}
252
253 if (!m_path_prefix.empty() &&
254 strncmp(req_path.c_str(), m_path_prefix.c_str(), m_path_prefix.size()))
255 {
256 return "";
257 }
258
259 if (!m_group.empty()) {
260 for (const auto &group : groups) {
261 if (group == m_group)
262 return m_result;
263 }
264 return "";
265 }
266 return m_result;
267 }
268
269 std::string m_sub;
270 std::string m_username;
271 std::string m_path_prefix;
272 std::string m_group;
273 std::string m_result;
274};
275
276struct IssuerConfig
277{
278 IssuerConfig(const std::string &issuer_name,
279 const std::string &issuer_url,
280 const std::vector<std::string> &base_paths,
281 const std::vector<std::string> &restricted_paths,
282 bool map_subject,
283 uint32_t authz_strategy,
284 const std::string &default_user,
285 const std::string &username_claim,
286 const std::string &groups_claim,
287 const std::vector<MapRule> rules)
288 : m_map_subject(map_subject || !username_claim.empty()),
289 m_authz_strategy(authz_strategy),
290 m_name(issuer_name),
291 m_url(issuer_url),
292 m_default_user(default_user),
293 m_username_claim(username_claim),
294 m_groups_claim(groups_claim),
295 m_base_paths(base_paths),
296 m_restricted_paths(restricted_paths),
297 m_map_rules(rules)
298 {}
299
300 const bool m_map_subject;
301 const uint32_t m_authz_strategy;
302 const std::string m_name;
303 const std::string m_url;
304 const std::string m_default_user;
305 const std::string m_username_claim;
306 const std::string m_groups_claim;
307 const std::vector<std::string> m_base_paths;
308 const std::vector<std::string> m_restricted_paths;
309 const std::vector<MapRule> m_map_rules;
310};
311
312}
313
314class OverrideINIReader: public INIReader {
315public:
317 inline OverrideINIReader(std::string filename) {
318 _error = ini_parse(filename.c_str(), ValueHandler, this);
319 }
320 inline OverrideINIReader(FILE *file) {
321 _error = ini_parse_file(file, ValueHandler, this);
322 }
323protected:
337 inline static int ValueHandler(void* user, const char* section, const char* name,
338 const char* value) {
339 OverrideINIReader* reader = (OverrideINIReader*)user;
340 std::string key = MakeKey(section, name);
341
342 // Overwrite existing values, if they exist
343 reader->_values[key] = value;
344 reader->_sections.insert(section);
345 return 1;
346 }
347
348};
349
351{
352public:
353 XrdAccRules(uint64_t expiry_time, const std::string &username, const std::string &token_subject,
354 const std::string &issuer, const std::vector<MapRule> &rules, const std::vector<std::string> &groups,
355 uint32_t authz_strategy) :
356 m_authz_strategy(authz_strategy),
357 m_expiry_time(expiry_time),
358 m_username(username),
359 m_token_subject(token_subject),
360 m_issuer(issuer),
361 m_map_rules(rules),
362 m_groups(groups)
363 {}
364
366
367 bool apply(Access_Operation oper, std::string path) {
368 for (const auto & rule : m_rules) {
369 // Skip rules that don't match the current operation
370 if (rule.first != oper)
371 continue;
372
373 // If the rule allows any path, allow the operation
374 if (rule.second == "/")
375 return true;
376
377 // Allow operation if path is a subdirectory of the rule's path
378 if (is_subdirectory(rule.second, path)) {
379 return true;
380 } else {
381 // Allow stat and mkdir of parent directories to comply with WLCG token specs
382 if (oper == AOP_Stat || oper == AOP_Mkdir)
383 if (is_subdirectory(path, rule.second))
384 return true;
385 }
386 }
387 return false;
388 }
389
390 bool expired() const {return monotonic_time() > m_expiry_time;}
391
392 void parse(const AccessRulesRaw &rules) {
393 m_rules.reserve(rules.size());
394 for (const auto &entry : rules) {
395 m_rules.emplace_back(entry.first, entry.second);
396 }
397 }
398
399 std::string get_username(const std::string &req_path) const
400 {
401 for (const auto &rule : m_map_rules) {
402 std::string name = rule.match(m_token_subject, m_username, req_path, m_groups);
403 if (!name.empty()) {
404 return name;
405 }
406 }
407 return "";
408 }
409
410 const std::string str() const
411 {
412 std::stringstream ss;
413 ss << "mapped_username=" << m_username << ", subject=" << m_token_subject
414 << ", issuer=" << m_issuer;
415 if (!m_groups.empty()) {
416 ss << ", groups=";
417 bool first=true;
418 for (const auto &group : m_groups) {
419 ss << (first ? "" : ",") << group;
420 first = false;
421 }
422 }
423 if (!m_rules.empty()) {
424 ss << ", authorizations=" << AccessRuleStr(m_rules);
425 }
426 return ss.str();
427 }
428
429
430 // Return the token's subject, an opaque unique string within the issuer's
431 // namespace. It may or may not be related to the username one should
432 // use within the authorization framework.
433 const std::string & get_token_subject() const {return m_token_subject;}
434 const std::string & get_default_username() const {return m_username;}
435 const std::string & get_issuer() const {return m_issuer;}
436
437 uint32_t get_authz_strategy() const {return m_authz_strategy;}
438
439 size_t size() const {return m_rules.size();}
440 const std::vector<std::string> &groups() const {return m_groups;}
441
442private:
443 uint32_t m_authz_strategy;
444 AccessRulesRaw m_rules;
445 uint64_t m_expiry_time{0};
446 const std::string m_username;
447 const std::string m_token_subject;
448 const std::string m_issuer;
449 const std::vector<MapRule> m_map_rules;
450 const std::vector<std::string> m_groups;
451};
452
453class XrdAccSciTokens;
454
457
459 public XrdSciTokensMon
460{
461
462 enum class AuthzBehavior {
463 PASSTHROUGH,
464 ALLOW,
465 DENY
466 };
467
468public:
469 XrdAccSciTokens(XrdSysLogger *lp, const char *parms, XrdAccAuthorize* chain, XrdOucEnv *envP) :
470 m_chain(chain),
471 m_parms(parms ? parms : ""),
472 m_next_clean(monotonic_time() + m_expiry_secs),
473 m_log(lp, "scitokens_")
474 {
475 pthread_rwlock_init(&m_config_lock, nullptr);
476 m_config_lock_initialized = true;
477 m_log.Say("++++++ XrdAccSciTokens: Initialized SciTokens-based authorization.");
478 if (!Config(envP)) {
479 throw std::runtime_error("Failed to configure SciTokens authorization.");
480 }
481 }
482
484 if (m_config_lock_initialized) {
485 pthread_rwlock_destroy(&m_config_lock);
486 }
487 }
488
489 virtual XrdAccPrivs Access(const XrdSecEntity *Entity,
490 const char *path,
491 const Access_Operation oper,
492 XrdOucEnv *env) override
493 {
494 const char *authz = env ? env->Get("authz") : nullptr;
495 // Note: this is more permissive than the plugin was previously.
496 // The prefix 'Bearer%20' used to be required as that's what HTTP
497 // required. However, to make this more pleasant for XRootD protocol
498 // users, we now simply "handle" the prefix insterad of requiring it.
499 if (authz && !strncmp(authz, "Bearer%20", 9)) {
500 authz += 9;
501 }
502 // If there's no request-specific token, then see if the ZTN authorization
503 // has provided us with a session token.
504 if (!authz && Entity && !strcmp("ztn", Entity->prot) && Entity->creds &&
505 Entity->credslen && Entity->creds[Entity->credslen] == '\0')
506 {
507 authz = Entity->creds;
508 }
509 if (authz == nullptr) {
510 return OnMissing(Entity, path, oper, env);
511 }
512 m_log.Log(LogMask::Debug, "Access", "Trying token-based access control");
513 std::shared_ptr<XrdAccRules> access_rules;
514 uint64_t now = monotonic_time();
515 Check(now);
516 {
517 std::lock_guard<std::mutex> guard(m_mutex);
518 const auto iter = m_map.find(authz);
519 if (iter != m_map.end() && !iter->second->expired()) {
520 access_rules = iter->second;
521 }
522 }
523 if (!access_rules) {
524 m_log.Log(LogMask::Debug, "Access", "Token not found in recent cache; parsing.");
525 try {
526 uint64_t cache_expiry;
527 AccessRulesRaw rules;
528 std::string username;
529 std::string token_subject;
530 std::string issuer;
531 std::vector<MapRule> map_rules;
532 std::vector<std::string> groups;
533 uint32_t authz_strategy;
534 if (GenerateAcls(authz, cache_expiry, rules, username, token_subject, issuer, map_rules, groups, authz_strategy)) {
535 access_rules.reset(new XrdAccRules(now + cache_expiry, username, token_subject, issuer, map_rules, groups, authz_strategy));
536 access_rules->parse(rules);
537 } else {
538 m_log.Log(LogMask::Warning, "Access", "Failed to generate ACLs for token");
539 return OnMissing(Entity, path, oper, env);
540 }
541 if (m_log.getMsgMask() & LogMask::Debug) {
542 m_log.Log(LogMask::Debug, "Access", "New valid token", access_rules->str().c_str());
543 }
544 } catch (std::exception &exc) {
545 m_log.Log(LogMask::Warning, "Access", "Error generating ACLs for authorization", exc.what());
546 return OnMissing(Entity, path, oper, env);
547 }
548 std::lock_guard<std::mutex> guard(m_mutex);
549 m_map[authz] = access_rules;
550 } else if (m_log.getMsgMask() & LogMask::Debug) {
551 m_log.Log(LogMask::Debug, "Access", "Cached token", access_rules->str().c_str());
552 }
553
554 // Strategy: assuming the corresponding strategy is enabled, we populate the name in
555 // the XrdSecEntity if:
556 // 1. There are scopes present in the token that authorize the request,
557 // 2. The token is mapped by some rule in the mapfile (group or subject-based mapping).
558 // The default username for the issuer is only used in (1).
559 // If the scope-based mapping is successful, authorize immediately. Otherwise, if the
560 // mapping is successful, we potentially chain to another plugin.
561 //
562 // We always populate the issuer and the groups, if present.
563
564 // Access may be authorized; populate XrdSecEntity
565 XrdSecEntity new_secentity;
566 new_secentity.vorg = nullptr;
567 new_secentity.grps = nullptr;
568 new_secentity.role = nullptr;
569 new_secentity.secMon = Entity->secMon;
570 new_secentity.addrInfo = Entity->addrInfo;
571 const auto &issuer = access_rules->get_issuer();
572 if (!issuer.empty()) {
573 new_secentity.vorg = strdup(issuer.c_str());
574 }
575 bool group_success = false;
576 if ((access_rules->get_authz_strategy() & IssuerAuthz::Group) && access_rules->groups().size()) {
577 std::stringstream ss;
578 for (const auto &grp : access_rules->groups()) {
579 ss << grp << " ";
580 }
581 const auto &groups_str = ss.str();
582 new_secentity.grps = static_cast<char*>(malloc(groups_str.size() + 1));
583 if (new_secentity.grps) {
584 memcpy(new_secentity.grps, groups_str.c_str(), groups_str.size());
585 new_secentity.grps[groups_str.size()] = '\0';
586 }
587 group_success = true;
588 }
589
590 std::string username;
591 bool mapping_success = false;
592 bool scope_success = false;
593 username = access_rules->get_username(path);
594
595 mapping_success = (access_rules->get_authz_strategy() & IssuerAuthz::Mapping) && !username.empty();
596 scope_success = (access_rules->get_authz_strategy() & IssuerAuthz::Capability) && access_rules->apply(oper, path);
597 if (scope_success && (m_log.getMsgMask() & LogMask::Debug)) {
598 std::stringstream ss;
599 ss << "Grant authorization based on scopes for operation=" << OpToName(oper) << ", path=" << path;
600 m_log.Log(LogMask::Debug, "Access", ss.str().c_str());
601 }
602
603 if (!scope_success && !mapping_success && !group_success) {
604 auto returned_accs = OnMissing(&new_secentity, path, oper, env);
605 // Clean up the new_secentity
606 if (new_secentity.vorg != nullptr) free(new_secentity.vorg);
607 if (new_secentity.grps != nullptr) free(new_secentity.grps);
608 if (new_secentity.role != nullptr) free(new_secentity.role);
609
610 return returned_accs;
611 }
612
613 // Default user only applies to scope-based mappings.
614 if (scope_success && username.empty()) {
615 username = access_rules->get_default_username();
616 }
617
618 // Setting the request.name will pass the username to the next plugin.
619 // Ensure we do that only if map-based or scope-based authorization worked.
620 if (scope_success || mapping_success) {
621 // Set scitokens.name in the extra attribute
622 Entity->eaAPI->Add("request.name", username, true);
623 new_secentity.eaAPI->Add("request.name", username, true);
624 m_log.Log(LogMask::Debug, "Access", "Request username", username.c_str());
625 }
626
627 // Make the token subject available. Even though it's a reasonably bad idea
628 // to use for *authorization* for file access, there may be other use cases.
629 // For example, the combination of (vorg, token.subject) is a reasonable
630 // approximation of a unique 'entity' (either person or a robot) and is
631 // more reasonable to use for resource fairshare in XrdThrottle.
632 const auto &token_subject = access_rules->get_token_subject();
633 if (!token_subject.empty()) {
634 Entity->eaAPI->Add("token.subject", token_subject, true);
635 }
636
637 // When the scope authorized this access, allow immediately. Otherwise, chain
638 XrdAccPrivs returned_op = scope_success ? AddPriv(oper, XrdAccPriv_None) : OnMissing(&new_secentity, path, oper, env);
639
640 // Since we are doing an early return, insert token info into the
641 // monitoring stream if monitoring is in effect and access granted
642 //
643 if (Entity->secMon && scope_success && returned_op && Mon_isIO(oper))
644 Mon_Report(new_secentity, token_subject, username);
645
646 // Cleanup the new_secentry
647 if (new_secentity.vorg != nullptr) free(new_secentity.vorg);
648 if (new_secentity.grps != nullptr) free(new_secentity.grps);
649 if (new_secentity.role != nullptr) free(new_secentity.role);
650
651 return returned_op;
652 }
653
654 virtual Issuers IssuerList() override
655 {
656 /*
657 Convert the m_issuers into the data structure:
658 struct ValidIssuer
659 {std::string issuer_name;
660 std::string issuer_url;
661 };
662 typedef std::vector<ValidIssuer> Issuers;
663 */
664 Issuers issuers;
665 for (auto it: m_issuers) {
666 ValidIssuer issuer_info;
667 issuer_info.issuer_name = it.first;
668 issuer_info.issuer_url = it.second.m_url;
669 issuers.push_back(issuer_info);
670 }
671 return issuers;
672
673 }
674
675 virtual bool Validate(const char *token, std::string &emsg, long long *expT,
676 XrdSecEntity *Entity) override
677 {
678 // Just check if the token is valid, no scope checking
679
680 // Deserialize the token
681 SciToken scitoken;
682 char *err_msg;
683 if (!strncmp(token, "Bearer%20", 9)) token += 9;
684 pthread_rwlock_rdlock(&m_config_lock);
685 auto retval = scitoken_deserialize(token, &scitoken, &m_valid_issuers_array[0], &err_msg);
686 pthread_rwlock_unlock(&m_config_lock);
687 if (retval) {
688 // This originally looked like a JWT so log the failure.
689 m_log.Log(LogMask::Warning, "Validate", "Failed to deserialize SciToken:", err_msg);
690 emsg = err_msg;
691 free(err_msg);
692 return false;
693 }
694
695 // If an entity was passed then we will fill it in with the subject
696 // name, should it exist. Note that we are gauranteed that all the
697 // settable entity fields are null so no need to worry setting them.
698 //
699 if (Entity)
700 {char *value = nullptr;
701 if (!scitoken_get_claim_string(scitoken, "sub", &value, &err_msg))
702 Entity->name = strdup(value);
703 }
704
705 // Return the expiration time of this token if so wanted.
706 //
707 if (expT && scitoken_get_expiration(scitoken, expT, &err_msg)) {
708 emsg = err_msg;
709 free(err_msg);
710 return false;
711 }
712
713
714 // Delete the scitokens
715 scitoken_destroy(scitoken);
716
717 // Deserialize checks the key, so we're good now.
718 return true;
719 }
720
721 virtual int Audit(const int accok,
722 const XrdSecEntity *Entity,
723 const char *path,
724 const Access_Operation oper,
725 XrdOucEnv *Env=0) override
726 {
727 return 0;
728 }
729
730 virtual int Test(const XrdAccPrivs priv,
731 const Access_Operation oper) override
732 {
733 return (m_chain ? m_chain->Test(priv, oper) : 0);
734 }
735
736 std::string GetConfigFile() {
737 return m_cfg_file;
738 }
739
740private:
741 XrdAccPrivs OnMissing(const XrdSecEntity *Entity, const char *path,
742 const Access_Operation oper, XrdOucEnv *env)
743 {
744 switch (m_authz_behavior) {
745 case AuthzBehavior::PASSTHROUGH:
746 return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
747 case AuthzBehavior::ALLOW:
748 return AddPriv(oper, XrdAccPriv_None);
749 case AuthzBehavior::DENY:
750 return XrdAccPriv_None;
751 }
752 // Code should be unreachable.
753 return XrdAccPriv_None;
754 }
755
756 bool GenerateAcls(const std::string &authz, uint64_t &cache_expiry, AccessRulesRaw &rules, std::string &username, std::string &token_subject, std::string &issuer, std::vector<MapRule> &map_rules, std::vector<std::string> &groups, uint32_t &authz_strategy) {
757 // Does this look like a JWT? If not, bail out early and
758 // do not pollute the log.
759 bool looks_good = true;
760 int separator_count = 0;
761 for (auto cur_char = authz.c_str(); *cur_char; cur_char++) {
762 if (*cur_char == '.') {
763 separator_count++;
764 if (separator_count > 2) {
765 break;
766 }
767 } else
768 if (!(*cur_char >= 65 && *cur_char <= 90) && // uppercase letters
769 !(*cur_char >= 97 && *cur_char <= 122) && // lowercase letters
770 !(*cur_char >= 48 && *cur_char <= 57) && // numbers
771 (*cur_char != 43) && (*cur_char != 47) && // + and /
772 (*cur_char != 45) && (*cur_char != 95)) // - and _
773 {
774 looks_good = false;
775 break;
776 }
777 }
778 if ((separator_count != 2) || (!looks_good)) {
779 m_log.Log(LogMask::Debug, "Parse", "Token does not appear to be a valid JWT; skipping.");
780 return false;
781 }
782
783 char *err_msg;
784 SciToken token = nullptr;
785 pthread_rwlock_rdlock(&m_config_lock);
786 auto retval = scitoken_deserialize(authz.c_str(), &token, &m_valid_issuers_array[0], &err_msg);
787 pthread_rwlock_unlock(&m_config_lock);
788 if (retval) {
789 // This originally looked like a JWT so log the failure.
790 m_log.Log(LogMask::Warning, "GenerateAcls", "Failed to deserialize SciToken:", err_msg);
791 free(err_msg);
792 return false;
793 }
794
795 long long expiry;
796 if (scitoken_get_expiration(token, &expiry, &err_msg)) {
797 m_log.Log(LogMask::Warning, "GenerateAcls", "Unable to determine token expiration:", err_msg);
798 free(err_msg);
799 scitoken_destroy(token);
800 return false;
801 }
802 if (expiry > 0) {
803 expiry = std::max(static_cast<int64_t>(monotonic_time() - expiry),
804 static_cast<int64_t>(60));
805 } else {
806 expiry = 60;
807 }
808
809 char *value = nullptr;
810 if (scitoken_get_claim_string(token, "iss", &value, &err_msg)) {
811 m_log.Log(LogMask::Warning, "GenerateAcls", "Failed to get issuer:", err_msg);
812 scitoken_destroy(token);
813 free(err_msg);
814 return false;
815 }
816 std::string token_issuer(value);
817 free(value);
818
819 pthread_rwlock_rdlock(&m_config_lock);
820 auto enf = enforcer_create(token_issuer.c_str(), &m_audiences_array[0], &err_msg);
821 pthread_rwlock_unlock(&m_config_lock);
822 if (!enf) {
823 m_log.Log(LogMask::Warning, "GenerateAcls", "Failed to create an enforcer:", err_msg);
824 scitoken_destroy(token);
825 free(err_msg);
826 return false;
827 }
828
829 Acl *acls = nullptr;
830 if (enforcer_generate_acls(enf, token, &acls, &err_msg)) {
831 scitoken_destroy(token);
832 enforcer_destroy(enf);
833 m_log.Log(LogMask::Warning, "GenerateAcls", "ACL generation from SciToken failed:", err_msg);
834 free(err_msg);
835 return false;
836 }
837 enforcer_destroy(enf);
838
839 pthread_rwlock_rdlock(&m_config_lock);
840 auto iter = m_issuers.find(token_issuer);
841 if (iter == m_issuers.end()) {
842 pthread_rwlock_unlock(&m_config_lock);
843 m_log.Log(LogMask::Warning, "GenerateAcls", "Authorized issuer without a config.");
844 scitoken_destroy(token);
845 return false;
846 }
847 const auto config = iter->second;
848 pthread_rwlock_unlock(&m_config_lock);
849 value = nullptr;
850
851 char **group_list;
852 std::vector<std::string> groups_parsed;
853 if (scitoken_get_claim_string_list(token, config.m_groups_claim.c_str(), &group_list, &err_msg) == 0) {
854 for (int idx=0; group_list[idx]; idx++) {
855 groups_parsed.emplace_back(group_list[idx]);
856 }
857 scitoken_free_string_list(group_list);
858 } else {
859 // Failing to parse groups is not fatal, but we should still warn about what's wrong
860 m_log.Log(LogMask::Warning, "GenerateAcls", "Failed to get token groups:", err_msg);
861 free(err_msg);
862 }
863
864 if (scitoken_get_claim_string(token, "sub", &value, &err_msg)) {
865 m_log.Log(LogMask::Warning, "GenerateAcls", "Failed to get token subject:", err_msg);
866 free(err_msg);
867 scitoken_destroy(token);
868 return false;
869 }
870 token_subject = std::string(value);
871 free(value);
872
873 auto tmp_username = token_subject;
874 if (!config.m_username_claim.empty()) {
875 if (scitoken_get_claim_string(token, config.m_username_claim.c_str(), &value, &err_msg)) {
876 m_log.Log(LogMask::Warning, "GenerateAcls", "Failed to get token username:", err_msg);
877 free(err_msg);
878 scitoken_destroy(token);
879 return false;
880 }
881 tmp_username = std::string(value);
882 free(value);
883 } else if (!config.m_map_subject) {
884 tmp_username = config.m_default_user;
885 }
886
887 for (auto rule : config.m_map_rules) {
888 for (auto path : config.m_base_paths) {
889 auto path_rule = rule;
890 path_rule.m_path_prefix = path + rule.m_path_prefix;
891 auto pos = path_rule.m_path_prefix.find("//");
892 if (pos != std::string::npos) {
893 path_rule.m_path_prefix.erase(pos + 1, 1);
894 }
895 map_rules.emplace_back(path_rule);
896 }
897 }
898
899 AccessRulesRaw xrd_rules;
900 int idx = 0;
901 std::set<std::string> paths_write_seen;
902 std::set<std::string> paths_create_or_modify_seen;
903 std::vector<std::string> acl_paths;
904 acl_paths.reserve(config.m_restricted_paths.size() + 1);
905 while (acls[idx].resource && acls[idx++].authz) {
906 acl_paths.clear();
907 const auto &acl_path = acls[idx-1].resource;
908 const auto &acl_authz = acls[idx-1].authz;
909 if (config.m_restricted_paths.empty()) {
910 acl_paths.push_back(acl_path);
911 } else {
912 auto acl_path_size = strlen(acl_path);
913 for (const auto &restricted_path : config.m_restricted_paths) {
914 // See if the acl_path is more specific than the restricted path; if so, accept it
915 // and move on to applying paths.
916 if (!strncmp(acl_path, restricted_path.c_str(), restricted_path.size())) {
917 // Only do prefix checking on full path components. If acl_path=/foobar and
918 // restricted_path=/foo, then we shouldn't authorize access to /foobar.
919 if (acl_path_size > restricted_path.size() && acl_path[restricted_path.size()] != '/') {
920 continue;
921 }
922 acl_paths.push_back(acl_path);
923 break;
924 }
925 // See if the restricted_path is more specific than the acl_path; if so, accept the
926 // restricted path as the ACL. Keep looping to see if other restricted paths add
927 // more possible authorizations.
928 if (!strncmp(acl_path, restricted_path.c_str(), acl_path_size)) {
929 // Only do prefix checking on full path components. If acl_path=/foo and
930 // restricted_path=/foobar, then we shouldn't authorize access to /foobar. Note:
931 // - The scitokens-cpp library guaranteees that acl_path is normalized and not
932 // of the form `/foo/`.
933 // - Hence, the only time that the acl_path can end in a '/' is when it is
934 // set to `/`.
935 if ((restricted_path.size() > acl_path_size && restricted_path[acl_path_size] != '/') && (acl_path_size != 1)) {
936 continue;
937 }
938 acl_paths.push_back(restricted_path);
939 }
940 }
941 }
942 for (const auto &acl_path : acl_paths) {
943 for (const auto &base_path : config.m_base_paths) {
944 if (!acl_path[0] || acl_path[0] != '/') {continue;}
945 std::string path;
946 MakeCanonical(base_path + acl_path, path);
947 if (!strcmp(acl_authz, "read")) {
948 xrd_rules.emplace_back(AOP_Read, path);
949 xrd_rules.emplace_back(AOP_Readdir, path);
950 xrd_rules.emplace_back(AOP_Stat, path);
951 } else if (!strcmp(acl_authz, "create")) {
952 paths_create_or_modify_seen.insert(path);
953 xrd_rules.emplace_back(AOP_Excl_Create, path);
954 xrd_rules.emplace_back(AOP_Mkdir, path);
955 xrd_rules.emplace_back(AOP_Rename, path);
956 xrd_rules.emplace_back(AOP_Excl_Insert, path);
957 xrd_rules.emplace_back(AOP_Stat, path);
958 } else if (!strcmp(acl_authz, "modify")) {
959 paths_create_or_modify_seen.insert(path);
960 xrd_rules.emplace_back(AOP_Create, path);
961 xrd_rules.emplace_back(AOP_Mkdir, path);
962 xrd_rules.emplace_back(AOP_Rename, path);
963 xrd_rules.emplace_back(AOP_Insert, path);
964 xrd_rules.emplace_back(AOP_Update, path);
965 xrd_rules.emplace_back(AOP_Chmod, path);
966 xrd_rules.emplace_back(AOP_Stat, path);
967 xrd_rules.emplace_back(AOP_Delete, path);
968 } else if (!strcmp(acl_authz, "write")) {
969 paths_write_seen.insert(path);
970 }
971 }
972 }
973 }
974 for (const auto &write_path : paths_write_seen) {
975 if (paths_create_or_modify_seen.find(write_path) == paths_create_or_modify_seen.end()) {
976 // This is a SciToken, add write ACLs.
977 xrd_rules.emplace_back(AOP_Create, write_path);
978 xrd_rules.emplace_back(AOP_Mkdir, write_path);
979 xrd_rules.emplace_back(AOP_Rename, write_path);
980 xrd_rules.emplace_back(AOP_Insert, write_path);
981 xrd_rules.emplace_back(AOP_Update, write_path);
982 xrd_rules.emplace_back(AOP_Stat, write_path);
983 xrd_rules.emplace_back(AOP_Chmod, write_path);
984 xrd_rules.emplace_back(AOP_Delete, write_path);
985 }
986 }
987 authz_strategy = config.m_authz_strategy;
988
989 cache_expiry = expiry;
990 rules = std::move(xrd_rules);
991 username = std::move(tmp_username);
992 issuer = std::move(token_issuer);
993 groups = std::move(groups_parsed);
994
995 return true;
996 }
997
998
999 bool Config(XrdOucEnv *envP) {
1000 // Set default mask for logging.
1001 m_log.setMsgMask(LogMask::Error | LogMask::Warning);
1002
1003 char *config_filename = nullptr;
1004 if (!XrdOucEnv::Import("XRDCONFIGFN", config_filename)) {
1005 return false;
1006 }
1007 XrdOucGatherConf scitokens_conf("scitokens.trace", &m_log);
1008 int result;
1009 if ((result = scitokens_conf.Gather(config_filename, XrdOucGatherConf::trim_lines)) < 0) {
1010 m_log.Emsg("Config", -result, "parsing config file", config_filename);
1011 return false;
1012 }
1013
1014 char *val;
1015 std::string map_filename;
1016 while (scitokens_conf.GetLine()) {
1017 m_log.setMsgMask(0);
1018 scitokens_conf.GetToken(); // Ignore the output; we asked for a single config value, trace
1019 if (!(val = scitokens_conf.GetToken())) {
1020 m_log.Emsg("Config", "scitokens.trace requires an argument. Usage: scitokens.trace [all|error|warning|info|debug|none]");
1021 return false;
1022 }
1023 do {
1024 if (!strcmp(val, "all")) {m_log.setMsgMask(m_log.getMsgMask() | LogMask::All);}
1025 else if (!strcmp(val, "error")) {m_log.setMsgMask(m_log.getMsgMask() | LogMask::Error);}
1026 else if (!strcmp(val, "warning")) {m_log.setMsgMask(m_log.getMsgMask() | LogMask::Warning);}
1027 else if (!strcmp(val, "info")) {m_log.setMsgMask(m_log.getMsgMask() | LogMask::Info);}
1028 else if (!strcmp(val, "debug")) {m_log.setMsgMask(m_log.getMsgMask() | LogMask::Debug);}
1029 else if (!strcmp(val, "none")) {m_log.setMsgMask(0);}
1030 else {m_log.Emsg("Config", "scitokens.trace encountered an unknown directive:", val); return false;}
1031 } while ((val = scitokens_conf.GetToken()));
1032 }
1033 m_log.Emsg("Config", "Logging levels enabled -", LogMaskToString(m_log.getMsgMask()).c_str());
1034
1035 auto xrdEnv = static_cast<XrdOucEnv*>(envP ? envP->GetPtr("xrdEnv*") : nullptr);
1036 auto tlsCtx = static_cast<XrdTlsContext*>(xrdEnv ? xrdEnv->GetPtr("XrdTlsContext*") : nullptr);
1037 if (tlsCtx) {
1038 auto params = tlsCtx->GetParams();
1039 if (params && !params->cafile.empty()) {
1040#ifdef HAVE_SCITOKEN_CONFIG_SET_STR
1041 scitoken_config_set_str("tls.ca_file", params->cafile.c_str(), nullptr);
1042#else
1043 m_log.Log(LogMask::Warning, "Config", "tls.ca_file is set but the platform's libscitokens.so does not support setting config parameters");
1044#endif
1045 }
1046 }
1047
1048 return Reconfig();
1049 }
1050
1051 bool ParseMapfile(const std::string &filename, std::vector<MapRule> &rules)
1052 {
1053 std::stringstream ss;
1054 std::ifstream mapfile(filename);
1055 if (!mapfile.is_open())
1056 {
1057 ss << "Error opening mapfile (" << filename << "): " << strerror(errno);
1058 m_log.Log(LogMask::Error, "ParseMapfile", ss.str().c_str());
1059 return false;
1060 }
1061 picojson::value val;
1062 auto err = picojson::parse(val, mapfile);
1063 if (!err.empty()) {
1064 ss << "Unable to parse mapfile (" << filename << ") as json: " << err;
1065 m_log.Log(LogMask::Error, "ParseMapfile", ss.str().c_str());
1066 return false;
1067 }
1068 if (!val.is<picojson::array>()) {
1069 ss << "Top-level element of the mapfile " << filename << " must be a list";
1070 m_log.Log(LogMask::Error, "ParseMapfile", ss.str().c_str());
1071 return false;
1072 }
1073 const auto& rule_list = val.get<picojson::array>();
1074 for (const auto &rule : rule_list)
1075 {
1076 if (!rule.is<picojson::object>()) {
1077 ss << "Mapfile " << filename << " must be a list of JSON objects; found non-object";
1078 m_log.Log(LogMask::Error, "ParseMapfile", ss.str().c_str());
1079 return false;
1080 }
1081 std::string path;
1082 std::string group;
1083 std::string sub;
1084 std::string username;
1085 std::string result;
1086 bool ignore = false;
1087 for (const auto &entry : rule.get<picojson::object>()) {
1088 if (!entry.second.is<std::string>()) {
1089 if (entry.first != "result" && entry.first != "group" && entry.first != "sub" && entry.first != "path") {continue;}
1090 ss << "In mapfile " << filename << ", rule entry for " << entry.first << " has non-string value";
1091 m_log.Log(LogMask::Error, "ParseMapfile", ss.str().c_str());
1092 return false;
1093 }
1094 if (entry.first == "result") {
1095 result = entry.second.get<std::string>();
1096 }
1097 else if (entry.first == "group") {
1098 group = entry.second.get<std::string>();
1099 }
1100 else if (entry.first == "sub") {
1101 sub = entry.second.get<std::string>();
1102 } else if (entry.first == "username") {
1103 username = entry.second.get<std::string>();
1104 } else if (entry.first == "path") {
1105 std::string norm_path;
1106 if (!MakeCanonical(entry.second.get<std::string>(), norm_path)) {
1107 ss << "In mapfile " << filename << " encountered a path " << entry.second.get<std::string>()
1108 << " that cannot be normalized";
1109 m_log.Log(LogMask::Error, "ParseMapfile", ss.str().c_str());
1110 return false;
1111 }
1112 path = norm_path;
1113 } else if (entry.first == "ignore") {
1114 ignore = true;
1115 break;
1116 }
1117 }
1118 if (ignore) continue;
1119 if (result.empty())
1120 {
1121 ss << "In mapfile " << filename << " encountered a rule without a 'result' attribute";
1122 m_log.Log(LogMask::Error, "ParseMapfile", ss.str().c_str());
1123 return false;
1124 }
1125 rules.emplace_back(sub, username, path, group, result);
1126 }
1127
1128 return true;
1129 }
1130
1131 bool Reconfig()
1132 {
1133 errno = 0;
1134 m_cfg_file = "/etc/xrootd/scitokens.cfg";
1135 if (!m_parms.empty()) {
1136 size_t pos = 0;
1137 std::vector<std::string> arg_list;
1138 do {
1139 while ((m_parms.size() > pos) && (m_parms[pos] == ' ')) {pos++;}
1140 auto next_pos = m_parms.find_first_of(", ", pos);
1141 auto next_arg = m_parms.substr(pos, next_pos - pos);
1142 pos = next_pos;
1143 if (!next_arg.empty()) {
1144 arg_list.emplace_back(std::move(next_arg));
1145 }
1146 } while (pos != std::string::npos);
1147
1148 for (const auto &arg : arg_list) {
1149 if (strncmp(arg.c_str(), "config=", 7)) {
1150 m_log.Log(LogMask::Error, "Reconfig", "Ignoring unknown configuration argument:", arg.c_str());
1151 continue;
1152 }
1153 m_cfg_file = std::string(arg.c_str() + 7);
1154 }
1155 }
1156 m_log.Log(LogMask::Info, "Reconfig", "Parsing configuration file:", m_cfg_file.c_str());
1157
1158 OverrideINIReader reader(m_cfg_file);
1159 if (reader.ParseError() < 0) {
1160 std::stringstream ss;
1161 ss << "Error opening config file (" << m_cfg_file << "): " << strerror(errno);
1162 m_log.Log(LogMask::Error, "Reconfig", ss.str().c_str());
1163 return false;
1164 } else if (reader.ParseError()) {
1165 std::stringstream ss;
1166 ss << "Parse error on line " << reader.ParseError() << " of file " << m_cfg_file;
1167 m_log.Log(LogMask::Error, "Reconfig", ss.str().c_str());
1168 return false;
1169 }
1170 std::vector<std::string> audiences;
1171 std::unordered_map<std::string, IssuerConfig> issuers;
1172 for (const auto &section : reader.Sections()) {
1173 std::string section_lower;
1174 std::transform(section.begin(), section.end(), std::back_inserter(section_lower),
1175 [](unsigned char c){ return std::tolower(c); });
1176
1177 if (section_lower.substr(0, 6) == "global") {
1178 auto audience = reader.Get(section, "audience", "");
1179 if (!audience.empty()) {
1180 size_t pos = 0;
1181 do {
1182 while (audience.size() > pos && (audience[pos] == ',' || audience[pos] == ' ')) {pos++;}
1183 auto next_pos = audience.find_first_of(", ", pos);
1184 auto next_aud = audience.substr(pos, next_pos - pos);
1185 pos = next_pos;
1186 if (!next_aud.empty()) {
1187 audiences.push_back(next_aud);
1188 }
1189 } while (pos != std::string::npos);
1190 }
1191 audience = reader.Get(section, "audience_json", "");
1192 if (!audience.empty()) {
1193 picojson::value json_obj;
1194 auto err = picojson::parse(json_obj, audience);
1195 if (!err.empty()) {
1196 m_log.Log(LogMask::Error, "Reconfig", "Unable to parse audience_json:", err.c_str());
1197 return false;
1198 }
1199 if (!json_obj.is<picojson::value::array>()) {
1200 m_log.Log(LogMask::Error, "Reconfig", "audience_json must be a list of strings; not a list.");
1201 return false;
1202 }
1203 for (const auto &val : json_obj.get<picojson::value::array>()) {
1204 if (!val.is<std::string>()) {
1205 m_log.Log(LogMask::Error, "Reconfig", "audience must be a list of strings; value is not a string.");
1206 return false;
1207 }
1208 audiences.push_back(val.get<std::string>());
1209 }
1210 }
1211 auto onmissing = reader.Get(section, "onmissing", "");
1212 if (onmissing == "passthrough") {
1213 m_authz_behavior = AuthzBehavior::PASSTHROUGH;
1214 } else if (onmissing == "allow") {
1215 m_authz_behavior = AuthzBehavior::ALLOW;
1216 } else if (onmissing == "deny") {
1217 m_authz_behavior = AuthzBehavior::DENY;
1218 } else if (!onmissing.empty()) {
1219 m_log.Log(LogMask::Error, "Reconfig", "Unknown value for onmissing key:", onmissing.c_str());
1220 return false;
1221 }
1222 }
1223
1224 if (section_lower.substr(0, 7) != "issuer ") {continue;}
1225
1226 auto issuer = reader.Get(section, "issuer", "");
1227 if (issuer.empty()) {
1228 m_log.Log(LogMask::Error, "Reconfig", "Ignoring section because 'issuer' attribute is not set:",
1229 section.c_str());
1230 continue;
1231 }
1232 m_log.Log(LogMask::Debug, "Reconfig", "Configuring issuer", issuer.c_str());
1233
1234 std::vector<MapRule> rules;
1235 auto name_mapfile = reader.Get(section, "name_mapfile", "");
1236 if (!name_mapfile.empty()) {
1237 if (!ParseMapfile(name_mapfile, rules)) {
1238 m_log.Log(LogMask::Error, "Reconfig", "Failed to parse mapfile; failing (re-)configuration", name_mapfile.c_str());
1239 return false;
1240 } else {
1241 m_log.Log(LogMask::Info, "Reconfig", "Successfully parsed SciTokens mapfile:", name_mapfile.c_str());
1242 }
1243 }
1244
1245 auto base_path = reader.Get(section, "base_path", "");
1246 if (base_path.empty()) {
1247 m_log.Log(LogMask::Error, "Reconfig", "Ignoring section because 'base_path' attribute is not set:",
1248 section.c_str());
1249 continue;
1250 }
1251
1252 size_t pos = 7;
1253 while (section.size() > pos && std::isspace(section[pos])) {pos++;}
1254
1255 auto name = section.substr(pos);
1256 if (name.empty()) {
1257 m_log.Log(LogMask::Error, "Reconfig", "Invalid section name:", section.c_str());
1258 continue;
1259 }
1260
1261 std::vector<std::string> base_paths;
1262 ParseCanonicalPaths(base_path, base_paths);
1263
1264 auto restricted_path = reader.Get(section, "restricted_path", "");
1265 std::vector<std::string> restricted_paths;
1266 if (!restricted_path.empty()) {
1267 ParseCanonicalPaths(restricted_path, restricted_paths);
1268 }
1269
1270 auto default_user = reader.Get(section, "default_user", "");
1271 auto map_subject = reader.GetBoolean(section, "map_subject", false);
1272 auto username_claim = reader.Get(section, "username_claim", "");
1273 auto groups_claim = reader.Get(section, "groups_claim", "wlcg.groups");
1274
1275 auto authz_strategy_str = reader.Get(section, "authorization_strategy", "");
1276 uint32_t authz_strategy = 0;
1277 if (authz_strategy_str.empty()) {
1278 authz_strategy = IssuerAuthz::Default;
1279 } else {
1280 std::istringstream authz_strategy_stream(authz_strategy_str);
1281 std::string authz_str;
1282 while (std::getline(authz_strategy_stream, authz_str, ' ')) {
1283 if (!strcasecmp(authz_str.c_str(), "capability")) {
1284 authz_strategy |= IssuerAuthz::Capability;
1285 } else if (!strcasecmp(authz_str.c_str(), "group")) {
1286 authz_strategy |= IssuerAuthz::Group;
1287 } else if (!strcasecmp(authz_str.c_str(), "mapping")) {
1288 authz_strategy |= IssuerAuthz::Mapping;
1289 } else {
1290 m_log.Log(LogMask::Error, "Reconfig", "Unknown authorization strategy (ignoring):", authz_str.c_str());
1291 }
1292 }
1293 }
1294
1295 issuers.emplace(std::piecewise_construct,
1296 std::forward_as_tuple(issuer),
1297 std::forward_as_tuple(name, issuer, base_paths, restricted_paths,
1298 map_subject, authz_strategy, default_user, username_claim, groups_claim, rules));
1299 }
1300
1301 if (issuers.empty()) {
1302 m_log.Log(LogMask::Warning, "Reconfig", "No issuers configured.");
1303 }
1304
1305 pthread_rwlock_wrlock(&m_config_lock);
1306 try {
1307 m_audiences = std::move(audiences);
1308 size_t idx = 0;
1309 m_audiences_array.resize(m_audiences.size() + 1);
1310 for (const auto &audience : m_audiences) {
1311 m_audiences_array[idx++] = audience.c_str();
1312 }
1313 m_audiences_array[idx] = nullptr;
1314
1315 m_issuers = std::move(issuers);
1316 m_valid_issuers_array.resize(m_issuers.size() + 1);
1317 idx = 0;
1318 for (const auto &issuer : m_issuers) {
1319 m_valid_issuers_array[idx++] = issuer.first.c_str();
1320 }
1321 m_valid_issuers_array[idx] = nullptr;
1322 } catch (...) {
1323 pthread_rwlock_unlock(&m_config_lock);
1324 return false;
1325 }
1326 pthread_rwlock_unlock(&m_config_lock);
1327 return true;
1328 }
1329
1330 void Check(uint64_t now)
1331 {
1332 if (now <= m_next_clean) {return;}
1333 std::lock_guard<std::mutex> guard(m_mutex);
1334
1335 for (auto iter = m_map.begin(); iter != m_map.end(); ) {
1336 if (iter->second->expired()) {
1337 iter = m_map.erase(iter);
1338 } else {
1339 ++iter;
1340 }
1341 }
1342 Reconfig();
1343
1344 m_next_clean = monotonic_time() + m_expiry_secs;
1345 }
1346
1347 bool m_config_lock_initialized{false};
1348 std::mutex m_mutex;
1349 pthread_rwlock_t m_config_lock;
1350 std::vector<std::string> m_audiences;
1351 std::vector<const char *> m_audiences_array;
1352 std::map<std::string, std::shared_ptr<XrdAccRules>> m_map;
1353 XrdAccAuthorize* m_chain;
1354 const std::string m_parms;
1355 std::vector<const char*> m_valid_issuers_array;
1356 std::unordered_map<std::string, IssuerConfig> m_issuers;
1357 uint64_t m_next_clean{0};
1358 XrdSysError m_log;
1359 AuthzBehavior m_authz_behavior{AuthzBehavior::PASSTHROUGH};
1360 std::string m_cfg_file;
1361
1362 static constexpr uint64_t m_expiry_secs = 60;
1363};
1364
1365void InitAccSciTokens(XrdSysLogger *lp, const char *cfn, const char *parm,
1366 XrdAccAuthorize *accP, XrdOucEnv *envP)
1367{
1368 try {
1369 accSciTokens = new XrdAccSciTokens(lp, parm, accP, envP);
1371 } catch (std::exception &) {
1372 }
1373}
1374
1375extern "C" {
1376
1378 const char *cfn,
1379 const char *parm,
1380 XrdOucEnv *envP,
1381 XrdAccAuthorize *accP)
1382{
1383 // Record the parent authorization plugin. There is no need to use
1384 // unique_ptr as all of this happens once in the main and only thread.
1385 //
1386
1387 // If we have been initialized by a previous load, them return that result.
1388 // Otherwise, it's the first time through, get a new SciTokens authorizer.
1389 //
1390 if (!accSciTokens) InitAccSciTokens(lp, cfn, parm, accP, envP);
1391 return accSciTokens;
1392}
1393
1395 const char *cfn,
1396 const char *parm)
1397{
1398 InitAccSciTokens(lp, cfn, parm, nullptr, nullptr);
1399 return accSciTokens;
1400}
1401
1403 const char *cfn,
1404 const char *parm,
1405 XrdOucEnv *envP)
1406{
1407 InitAccSciTokens(lp, cfn, parm, nullptr, envP);
1408 return accSciTokens;
1409}
1410
1411
1412}
Access_Operation
The following are supported operations.
@ AOP_Delete
rm() or rmdir()
@ AOP_Mkdir
mkdir()
@ AOP_Update
open() r/w or append
@ AOP_Create
open() with create
@ AOP_Readdir
opendir()
@ AOP_Chmod
chmod()
@ AOP_Any
Special for getting privs.
@ AOP_Stat
exists(), stat()
@ AOP_Rename
mv() for source
@ AOP_Read
open() r/o, prepare()
@ AOP_Excl_Create
open() with O_EXCL|O_CREAT
@ AOP_Insert
mv() for target
@ AOP_Lock
n/a
@ AOP_Chown
chown()
@ AOP_Excl_Insert
mv() where destination doesn't exist.
XrdAccPrivs
@ XrdAccPriv_Mkdir
@ XrdAccPriv_Chown
@ XrdAccPriv_Insert
@ XrdAccPriv_Lookup
@ XrdAccPriv_Rename
@ XrdAccPriv_Update
@ XrdAccPriv_Read
@ XrdAccPriv_Lock
@ XrdAccPriv_None
@ XrdAccPriv_Delete
@ XrdAccPriv_Create
@ XrdAccPriv_Readdir
@ XrdAccPriv_Chmod
static bool is_subdirectory(const std::string &dir, const std::string &subdir)
XrdAccSciTokens * accSciTokens
XrdAccAuthorize * XrdAccAuthorizeObjAdd(XrdSysLogger *lp, const char *cfn, const char *parm, XrdOucEnv *envP, XrdAccAuthorize *accP)
XrdSciTokensHelper * SciTokensHelper
XrdVERSIONINFO(XrdAccAuthorizeObject, XrdAccSciTokens)
void InitAccSciTokens(XrdSysLogger *lp, const char *cfn, const char *parm, XrdAccAuthorize *accP, XrdOucEnv *envP)
XrdAccAuthorize * XrdAccAuthorizeObject2(XrdSysLogger *lp, const char *cfn, const char *parm, XrdOucEnv *envP)
XrdAccAuthorize * XrdAccAuthorizeObject(XrdSysLogger *lp, const char *cfn, const char *parm)
bool Debug
int emsg(int rc, char *msg)
OverrideINIReader(std::string filename)
static int ValueHandler(void *user, const char *section, const char *name, const char *value)
virtual int Test(const XrdAccPrivs priv, const Access_Operation oper)=0
virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env=0)=0
const std::string & get_default_username() const
XrdAccRules(uint64_t expiry_time, const std::string &username, const std::string &token_subject, const std::string &issuer, const std::vector< MapRule > &rules, const std::vector< std::string > &groups, uint32_t authz_strategy)
bool apply(Access_Operation oper, std::string path)
const std::string & get_token_subject() const
bool expired() const
uint32_t get_authz_strategy() const
size_t size() const
void parse(const AccessRulesRaw &rules)
const std::string & get_issuer() const
const std::string str() const
std::string get_username(const std::string &req_path) const
const std::vector< std::string > & groups() const
virtual int Audit(const int accok, const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env=0) override
virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *env) override
XrdAccSciTokens(XrdSysLogger *lp, const char *parms, XrdAccAuthorize *chain, XrdOucEnv *envP)
virtual Issuers IssuerList() override
virtual bool Validate(const char *token, std::string &emsg, long long *expT, XrdSecEntity *Entity) override
virtual int Test(const XrdAccPrivs priv, const Access_Operation oper) override
std::string GetConfigFile()
static bool Import(const char *var, char *&val)
Definition XrdOucEnv.cc:204
char * Get(const char *varname)
Definition XrdOucEnv.hh:69
void * GetPtr(const char *varname)
Definition XrdOucEnv.cc:263
@ trim_lines
Prefix trimmed lines.
std::vector< ValidIssuer > Issuers
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 * name
Entity's name.
char * role
Entity's role(s)
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
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)
void setMsgMask(int mask)
void Log(int mask, const char *esfx, const char *text1, const char *text2=0, const char *text3=0)
const CTX_Params * GetParams()
XrdTlsContext * tlsCtx
Definition XrdGlobals.cc:52
std::string LogMaskToString(int mask)
XrdOucEnv * envP
Definition XrdPss.cc:109