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