From 14331c3fd43104b566212a615b9eb49ba63d0388 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 15 Oct 2022 13:32:36 -0500 Subject: [PATCH] Implement XrdSciTokensHelper interface for macaroons. In order to use ZTN with a token library, the library must provide a XrdSciTokensHelper object. This implements the interface inside the Macaroons::Authz object, allowing ZTN authentication to be utilized with a macaroon-based token. This can be enabled with the following configuration directive: ``` sec.protocol -tokenlib libXrdMacaroons-5.so ``` --- src/XrdMacaroons/XrdMacaroons.cc | 9 +- src/XrdMacaroons/XrdMacaroonsAuthz.cc | 131 ++++++++++++++++++++++++-- src/XrdMacaroons/XrdMacaroonsAuthz.hh | 20 +++- src/XrdMacaroons/export-lib-symbols | 1 + 4 files changed, 145 insertions(+), 16 deletions(-) diff --git a/src/XrdMacaroons/XrdMacaroons.cc b/src/XrdMacaroons/XrdMacaroons.cc index f2d355c55fd..6d43507f7c6 100644 --- a/src/XrdMacaroons/XrdMacaroons.cc +++ b/src/XrdMacaroons/XrdMacaroons.cc @@ -27,6 +27,7 @@ extern XrdAccAuthorize *XrdAccDefaultAuthorizeObject(XrdSysLogger *lp, const char *parm, XrdVersionInfo &myVer); +XrdSciTokensHelper *SciTokensHelper = nullptr; extern "C" { @@ -38,7 +39,9 @@ XrdAccAuthorize *XrdAccAuthorizeObjAdd(XrdSysLogger *log, { try { - return new Macaroons::Authz(log, config, chain_authz); + auto new_authz = new Macaroons::Authz(log, config, chain_authz); + SciTokensHelper = new_authz; + return new_authz; } catch (std::runtime_error &e) { @@ -109,7 +112,9 @@ XrdAccAuthorize *XrdAccAuthorizeObject(XrdSysLogger *log, } try { - return new Macaroons::Authz(log, config, chain_authz); + auto new_authz = new Macaroons::Authz(log, config, chain_authz); + SciTokensHelper = new_authz; + return new_authz; } catch (const std::runtime_error &e) { diff --git a/src/XrdMacaroons/XrdMacaroonsAuthz.cc b/src/XrdMacaroons/XrdMacaroonsAuthz.cc index 13a206f2e8a..2494e6750ff 100644 --- a/src/XrdMacaroons/XrdMacaroonsAuthz.cc +++ b/src/XrdMacaroons/XrdMacaroonsAuthz.cc @@ -24,6 +24,7 @@ class AuthzCheck AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t m_max_duration, XrdSysError &log); const std::string &GetSecName() const {return m_sec_name;} + const std::string &GetErrorMessage() const {return m_emsg;} static int verify_before_s(void *authz_ptr, const unsigned char *pred, @@ -49,6 +50,7 @@ class AuthzCheck ssize_t m_max_duration; XrdSysError &m_log; + std::string m_emsg; const std::string m_path; std::string m_desired_activity; std::string m_sec_name; @@ -105,6 +107,24 @@ static XrdAccPrivs AddPriv(Access_Operation op, XrdAccPrivs privs) return static_cast(new_privs); } + +// Accept any value of the path, name, or activity caveats +int validate_verify_empty(void *emsg_ptr, + const unsigned char *pred, + size_t pred_sz) +{ + if ((pred_sz >= 5) && (!memcmp(reinterpret_cast(pred), "path:", 5) || + !memcmp(reinterpret_cast(pred), "name:", 5))) + { + return 0; + } + if ((pred_sz >= 9) && (!memcmp(reinterpret_cast(pred), "activity:", 9))) + { + return 0; + } + return 1; +} + } @@ -144,19 +164,29 @@ XrdAccPrivs Authz::Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *env) { - const char *authz = env ? env->Get("authz") : nullptr; // We don't allow any testing to occur in this authz module, preventing // a macaroon to be used to receive further macaroons. if (oper == AOP_Any) { return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None; } - if (!authz || strncmp(authz, "Bearer%20", 9)) + + const char *authz = env ? env->Get("authz") : nullptr; + if (authz && !strncmp(authz, "Bearer%20", 9)) { - //m_log.Emsg("Access", "No bearer token present"); + authz += 9; + } + + // If there's no request-specific token, check for a ZTN session token + if (!authz && Entity && !strcmp("ztn", Entity->prot) && Entity->creds && + Entity->credslen && Entity->creds[Entity->credslen] == '\0') + { + authz = Entity->creds; + } + + if (!authz) { return OnMissing(Entity, path, oper, env); } - authz += 9; macaroon_returncode mac_err = MACAROON_SUCCESS; struct macaroon* macaroon = macaroon_deserialize( @@ -238,6 +268,77 @@ Authz::Access(const XrdSecEntity *Entity, const char *path, return AddPriv(oper, XrdAccPriv_None); } +bool Authz::Validate(const char *token, + std::string &emsg, + long long *expT, + XrdSecEntity *entP) +{ + macaroon_returncode mac_err = MACAROON_SUCCESS; + std::unique_ptr macaroon( + macaroon_deserialize(token, &mac_err), + &macaroon_destroy); + + if (!macaroon) + { + emsg = "Failed to deserialize the token as a macaroon"; + // Purposely log at debug level in case if this validation is ever + // chained so we don't have overly-chatty logs. + m_log.Log(LogMask::Debug, "Validate", emsg.c_str()); + return false; + } + + std::unique_ptr verifier( + macaroon_verifier_create(), &macaroon_verifier_destroy); + if (!verifier) + { + emsg = "Internal error: failed to create a verifier."; + m_log.Log(LogMask::Error, "Validate", emsg.c_str()); + return false; + } + + // Note the path and operation here are ignored as we won't use those validators + AuthzCheck check_helper("/", AOP_Read, m_max_duration, m_log); + + if (macaroon_verifier_satisfy_general(verifier.get(), AuthzCheck::verify_before_s, &check_helper, &mac_err) || + macaroon_verifier_satisfy_general(verifier.get(), validate_verify_empty, nullptr, &mac_err)) + { + emsg = "Failed to configure the verifier"; + m_log.Log(LogMask::Error, "Validate", emsg.c_str()); + return false; + } + + const unsigned char *macaroon_loc; + size_t location_sz; + macaroon_location(macaroon.get(), &macaroon_loc, &location_sz); + if (strncmp(reinterpret_cast(macaroon_loc), m_location.c_str(), location_sz)) + { + emsg = "Macaroon contains incorrect location: " + + std::string(reinterpret_cast(macaroon_loc), location_sz); + m_log.Log(LogMask::Warning, "Validate", emsg.c_str(), ("all.sitename is " + m_location).c_str()); + return false; + } + + if (macaroon_verify(verifier.get(), macaroon.get(), + reinterpret_cast(m_secret.c_str()), + m_secret.size(), + nullptr, 0, + &mac_err)) + { + emsg = "Macaroon verification error" + (check_helper.GetErrorMessage().size() ? + (", " + check_helper.GetErrorMessage()) : ""); + m_log.Log(LogMask::Warning, "Validate", emsg.c_str()); + return false; + } + + const unsigned char *macaroon_id; + size_t id_sz; + macaroon_identifier(macaroon.get(), &macaroon_id, &id_sz); + m_log.Log(LogMask::Info, "Validate", ("Macaroon verification successful; ID " + + std::string(reinterpret_cast(macaroon_id), id_sz)).c_str()); + + return true; +} + AuthzCheck::AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t max_duration, XrdSysError &log) : m_max_duration(max_duration), @@ -325,12 +426,13 @@ AuthzCheck::verify_before(const unsigned char * pred, size_t pred_sz) { return 1; } - m_log.Log(LogMask::Debug, "AuthzCheck", "running verify before", pred_str.c_str()); + m_log.Log(LogMask::Debug, "AuthzCheck", "Checking macaroon for expiration; caveat:", pred_str.c_str()); struct tm caveat_tm; if (strptime(&pred_str[7], "%Y-%m-%dT%H:%M:%SZ", &caveat_tm) == nullptr) { - m_log.Log(LogMask::Debug, "AuthzCheck", "failed to parse time string", &pred_str[7]); + m_emsg = "Failed to parse time string: " + pred_str.substr(7); + m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str()); return 1; } caveat_tm.tm_isdst = -1; @@ -338,18 +440,27 @@ AuthzCheck::verify_before(const unsigned char * pred, size_t pred_sz) time_t caveat_time = timegm(&caveat_tm); if (-1 == caveat_time) { - m_log.Log(LogMask::Debug, "AuthzCheck", "failed to generate unix time", &pred_str[7]); + m_emsg = "Failed to generate unix time: " + pred_str.substr(7); + m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str()); return 1; } if ((m_max_duration > 0) && (caveat_time > m_now + m_max_duration)) { - m_log.Log(LogMask::Warning, "AuthzCheck", "Max token age is greater than configured max duration; rejecting"); + m_emsg = "Max token age is greater than configured max duration; rejecting"; + m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str()); return 1; } int result = (m_now >= caveat_time); - if (!result) m_log.Log(LogMask::Debug, "AuthzCheck", "verify before successful"); - else m_log.Log(LogMask::Debug, "AuthzCheck", "verify before failed"); + if (!result) + { + m_log.Log(LogMask::Debug, "AuthzCheck", "Macaroon has not expired."); + } + else + { + m_emsg = "Macaroon expired at " + pred_str.substr(7); + m_log.Log(LogMask::Debug, "AuthzCheck", m_emsg.c_str()); + } return result; } diff --git a/src/XrdMacaroons/XrdMacaroonsAuthz.hh b/src/XrdMacaroons/XrdMacaroonsAuthz.hh index 4a9ac3d251d..acf88bc9c73 100644 --- a/src/XrdMacaroons/XrdMacaroonsAuthz.hh +++ b/src/XrdMacaroons/XrdMacaroonsAuthz.hh @@ -1,5 +1,6 @@ #include "XrdAcc/XrdAccAuthorize.hh" +#include "XrdSciTokens/XrdSciTokensHelper.hh" #include "XrdSys/XrdSysError.hh" @@ -8,7 +9,7 @@ class XrdSysError; namespace Macaroons { -class Authz : public XrdAccAuthorize +class Authz final : public XrdAccAuthorize, public XrdSciTokensHelper { public: Authz(XrdSysLogger *lp, const char *parms, XrdAccAuthorize *chain); @@ -18,21 +19,32 @@ public: virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, - XrdOucEnv *env); + XrdOucEnv *env) override; + + // Do a minimal validation that this is a non-expired token; used + // for session tokens. + virtual bool Validate(const char *token, + std::string &emsg, + long long *expT, + XrdSecEntity *entP) override; virtual int Audit(const int accok, const XrdSecEntity *Entity, const char *path, const Access_Operation oper, - XrdOucEnv *Env) + XrdOucEnv *Env) override { return 0; } virtual int Test(const XrdAccPrivs priv, - const Access_Operation oper) + const Access_Operation oper) override { return 0; } + // Macaroons don't have a concept off an "issuers"; return an empty + // list. + virtual Issuers IssuerList() {return Issuers();} + private: XrdAccPrivs OnMissing(const XrdSecEntity *Entity, const char *path, diff --git a/src/XrdMacaroons/export-lib-symbols b/src/XrdMacaroons/export-lib-symbols index 46713c737e4..0a6279d9046 100644 --- a/src/XrdMacaroons/export-lib-symbols +++ b/src/XrdMacaroons/export-lib-symbols @@ -3,6 +3,7 @@ global: XrdAccAuthorizeObject*; XrdAccAuthorizeObjAdd*; XrdHttpGetExtHandler*; + SciTokensHelper; local: *;