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