XRootD
XrdMacaroonsAuthz.cc
Go to the documentation of this file.
1 
2 #include <stdexcept>
3 #include <sstream>
4 
5 #include <ctime>
6 
7 #include "macaroons.h"
8 
9 #include "XrdOuc/XrdOucEnv.hh"
10 #include "XrdSec/XrdSecEntity.hh"
12 
13 #include "XrdMacaroonsHandler.hh"
14 #include "XrdMacaroonsAuthz.hh"
15 
16 using namespace Macaroons;
17 
18 
19 namespace {
20 
21 class AuthzCheck
22 {
23 public:
24  AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t max_duration, XrdSysError &log);
25 
26  const std::string &GetSecName() const {return m_sec_name;}
27  const std::string &GetErrorMessage() const {return m_emsg;}
28 
29  static int verify_before_s(void *authz_ptr,
30  const unsigned char *pred,
31  size_t pred_sz);
32 
33  static int verify_activity_s(void *authz_ptr,
34  const unsigned char *pred,
35  size_t pred_sz);
36 
37  static int verify_path_s(void *authz_ptr,
38  const unsigned char *pred,
39  size_t pred_sz);
40 
41  static int verify_name_s(void *authz_ptr,
42  const unsigned char *pred,
43  size_t pred_sz);
44 
45 private:
46  int verify_before(const unsigned char *pred, size_t pred_sz);
47  int verify_activity(const unsigned char *pred, size_t pred_sz);
48  int verify_path(const unsigned char *pred, size_t pred_sz);
49  int verify_name(const unsigned char *pred, size_t pred_sz);
50 
51  ssize_t m_max_duration;
52  XrdSysError &m_log;
53  std::string m_emsg;
54  const std::string m_path;
55  std::string m_desired_activity;
56  std::string m_sec_name;
57  Access_Operation m_oper;
58  time_t m_now;
59 };
60 
61 
62 static XrdAccPrivs AddPriv(Access_Operation op, XrdAccPrivs privs)
63 {
64  int new_privs = privs;
65  switch (op) {
66  case AOP_Any:
67  break;
68  case AOP_Chmod:
69  new_privs |= static_cast<int>(XrdAccPriv_Chmod);
70  break;
71  case AOP_Chown:
72  new_privs |= static_cast<int>(XrdAccPriv_Chown);
73  break;
74  case AOP_Excl_Create: // fallthrough
75  case AOP_Create:
76  new_privs |= static_cast<int>(XrdAccPriv_Create);
77  break;
78  case AOP_Delete:
79  new_privs |= static_cast<int>(XrdAccPriv_Delete);
80  break;
81  case AOP_Excl_Insert: // fallthrough
82  case AOP_Insert:
83  new_privs |= static_cast<int>(XrdAccPriv_Insert);
84  break;
85  case AOP_Lock:
86  new_privs |= static_cast<int>(XrdAccPriv_Lock);
87  break;
88  case AOP_Mkdir:
89  new_privs |= static_cast<int>(XrdAccPriv_Mkdir);
90  break;
91  case AOP_Read:
92  new_privs |= static_cast<int>(XrdAccPriv_Read);
93  break;
94  case AOP_Readdir:
95  new_privs |= static_cast<int>(XrdAccPriv_Readdir);
96  break;
97  case AOP_Rename:
98  new_privs |= static_cast<int>(XrdAccPriv_Rename);
99  break;
100  case AOP_Stat:
101  new_privs |= static_cast<int>(XrdAccPriv_Lookup);
102  break;
103  case AOP_Update:
104  new_privs |= static_cast<int>(XrdAccPriv_Update);
105  break;
106  };
107  return static_cast<XrdAccPrivs>(new_privs);
108 }
109 
110 
111 // Accept any value of the path, name, or activity caveats
112 int validate_verify_empty(void *emsg_ptr,
113  const unsigned char *pred,
114  size_t pred_sz)
115 {
116  if ((pred_sz >= 5) && (!memcmp(reinterpret_cast<const char *>(pred), "path:", 5) ||
117  !memcmp(reinterpret_cast<const char *>(pred), "name:", 5)))
118  {
119  return 0;
120  }
121  if ((pred_sz >= 9) && (!memcmp(reinterpret_cast<const char *>(pred), "activity:", 9)))
122  {
123  return 0;
124  }
125  return 1;
126 }
127 
128 }
129 
130 
131 Authz::Authz(XrdSysLogger *log, char const *config, XrdAccAuthorize *chain)
132  : m_max_duration(86400),
133  m_chain(chain),
134  m_log(log, "macarons_"),
135  m_authz_behavior(static_cast<int>(Handler::AuthzBehavior::PASSTHROUGH))
136 {
137  Handler::AuthzBehavior behavior(Handler::AuthzBehavior::PASSTHROUGH);
138  XrdOucEnv env;
139  if (!Handler::Config(config, &env, &m_log, m_location, m_secret, m_max_duration, behavior))
140  {
141  throw std::runtime_error("Macaroon authorization config failed.");
142  }
143  m_authz_behavior = static_cast<int>(behavior);
144 }
145 
146 
148 Authz::OnMissing(const XrdSecEntity *Entity, const char *path,
149  const Access_Operation oper, XrdOucEnv *env)
150 {
151  switch (m_authz_behavior) {
152  case Handler::AuthzBehavior::PASSTHROUGH:
153  return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
154  case Handler::AuthzBehavior::ALLOW:
155  return AddPriv(oper, XrdAccPriv_None);;
156  case Handler::AuthzBehavior::DENY:
157  return XrdAccPriv_None;
158  }
159  // Code should be unreachable.
160  return XrdAccPriv_None;
161 }
162 
164 Authz::Access(const XrdSecEntity *Entity, const char *path,
165  const Access_Operation oper, XrdOucEnv *env)
166 {
167  // We don't allow any testing to occur in this authz module, preventing
168  // a macaroon to be used to receive further macaroons.
169  if (oper == AOP_Any)
170  {
171  return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
172  }
173 
174  const char *authz = env ? env->Get("authz") : nullptr;
175  if (authz && !strncmp(authz, "Bearer%20", 9))
176  {
177  authz += 9;
178  }
179 
180  // If there's no request-specific token, check for a ZTN session token
181  if (!authz && Entity && !strcmp("ztn", Entity->prot) && Entity->creds &&
182  Entity->credslen && Entity->creds[Entity->credslen] == '\0')
183  {
184  authz = Entity->creds;
185  }
186 
187  if (!authz) {
188  return OnMissing(Entity, path, oper, env);
189  }
190 
191  macaroon_returncode mac_err = MACAROON_SUCCESS;
192  struct macaroon* macaroon = macaroon_deserialize(
193  authz,
194  &mac_err);
195  if (!macaroon)
196  {
197  // Do not log - might be other token type!
198  //m_log.Emsg("Access", "Failed to parse the macaroon");
199  return OnMissing(Entity, path, oper, env);
200  }
201 
202  struct macaroon_verifier *verifier = macaroon_verifier_create();
203  if (!verifier)
204  {
205  m_log.Emsg("Access", "Failed to create a new macaroon verifier");
206  return XrdAccPriv_None;
207  }
208  if (!path)
209  {
210  m_log.Emsg("Access", "Request with no provided path.");
211  macaroon_verifier_destroy(verifier);
212  return XrdAccPriv_None;
213  }
214 
215  AuthzCheck check_helper(path, oper, m_max_duration, m_log);
216 
217  if (macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_before_s, &check_helper, &mac_err) ||
218  macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_activity_s, &check_helper, &mac_err) ||
219  macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_name_s, &check_helper, &mac_err) ||
220  macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_path_s, &check_helper, &mac_err))
221  {
222  m_log.Emsg("Access", "Failed to configure caveat verifier:");
223  macaroon_verifier_destroy(verifier);
224  return XrdAccPriv_None;
225  }
226 
227  const unsigned char *macaroon_loc;
228  size_t location_sz;
229  macaroon_location(macaroon, &macaroon_loc, &location_sz);
230  if (strncmp(reinterpret_cast<const char *>(macaroon_loc), m_location.c_str(), location_sz))
231  {
232  std::string location_str(reinterpret_cast<const char *>(macaroon_loc), location_sz);
233  m_log.Emsg("Access", "Macaroon is for incorrect location", location_str.c_str());
234  macaroon_verifier_destroy(verifier);
235  macaroon_destroy(macaroon);
236  return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
237  }
238 
239  if (macaroon_verify(verifier, macaroon,
240  reinterpret_cast<const unsigned char *>(m_secret.c_str()),
241  m_secret.size(),
242  NULL, 0, // discharge macaroons
243  &mac_err))
244  {
245  m_log.Log(LogMask::Debug, "Access", "Macaroon verification failed");
246  macaroon_verifier_destroy(verifier);
247  macaroon_destroy(macaroon);
248  return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
249  }
250  macaroon_verifier_destroy(verifier);
251 
252  const unsigned char *macaroon_id;
253  size_t id_sz;
254  macaroon_identifier(macaroon, &macaroon_id, &id_sz);
255 
256  std::string macaroon_id_str(reinterpret_cast<const char *>(macaroon_id), id_sz);
257  m_log.Log(LogMask::Info, "Access", "Macaroon verification successful; ID", macaroon_id_str.c_str());
258  macaroon_destroy(macaroon);
259 
260  // Copy the name, if present into the macaroon, into the credential object.
261  if (Entity && check_helper.GetSecName().size()) {
262  const std::string &username = check_helper.GetSecName();
263  m_log.Log(LogMask::Debug, "Access", "Setting the request name to", username.c_str());
264  Entity->eaAPI->Add("request.name", username,true);
265  }
266 
267  // We passed verification - give the correct privilege.
268  return AddPriv(oper, XrdAccPriv_None);
269 }
270 
271 bool Authz::Validate(const char *token,
272  std::string &emsg,
273  long long *expT,
274  XrdSecEntity *entP)
275 {
276  macaroon_returncode mac_err = MACAROON_SUCCESS;
277  std::unique_ptr<struct macaroon, decltype(&macaroon_destroy)> macaroon(
278  macaroon_deserialize(token, &mac_err),
279  &macaroon_destroy);
280 
281  if (!macaroon)
282  {
283  emsg = "Failed to deserialize the token as a macaroon";
284  // Purposely log at debug level in case if this validation is ever
285  // chained so we don't have overly-chatty logs.
286  m_log.Log(LogMask::Debug, "Validate", emsg.c_str());
287  return false;
288  }
289 
290  std::unique_ptr<struct macaroon_verifier, decltype(&macaroon_verifier_destroy)> verifier(
291  macaroon_verifier_create(), &macaroon_verifier_destroy);
292  if (!verifier)
293  {
294  emsg = "Internal error: failed to create a verifier.";
295  m_log.Log(LogMask::Error, "Validate", emsg.c_str());
296  return false;
297  }
298 
299  // Note the path and operation here are ignored as we won't use those validators
300  AuthzCheck check_helper("/", AOP_Read, m_max_duration, m_log);
301 
302  if (macaroon_verifier_satisfy_general(verifier.get(), AuthzCheck::verify_before_s, &check_helper, &mac_err) ||
303  macaroon_verifier_satisfy_general(verifier.get(), validate_verify_empty, nullptr, &mac_err))
304  {
305  emsg = "Failed to configure the verifier";
306  m_log.Log(LogMask::Error, "Validate", emsg.c_str());
307  return false;
308  }
309 
310  const unsigned char *macaroon_loc;
311  size_t location_sz;
312  macaroon_location(macaroon.get(), &macaroon_loc, &location_sz);
313  if (strncmp(reinterpret_cast<const char *>(macaroon_loc), m_location.c_str(), location_sz))
314  {
315  emsg = "Macaroon contains incorrect location: " +
316  std::string(reinterpret_cast<const char *>(macaroon_loc), location_sz);
317  m_log.Log(LogMask::Warning, "Validate", emsg.c_str(), ("all.sitename is " + m_location).c_str());
318  return false;
319  }
320 
321  if (macaroon_verify(verifier.get(), macaroon.get(),
322  reinterpret_cast<const unsigned char *>(m_secret.c_str()),
323  m_secret.size(),
324  nullptr, 0,
325  &mac_err))
326  {
327  emsg = "Macaroon verification error" + (check_helper.GetErrorMessage().size() ?
328  (", " + check_helper.GetErrorMessage()) : "");
329  m_log.Log(LogMask::Warning, "Validate", emsg.c_str());
330  return false;
331  }
332 
333  const unsigned char *macaroon_id;
334  size_t id_sz;
335  macaroon_identifier(macaroon.get(), &macaroon_id, &id_sz);
336  m_log.Log(LogMask::Info, "Validate", ("Macaroon verification successful; ID " +
337  std::string(reinterpret_cast<const char *>(macaroon_id), id_sz)).c_str());
338 
339  return true;
340 }
341 
342 
343 AuthzCheck::AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t max_duration, XrdSysError &log)
344  : m_max_duration(max_duration),
345  m_log(log),
346  m_path(NormalizeSlashes(req_path)),
347  m_oper(req_oper),
348  m_now(time(NULL))
349 {
350  switch (m_oper)
351  {
352  case AOP_Any:
353  break;
354  case AOP_Chmod:
355  case AOP_Chown:
356  m_desired_activity = "UPDATE_METADATA";
357  break;
358  case AOP_Insert:
359  case AOP_Lock:
360  case AOP_Mkdir:
361  case AOP_Update:
362  case AOP_Create:
363  m_desired_activity = "MANAGE";
364  break;
365  case AOP_Rename:
366  case AOP_Excl_Create:
367  case AOP_Excl_Insert:
368  m_desired_activity = "UPLOAD";
369  break;
370  case AOP_Delete:
371  m_desired_activity = "DELETE";
372  break;
373  case AOP_Read:
374  m_desired_activity = "DOWNLOAD";
375  break;
376  case AOP_Readdir:
377  m_desired_activity = "LIST";
378  break;
379  case AOP_Stat:
380  m_desired_activity = "READ_METADATA";
381  };
382 }
383 
384 
385 int
386 AuthzCheck::verify_before_s(void *authz_ptr,
387  const unsigned char *pred,
388  size_t pred_sz)
389 {
390  return static_cast<AuthzCheck*>(authz_ptr)->verify_before(pred, pred_sz);
391 }
392 
393 
394 int
395 AuthzCheck::verify_activity_s(void *authz_ptr,
396  const unsigned char *pred,
397  size_t pred_sz)
398 {
399  return static_cast<AuthzCheck*>(authz_ptr)->verify_activity(pred, pred_sz);
400 }
401 
402 
403 int
404 AuthzCheck::verify_path_s(void *authz_ptr,
405  const unsigned char *pred,
406  size_t pred_sz)
407 {
408  return static_cast<AuthzCheck*>(authz_ptr)->verify_path(pred, pred_sz);
409 }
410 
411 
412 int
413 AuthzCheck::verify_name_s(void *authz_ptr,
414  const unsigned char *pred,
415  size_t pred_sz)
416 {
417  return static_cast<AuthzCheck*>(authz_ptr)->verify_name(pred, pred_sz);
418 }
419 
420 
421 int
422 AuthzCheck::verify_before(const unsigned char * pred, size_t pred_sz)
423 {
424  std::string pred_str(reinterpret_cast<const char *>(pred), pred_sz);
425  if (strncmp("before:", pred_str.c_str(), 7))
426  {
427  return 1;
428  }
429  m_log.Log(LogMask::Debug, "AuthzCheck", "Checking macaroon for expiration; caveat:", pred_str.c_str());
430 
431  struct tm caveat_tm;
432  if (strptime(&pred_str[7], "%Y-%m-%dT%H:%M:%SZ", &caveat_tm) == nullptr)
433  {
434  m_emsg = "Failed to parse time string: " + pred_str.substr(7);
435  m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str());
436  return 1;
437  }
438  caveat_tm.tm_isdst = -1;
439 
440  time_t caveat_time = timegm(&caveat_tm);
441  if (-1 == caveat_time)
442  {
443  m_emsg = "Failed to generate unix time: " + pred_str.substr(7);
444  m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str());
445  return 1;
446  }
447  if ((m_max_duration > 0) && (caveat_time > m_now + m_max_duration))
448  {
449  m_emsg = "Max token age is greater than configured max duration; rejecting";
450  m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str());
451  return 1;
452  }
453 
454  int result = (m_now >= caveat_time);
455  if (!result)
456  {
457  m_log.Log(LogMask::Debug, "AuthzCheck", "Macaroon has not expired.");
458  }
459  else
460  {
461  m_emsg = "Macaroon expired at " + pred_str.substr(7);
462  m_log.Log(LogMask::Debug, "AuthzCheck", m_emsg.c_str());
463  }
464  return result;
465 }
466 
467 
468 int
469 AuthzCheck::verify_activity(const unsigned char * pred, size_t pred_sz)
470 {
471  if (!m_desired_activity.size()) {return 1;}
472  std::string pred_str(reinterpret_cast<const char *>(pred), pred_sz);
473  if (strncmp("activity:", pred_str.c_str(), 9)) {return 1;}
474  m_log.Log(LogMask::Debug, "AuthzCheck", "running verify activity", pred_str.c_str());
475 
476  std::stringstream ss(pred_str.substr(9));
477  for (std::string activity; std::getline(ss, activity, ','); )
478  {
479  // Any allowed activity also implies "READ_METADATA"
480  if (m_desired_activity == "READ_METADATA") {return 0;}
481  if ((activity == m_desired_activity) || ((m_desired_activity == "UPLOAD") && (activity == "MANAGE")))
482  {
483  m_log.Log(LogMask::Debug, "AuthzCheck", "macaroon has desired activity", activity.c_str());
484  return 0;
485  }
486  }
487  m_log.Log(LogMask::Info, "AuthzCheck", "macaroon does NOT have desired activity", m_desired_activity.c_str());
488  return 1;
489 }
490 
491 
492 int
493 AuthzCheck::verify_path(const unsigned char * pred, size_t pred_sz)
494 {
495  std::string pred_str_raw(reinterpret_cast<const char *>(pred), pred_sz);
496  if (strncmp("path:", pred_str_raw.c_str(), 5)) {return 1;}
497  std::string pred_str = NormalizeSlashes(pred_str_raw.substr(5));
498  m_log.Log(LogMask::Debug, "AuthzCheck", "running verify path", pred_str.c_str());
499 
500  if ((m_path.find("/./") != std::string::npos) ||
501  (m_path.find("/../") != std::string::npos))
502  {
503  m_log.Log(LogMask::Info, "AuthzCheck", "invalid requested path", m_path.c_str());
504  return 1;
505  }
506 
507  int result = strncmp(pred_str.c_str(), m_path.c_str(), pred_str.size());
508  if (!result)
509  {
510  m_log.Log(LogMask::Debug, "AuthzCheck", "path request verified for", m_path.c_str());
511  }
512  // READ_METADATA permission for /foo/bar automatically implies permission
513  // to READ_METADATA for /foo.
514  else if (m_oper == AOP_Stat)
515  {
516  result = strncmp(m_path.c_str(), pred_str.c_str(), m_path.size());
517  if (!result) {m_log.Log(LogMask::Debug, "AuthzCheck", "READ_METADATA path request verified for", m_path.c_str());}
518  else {m_log.Log(LogMask::Debug, "AuthzCheck", "READ_METADATA path request NOT allowed", m_path.c_str());}
519  }
520  else
521  {
522  m_log.Log(LogMask::Debug, "AuthzCheck", "path request NOT allowed", m_path.c_str());
523  }
524 
525  return result;
526 }
527 
528 
529 int
530 AuthzCheck::verify_name(const unsigned char * pred, size_t pred_sz)
531 {
532  std::string pred_str(reinterpret_cast<const char *>(pred), pred_sz);
533  if (strncmp("name:", pred_str.c_str(), 5)) {return 1;}
534  if (pred_str.size() < 6) {return 1;}
535  m_log.Log(LogMask::Debug, "AuthzCheck", "Verifying macaroon with", pred_str.c_str());
536 
537  // Make a copy of the name for the XrdSecEntity; this will be used later.
538  m_sec_name = pred_str.substr(5);
539 
540  return 0;
541 }
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
bool Debug
void getline(uchar *buff, int blen)
int emsg(int rc, char *msg)
@ Error
virtual bool Validate(const char *token, std::string &emsg, long long *expT, XrdSecEntity *entP) override
Authz(XrdSysLogger *lp, const char *parms, XrdAccAuthorize *chain)
virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *env) override
static bool Config(const char *config, XrdOucEnv *env, XrdSysError *log, std::string &location, std::string &secret, ssize_t &max_duration, AuthzBehavior &behavior)
virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env=0)=0
char * Get(const char *varname)
Definition: XrdOucEnv.hh:69
bool Add(XrdSecAttr &attr)
int credslen
Length of the 'creds' data.
Definition: XrdSecEntity.hh:78
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
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:95
void Log(int mask, const char *esfx, const char *text1, const char *text2=0, const char *text3=0)
Definition: XrdSysError.hh:133
std::string NormalizeSlashes(const std::string &)
@ Warning