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