XRootD
Loading...
Searching...
No Matches
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"
12
14#include "XrdMacaroonsAuthz.hh"
15
16using namespace Macaroons;
17
18
19namespace {
20
21class AuthzCheck
22{
23public:
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
45private:
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
62static 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
112int 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
131Authz::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{
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
148Authz::OnMissing(const XrdSecEntity *Entity, const char *path,
149 const Access_Operation oper, XrdOucEnv *env)
150{
151 switch (m_authz_behavior) {
153 return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
155 return AddPriv(oper, XrdAccPriv_None);;
157 return XrdAccPriv_None;
158 }
159 // Code should be unreachable.
160 return XrdAccPriv_None;
161}
162
164Authz::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
271bool 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
343AuthzCheck::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
385int
386AuthzCheck::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
394int
395AuthzCheck::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
403int
404AuthzCheck::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
412int
413AuthzCheck::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
421int
422AuthzCheck::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
468int
469AuthzCheck::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
492int
493AuthzCheck::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
529int
530AuthzCheck::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
@ XrdAccPriv_Mkdir
@ XrdAccPriv_Chown
@ XrdAccPriv_Insert
@ XrdAccPriv_Lookup
@ XrdAccPriv_Rename
@ XrdAccPriv_Update
@ XrdAccPriv_Read
@ XrdAccPriv_Lock
@ XrdAccPriv_None
@ XrdAccPriv_Delete
@ XrdAccPriv_Create
@ XrdAccPriv_Readdir
@ XrdAccPriv_Chmod
int emsg(int rc, char *msg)
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.
XrdSecEntityAttr * eaAPI
non-const API to attributes
char prot[XrdSecPROTOIDSIZE]
Auth protocol used (e.g. krb5)
char * creds
Raw entity credentials or cert.
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
void Log(int mask, const char *esfx, const char *text1, const char *text2=0, const char *text3=0)
std::string NormalizeSlashes(const std::string &)