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 else if (!authz && (authz = env ? env->Get("access_token") : nullptr) && !strncmp(authz, "Bearer%20", 9))
187 {
188 authz += 9;
189 }
190
191 // If there's no request-specific token, check for a ZTN session token
192 if (!authz && Entity && !strcmp("ztn", Entity->prot) && Entity->creds &&
193 Entity->credslen && Entity->creds[Entity->credslen] == '\0')
194 {
195 authz = Entity->creds;
196 }
197
198 if (!authz) {
199 return OnMissing(Entity, path, oper, env);
200 }
201
202 macaroon_returncode mac_err = MACAROON_SUCCESS;
203 struct macaroon* macaroon = macaroon_deserialize(
204 authz,
205 &mac_err);
206 if (!macaroon)
207 {
208 // Do not log - might be other token type!
209 //m_log.Emsg("Access", "Failed to parse the macaroon");
210 return OnMissing(Entity, path, oper, env);
211 }
212
213 struct macaroon_verifier *verifier = macaroon_verifier_create();
214 if (!verifier)
215 {
216 m_log.Emsg("Access", "Failed to create a new macaroon verifier");
217 return XrdAccPriv_None;
218 }
219 if (!path)
220 {
221 m_log.Emsg("Access", "Request with no provided path.");
222 macaroon_verifier_destroy(verifier);
223 return XrdAccPriv_None;
224 }
225
226 AuthzCheck check_helper(path, oper, m_max_duration, m_log);
227
228 if (macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_before_s, &check_helper, &mac_err) ||
229 macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_activity_s, &check_helper, &mac_err) ||
230 macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_name_s, &check_helper, &mac_err) ||
231 macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_path_s, &check_helper, &mac_err))
232 {
233 m_log.Emsg("Access", "Failed to configure caveat verifier:");
234 macaroon_verifier_destroy(verifier);
235 return XrdAccPriv_None;
236 }
237
238 const unsigned char *macaroon_loc;
239 size_t location_sz;
240 macaroon_location(macaroon, &macaroon_loc, &location_sz);
241 if (strncmp(reinterpret_cast<const char *>(macaroon_loc), m_location.c_str(), location_sz))
242 {
243 std::string location_str(reinterpret_cast<const char *>(macaroon_loc), location_sz);
244 m_log.Emsg("Access", "Macaroon is for incorrect location", location_str.c_str());
245 macaroon_verifier_destroy(verifier);
246 macaroon_destroy(macaroon);
247 return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
248 }
249
250 if (macaroon_verify(verifier, macaroon,
251 reinterpret_cast<const unsigned char *>(m_secret.c_str()),
252 m_secret.size(),
253 NULL, 0, // discharge macaroons
254 &mac_err))
255 {
256 m_log.Log(LogMask::Debug, "Access", "Macaroon verification failed");
257 macaroon_verifier_destroy(verifier);
258 macaroon_destroy(macaroon);
259 return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None;
260 }
261 macaroon_verifier_destroy(verifier);
262
263 const unsigned char *macaroon_id;
264 size_t id_sz;
265 macaroon_identifier(macaroon, &macaroon_id, &id_sz);
266
267 std::string macaroon_id_str(reinterpret_cast<const char *>(macaroon_id), id_sz);
268 m_log.Log(LogMask::Info, "Access", "Macaroon verification successful; ID", macaroon_id_str.c_str());
269 macaroon_destroy(macaroon);
270
271 // Copy the name, if present into the macaroon, into the credential object.
272 if (Entity && check_helper.GetSecName().size()) {
273 const std::string &username = check_helper.GetSecName();
274 m_log.Log(LogMask::Debug, "Access", "Setting the request name to", username.c_str());
275 Entity->eaAPI->Add("request.name", username,true);
276 }
277
278 // We passed verification - give the correct privilege.
279 return AddPriv(oper, XrdAccPriv_None);
280}
281
282bool Authz::Validate(const char *token,
283 std::string &emsg,
284 long long *expT,
285 XrdSecEntity *entP)
286{
287 macaroon_returncode mac_err = MACAROON_SUCCESS;
288 std::unique_ptr<struct macaroon, decltype(&macaroon_destroy)> macaroon(
289 macaroon_deserialize(token, &mac_err),
290 &macaroon_destroy);
291
292 if (!macaroon)
293 {
294 emsg = "Failed to deserialize the token as a macaroon";
295 // Purposely log at debug level in case if this validation is ever
296 // chained so we don't have overly-chatty logs.
297 m_log.Log(LogMask::Debug, "Validate", emsg.c_str());
298 return false;
299 }
300
301 std::unique_ptr<struct macaroon_verifier, decltype(&macaroon_verifier_destroy)> verifier(
302 macaroon_verifier_create(), &macaroon_verifier_destroy);
303 if (!verifier)
304 {
305 emsg = "Internal error: failed to create a verifier.";
306 m_log.Log(LogMask::Error, "Validate", emsg.c_str());
307 return false;
308 }
309
310 // Note the path and operation here are ignored as we won't use those validators
311 AuthzCheck check_helper("/", AOP_Read, m_max_duration, m_log);
312
313 if (macaroon_verifier_satisfy_general(verifier.get(), AuthzCheck::verify_before_s, &check_helper, &mac_err) ||
314 macaroon_verifier_satisfy_general(verifier.get(), validate_verify_empty, nullptr, &mac_err))
315 {
316 emsg = "Failed to configure the verifier";
317 m_log.Log(LogMask::Error, "Validate", emsg.c_str());
318 return false;
319 }
320
321 const unsigned char *macaroon_loc;
322 size_t location_sz;
323 macaroon_location(macaroon.get(), &macaroon_loc, &location_sz);
324 if (strncmp(reinterpret_cast<const char *>(macaroon_loc), m_location.c_str(), location_sz))
325 {
326 emsg = "Macaroon contains incorrect location: " +
327 std::string(reinterpret_cast<const char *>(macaroon_loc), location_sz);
328 m_log.Log(LogMask::Warning, "Validate", emsg.c_str(), ("all.sitename is " + m_location).c_str());
329 return false;
330 }
331
332 if (macaroon_verify(verifier.get(), macaroon.get(),
333 reinterpret_cast<const unsigned char *>(m_secret.c_str()),
334 m_secret.size(),
335 nullptr, 0,
336 &mac_err))
337 {
338 emsg = "Macaroon verification error" + (check_helper.GetErrorMessage().size() ?
339 (", " + check_helper.GetErrorMessage()) : "");
340 m_log.Log(LogMask::Warning, "Validate", emsg.c_str());
341 return false;
342 }
343
344 const unsigned char *macaroon_id;
345 size_t id_sz;
346 macaroon_identifier(macaroon.get(), &macaroon_id, &id_sz);
347 m_log.Log(LogMask::Info, "Validate", ("Macaroon verification successful; ID " +
348 std::string(reinterpret_cast<const char *>(macaroon_id), id_sz)).c_str());
349
350 return true;
351}
352
353
354AuthzCheck::AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t max_duration, XrdSysError &log)
355 : m_max_duration(max_duration),
356 m_log(log),
357 m_path(NormalizeSlashes(req_path)),
358 m_oper(req_oper),
359 m_now(time(NULL))
360{
361 switch (m_oper)
362 {
363 case AOP_Any:
364 break;
365 case AOP_Chmod:
366 case AOP_Chown:
367 m_desired_activity = "UPDATE_METADATA";
368 break;
369 case AOP_Insert:
370 case AOP_Lock:
371 case AOP_Mkdir:
372 case AOP_Update:
373 case AOP_Create:
374 m_desired_activity = "MANAGE";
375 break;
376 case AOP_Rename:
377 case AOP_Excl_Create:
378 case AOP_Excl_Insert:
379 m_desired_activity = "UPLOAD";
380 break;
381 case AOP_Delete:
382 m_desired_activity = "DELETE";
383 break;
384 case AOP_Read:
385 m_desired_activity = "DOWNLOAD";
386 break;
387 case AOP_Readdir:
388 m_desired_activity = "LIST";
389 break;
390 case AOP_Stat:
391 m_desired_activity = "READ_METADATA";
392 break;
393 case AOP_Stage:
394 case AOP_Poll:
395 break;
396 };
397}
398
399
400int
401AuthzCheck::verify_before_s(void *authz_ptr,
402 const unsigned char *pred,
403 size_t pred_sz)
404{
405 return static_cast<AuthzCheck*>(authz_ptr)->verify_before(pred, pred_sz);
406}
407
408
409int
410AuthzCheck::verify_activity_s(void *authz_ptr,
411 const unsigned char *pred,
412 size_t pred_sz)
413{
414 return static_cast<AuthzCheck*>(authz_ptr)->verify_activity(pred, pred_sz);
415}
416
417
418int
419AuthzCheck::verify_path_s(void *authz_ptr,
420 const unsigned char *pred,
421 size_t pred_sz)
422{
423 return static_cast<AuthzCheck*>(authz_ptr)->verify_path(pred, pred_sz);
424}
425
426
427int
428AuthzCheck::verify_name_s(void *authz_ptr,
429 const unsigned char *pred,
430 size_t pred_sz)
431{
432 return static_cast<AuthzCheck*>(authz_ptr)->verify_name(pred, pred_sz);
433}
434
435
436int
437AuthzCheck::verify_before(const unsigned char * pred, size_t pred_sz)
438{
439 std::string pred_str(reinterpret_cast<const char *>(pred), pred_sz);
440 if (strncmp("before:", pred_str.c_str(), 7))
441 {
442 return 1;
443 }
444 m_log.Log(LogMask::Debug, "AuthzCheck", "Checking macaroon for expiration; caveat:", pred_str.c_str());
445
446 struct tm caveat_tm;
447 if (strptime(&pred_str[7], "%Y-%m-%dT%H:%M:%SZ", &caveat_tm) == nullptr)
448 {
449 m_emsg = "Failed to parse time string: " + pred_str.substr(7);
450 m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str());
451 return 1;
452 }
453 caveat_tm.tm_isdst = -1;
454
455 time_t caveat_time = timegm(&caveat_tm);
456 if (-1 == caveat_time)
457 {
458 m_emsg = "Failed to generate unix time: " + pred_str.substr(7);
459 m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str());
460 return 1;
461 }
462 if ((m_max_duration > 0) && (caveat_time > m_now + m_max_duration))
463 {
464 m_emsg = "Max token age is greater than configured max duration; rejecting";
465 m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str());
466 return 1;
467 }
468
469 int result = (m_now >= caveat_time);
470 if (!result)
471 {
472 m_log.Log(LogMask::Debug, "AuthzCheck", "Macaroon has not expired.");
473 }
474 else
475 {
476 m_emsg = "Macaroon expired at " + pred_str.substr(7);
477 m_log.Log(LogMask::Debug, "AuthzCheck", m_emsg.c_str());
478 }
479 return result;
480}
481
482
483int
484AuthzCheck::verify_activity(const unsigned char * pred, size_t pred_sz)
485{
486 if (!m_desired_activity.size()) {return 1;}
487 std::string pred_str(reinterpret_cast<const char *>(pred), pred_sz);
488 if (strncmp("activity:", pred_str.c_str(), 9)) {return 1;}
489 m_log.Log(LogMask::Debug, "AuthzCheck", "running verify activity", pred_str.c_str());
490
491 std::stringstream ss(pred_str.substr(9));
492 for (std::string activity; std::getline(ss, activity, ','); )
493 {
494 // Any allowed activity also implies "READ_METADATA"
495 if (m_desired_activity == "READ_METADATA") {return 0;}
496 if ((activity == m_desired_activity) || ((m_desired_activity == "UPLOAD") && (activity == "MANAGE")))
497 {
498 m_log.Log(LogMask::Debug, "AuthzCheck", "macaroon has desired activity", activity.c_str());
499 return 0;
500 }
501 }
502 m_log.Log(LogMask::Info, "AuthzCheck", "macaroon does NOT have desired activity", m_desired_activity.c_str());
503 return 1;
504}
505
506
507int
508AuthzCheck::verify_path(const unsigned char * pred, size_t pred_sz)
509{
510 std::string pred_str_raw(reinterpret_cast<const char *>(pred), pred_sz);
511 if (strncmp("path:", pred_str_raw.c_str(), 5)) {return 1;}
512 std::string pred_str = NormalizeSlashes(pred_str_raw.substr(5));
513 m_log.Log(LogMask::Debug, "AuthzCheck", "running verify path", pred_str.c_str());
514
515 if ((m_path.find("/./") != std::string::npos) ||
516 (m_path.find("/../") != std::string::npos))
517 {
518 m_log.Log(LogMask::Info, "AuthzCheck", "invalid requested path", m_path.c_str());
519 return 1;
520 }
521
522 // Allow operations under subdirectories and not substrings
523 // For e.g. pred_str = "/data/sudir/mydir"
524 // Allows m_path = /data/subdir/mydir/newdir
525 // But rejects, m_path = /data/subdir/mydirmycoolname/newdir
526 int is_subdir = is_subdirectory(pred_str, m_path);
527 if (is_subdir)
528 {
529 m_log.Log(LogMask::Debug, "AuthzCheck", "path request verified for", m_path.c_str());
530 }
531
532 // READ_METADATA (i.e AOP_Stat) permission for /foo/bar automatically implies permission
533 // to READ_METADATA for /foo.
534 // Similarly, MKDIR Pemissions for a parent path is implied.
535 else if (m_oper == AOP_Stat || m_oper == AOP_Mkdir)
536 {
537 is_subdir = is_subdirectory(m_path, pred_str);
538 const char *opName = (m_oper == AOP_Stat) ? "READ_METADATA" : "MKDIR";
539 m_log.Log(LogMask::Debug, "AuthzCheck",
540 (std::string(opName) + (is_subdir? " Path request verified for" : " Path request NOT allowed for")).c_str(),
541 m_path.c_str());
542 }
543 else
544 {
545 m_log.Log(LogMask::Debug, "AuthzCheck", "path request NOT allowed", m_path.c_str());
546 }
547
548 return !is_subdir;
549}
550
551
552int
553AuthzCheck::verify_name(const unsigned char * pred, size_t pred_sz)
554{
555 std::string pred_str(reinterpret_cast<const char *>(pred), pred_sz);
556 if (strncmp("name:", pred_str.c_str(), 5)) {return 1;}
557 if (pred_str.size() < 6) {return 1;}
558 m_log.Log(LogMask::Debug, "AuthzCheck", "Verifying macaroon with", pred_str.c_str());
559
560 // Make a copy of the name for the XrdSecEntity; this will be used later.
561 m_sec_name = pred_str.substr(5);
562
563 return 0;
564}
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_view dir, const std::string_view 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 &)