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