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