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