Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement XrdSciTokensHelper interface for macaroons. #1802

Merged
merged 1 commit into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/XrdMacaroons/XrdMacaroons.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extern XrdAccAuthorize *XrdAccDefaultAuthorizeObject(XrdSysLogger *lp,
const char *parm,
XrdVersionInfo &myVer);

XrdSciTokensHelper *SciTokensHelper = nullptr;

extern "C" {

Expand All @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down
131 changes: 121 additions & 10 deletions src/XrdMacaroons/XrdMacaroonsAuthz.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -105,6 +107,24 @@ static XrdAccPrivs AddPriv(Access_Operation op, XrdAccPrivs privs)
return static_cast<XrdAccPrivs>(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<const char *>(pred), "path:", 5) ||
!memcmp(reinterpret_cast<const char *>(pred), "name:", 5)))
{
return 0;
}
if ((pred_sz >= 9) && (!memcmp(reinterpret_cast<const char *>(pred), "activity:", 9)))
{
return 0;
}
return 1;
}

}


Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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<struct macaroon, decltype(&macaroon_destroy)> 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<struct macaroon_verifier, decltype(&macaroon_verifier_destroy)> 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<const char *>(macaroon_loc), m_location.c_str(), location_sz))
{
emsg = "Macaroon contains incorrect location: " +
std::string(reinterpret_cast<const char *>(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<const unsigned char *>(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<const char *>(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),
Expand Down Expand Up @@ -325,31 +426,41 @@ 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;

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;
}

Expand Down
20 changes: 16 additions & 4 deletions src/XrdMacaroons/XrdMacaroonsAuthz.hh
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

#include "XrdAcc/XrdAccAuthorize.hh"
#include "XrdSciTokens/XrdSciTokensHelper.hh"
#include "XrdSys/XrdSysError.hh"


Expand All @@ -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);
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/XrdMacaroons/export-lib-symbols
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ global:
XrdAccAuthorizeObject*;
XrdAccAuthorizeObjAdd*;
XrdHttpGetExtHandler*;
SciTokensHelper;

local:
*;
Expand Down