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