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