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