diff --git a/cmake/FindMacaroons.cmake b/cmake/FindMacaroons.cmake new file mode 100644 index 00000000000..204bf48f5df --- /dev/null +++ b/cmake/FindMacaroons.cmake @@ -0,0 +1,21 @@ + +FIND_PATH(MACAROONS_INCLUDES macaroons.h + HINTS + ${MACAROONS_DIR} + $ENV{MACAROONS_DIR} + /usr + PATH_SUFFIXES include +) + +FIND_LIBRARY(MACAROONS_LIB macaroons + HINTS + ${MACAROONS_DIR} + $ENV{MACAROONS_DIR} + /usr + PATH_SUFFIXES lib + PATH_SUFFIXES .libs +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Macaroons DEFAULT_MSG MACAROONS_INCLUDES MACAROONS_LIB) + diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index 9554515293d..330c8c402fd 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -9,14 +9,14 @@ if( "${CMAKE_BUILD_TYPE}" STREQUAL "" ) endif() endif() -define_default( PLUGIN_VERSION 4 ) -define_default( ENABLE_FUSE TRUE ) -define_default( ENABLE_CRYPTO TRUE ) -define_default( ENABLE_KRB5 TRUE ) -define_default( ENABLE_READLINE TRUE ) -define_default( ENABLE_XRDCL TRUE ) -define_default( ENABLE_TESTS FALSE ) -define_default( ENABLE_HTTP TRUE ) -define_default( ENABLE_CEPH TRUE ) -define_default( ENABLE_PYTHON TRUE ) +define_default( PLUGIN_VERSION 4 ) +define_default( ENABLE_FUSE TRUE ) +define_default( ENABLE_CRYPTO TRUE ) +define_default( ENABLE_KRB5 TRUE ) +define_default( ENABLE_READLINE TRUE ) +define_default( ENABLE_XRDCL TRUE ) +define_default( ENABLE_TESTS FALSE ) +define_default( ENABLE_HTTP TRUE ) +define_default( ENABLE_CEPH TRUE ) +define_default( ENABLE_PYTHON TRUE ) define_default( XRD_PYTHON_REQ_VERSION 2.4 ) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index d44ad887469..5619967a518 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -89,6 +89,17 @@ check_function_exists( curl_multi_wait HAVE_CURL_MULTI_WAIT ) compiler_define_if_found( HAVE_CURL_MULTI_WAIT HAVE_CURL_MULTI_WAIT ) endif() +find_package( Macaroons ) +include (FindPkgConfig) +pkg_check_modules(JSON json-c) +pkg_check_modules(UUID uuid) + +if( Macaroons_FOUND AND JSON_FOUND AND UUID_FOUND ) + set( BUILD_MACAROONS TRUE ) +else() + set( BUILD_MACAROONS FALSE ) +endif() + if( ENABLE_CEPH ) find_package( ceph ) if( CEPH_FOUND ) diff --git a/cmake/XRootDSummary.cmake b/cmake/XRootDSummary.cmake index 606c613e676..f44b0e53c76 100644 --- a/cmake/XRootDSummary.cmake +++ b/cmake/XRootDSummary.cmake @@ -2,16 +2,17 @@ # Print the configuration summary #------------------------------------------------------------------------------- set( TRUE_VAR TRUE ) -component_status( READLINE ENABLE_READLINE READLINE_FOUND ) -component_status( FUSE BUILD_FUSE FUSE_FOUND ) -component_status( CRYPTO BUILD_CRYPTO OPENSSL_FOUND ) -component_status( KRB5 BUILD_KRB5 KERBEROS5_FOUND ) -component_status( XRDCL ENABLE_XRDCL TRUE_VAR ) -component_status( TESTS BUILD_TESTS CPPUNIT_FOUND ) -component_status( HTTP BUILD_HTTP OPENSSL_FOUND ) -component_status( TPC BUILD_TPC CURL_FOUND ) -component_status( CEPH BUILD_CEPH CEPH_FOUND ) -component_status( PYTHON BUILD_PYTHON PYTHON_FOUND ) +component_status( READLINE ENABLE_READLINE READLINE_FOUND ) +component_status( FUSE BUILD_FUSE FUSE_FOUND ) +component_status( CRYPTO BUILD_CRYPTO OPENSSL_FOUND ) +component_status( KRB5 BUILD_KRB5 KERBEROS5_FOUND ) +component_status( XRDCL ENABLE_XRDCL TRUE_VAR ) +component_status( TESTS BUILD_TESTS CPPUNIT_FOUND ) +component_status( HTTP BUILD_HTTP OPENSSL_FOUND ) +component_status( TPC BUILD_TPC CURL_FOUND ) +component_status( MACAROONS BUILD_MACAROONS MACAROONS_FOUND ) +component_status( CEPH BUILD_CEPH CEPH_FOUND ) +component_status( PYTHON BUILD_PYTHON PYTHON_FOUND ) message( STATUS "----------------------------------------" ) message( STATUS "Installation path: " ${CMAKE_INSTALL_PREFIX} ) @@ -28,6 +29,7 @@ message( STATUS "XrdCl: " ${STATUS_XRDCL} ) message( STATUS "Tests: " ${STATUS_TESTS} ) message( STATUS "HTTP support: " ${STATUS_HTTP} ) message( STATUS "HTTP TPC support: " ${STATUS_TPC} ) +message( STATUS "Macaroons support: " ${STATUS_MACAROONS} ) message( STATUS "CEPH support: " ${STATUS_CEPH} ) message( STATUS "Python support: " ${STATUS_PYTHON} ) message( STATUS "----------------------------------------" ) diff --git a/packaging/rhel/xrootd.spec.in b/packaging/rhel/xrootd.spec.in index 5f6d5375eee..37915595bc5 100644 --- a/packaging/rhel/xrootd.spec.in +++ b/packaging/rhel/xrootd.spec.in @@ -63,6 +63,9 @@ BuildRequires: krb5-devel BuildRequires: zlib-devel BuildRequires: ncurses-devel BuildRequires: libcurl-devel +BuildRequires: libuuid-devel +BuildRequires: libmacaroons-devel +BuildRequires: json-c-devel BuildRequires: python2-devel %if %{?fedora}%{!?fedora:0} >= 13 @@ -781,6 +784,7 @@ fi %{_libdir}/libXrdHttp-4.so %{_libdir}/libXrdHttpTPC-4.so %{_libdir}/libXrdHttpUtils.so.* +%{_libdir}/libXrdMacaroons-4.so %{_libdir}/libXrdN2No2p-4.so %{_libdir}/libXrdOssSIgpfsT-4.so %{_libdir}/libXrdServer.so.* diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eafa85da0bc..dac3116a0bb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,6 +41,10 @@ if( BUILD_HTTP ) include( XrdTpc ) endif() +if( BUILD_MACAROONS ) + include( XrdMacaroons ) +endif() + if( BUILD_CEPH ) include( XrdCeph ) endif() diff --git a/src/XrdMacaroons.cmake b/src/XrdMacaroons.cmake new file mode 100644 index 00000000000..f6d42004190 --- /dev/null +++ b/src/XrdMacaroons.cmake @@ -0,0 +1,54 @@ +include( XRootDCommon ) + +#------------------------------------------------------------------------------- +# Modules +#------------------------------------------------------------------------------- +set( LIB_XRD_MACAROONS XrdMacaroons-${PLUGIN_VERSION} ) + +#------------------------------------------------------------------------------- +# Shared library version +#------------------------------------------------------------------------------- + +if( BUILD_MACAROONS ) + include_directories(${MACAROONS_INCLUDES} ${JSON_INCLUDE_DIRS} ${UUID_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR}) + + add_library( + ${LIB_XRD_MACAROONS} + MODULE + XrdMacaroons/XrdMacaroons.cc + XrdMacaroons/XrdMacaroonsHandler.cc XrdMacaroons/XrdMacaroonsHandler.hh + XrdMacaroons/XrdMacaroonsAuthz.cc XrdMacaroons/XrdMacaroonsAuthz.hh + XrdMacaroons/XrdMacaroonsConfigure.cc) + + target_link_libraries( + ${LIB_XRD_MACAROONS} -ldl + XrdHttpUtils + XrdUtils + XrdServer + ${MACAROONS_LIB} + ${JSON_LIBRARIES} + ${XROOTD_HTTP_LIB} + ${UUID_LIBRARIES} + ${OPENSSL_CRYPTO_LIBRARY}) + + if( MacOSX ) + SET( MACAROONS_LINK_FLAGS "-Wl") + else() + SET( MACAROONS_LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/src/XrdMacaroons/export-lib-symbols" ) + endif() + + set_target_properties( + ${LIB_XRD_MACAROONS} + PROPERTIES + INTERFACE_LINK_LIBRARIES "" + LINK_INTERFACE_LIBRARIES "" + LINK_FLAGS "${MACAROONS_LINK_FLAGS}") + + #----------------------------------------------------------------------------- + # Install + #----------------------------------------------------------------------------- + install( + TARGETS ${LIB_XRD_MACAROONS} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +endif() diff --git a/src/XrdMacaroons/README.md b/src/XrdMacaroons/README.md new file mode 100644 index 00000000000..beac66bbc97 --- /dev/null +++ b/src/XrdMacaroons/README.md @@ -0,0 +1,62 @@ + +Macaroon support for Xrootd +=========================== + +This plugin adds support for macaroon-style authorizations in XRootD, particularly +for the XrdHttp protocol implementation. + +Configuration +============= + +To enable, you need to add three lines to the configuration file: + +``` +ofs.authlib libXrdMacaroons.so +http.exthandler xrdmacaroons libXrdMacaroons.so +macaroons.secretkey /etc/xrootd/macaroon-secret +all.sitename Example_Site +``` + +You will need to change `all.sitename` accordingly. The secret key is a symmetric +key necessary to verify macaroons; the same key must be deployed to all XRootD +servers in your cluster. + +The secret key must be base64-encoded. The most straightforward way to generate +this is the following: + +``` +openssl rand -base64 -out /etc/xrootd/macaroon-secret 64 +``` + +Usage +===== + +To generate a macaroon for personal use, you can run: + +``` +macaroon-init https://host.example.com//path/to/directory/ --validity 60 --activity DOWNLOAD,UPLOAD +``` + +(the `macaroon-init` CLI can be found as part of the `x509-scitokens-issuer-client` package). This +will generate a macaroon with 60 minutes of validity that has upload and download access to the path +specified at `/path/to/directory`, provided that your X509 identity has that access. + +The output will look like the following: + +``` +Querying https://host.example.com//path/to/directory/ for new token. +Validity: PT60M, activities: DOWNLOAD,UPLOAD,READ_METADATA. +Successfully generated a new token: +{ + "macaroon":"MDAxY2xvY2F0aW9uIFQyX1VTX05lYnJhc2thCjAwMzRpZGVudGlmaWVyIGMzODU3MjQ3LThjYzItNGI0YS04ZDUwLWNiZDYzN2U2MzJhMQowMDUyY2lkIGFjdGl2aXR5OlJFQURfTUVUQURBVEEsVVBMT0FELERPV05MT0FELERFTEVURSxNQU5BR0UsVVBEQVRFX01FVEFEQVRBLExJU1QKMDAyZmNpZCBhY3Rpdml0eTpET1dOTE9BRCxVUExPQUQsUkVBRF9NRVRBREFUQQowMDM2Y2lkIHBhdGg6L2hvbWUvY3NlNDk2L2Jib2NrZWxtL3RtcC94cm9vdGRfZXhwb3J0LwowMDI0Y2lkIGJlZm9yZToyMDE4LTA2LTE1VDE4OjE5OjI5WgowMDJmc2lnbmF0dXJlIFXI_x3v8Tq1jYcP-2WUvPV-BIewn5MHRODVu8UszyYkCg" +} +``` + +The contents of the `macaroon` key is your new security token. Anyone you share it with will be able to read and write from the same path. +You can use this token as a bearer token for HTTPS authorization. For example, it can authorize the following transfer: + +``` +curl -v + -H 'Authorization: Bearer MDAxY2xvY2F0aW9uIFQyX1VTX05lYnJhc2thCjAwMzRpZGVudGlmaWVyIGMzODU3MjQ3LThjYzItNGI0YS04ZDUwLWNiZDYzN2U2MzJhMQowMDUyY2lkIGFjdGl2aXR5OlJFQURfTUVUQURBVEEsVVBMT0FELERPV05MT0FELERFTEVURSxNQU5BR0UsVVBEQVRFX01FVEFEQVRBLExJU1QKMDAyZmNpZCBhY3Rpdml0eTpET1dOTE9BRCxVUExPQUQsUkVBRF9NRVRBREFUQQowMDM2Y2lkIHBhdGg6L2hvbWUvY3NlNDk2L2Jib2NrZWxtL3RtcC94cm9vdGRfZXhwb3J0LwowMDI0Y2lkIGJlZm9yZToyMDE4LTA2LTE1VDE4OjE5OjI5WgowMDJmc2lnbmF0dXJlIFXI_x3v8Tq1jYcP-2WUvPV-BIewn5MHRODVu8UszyYkCg' \ + https://host.example.com//path/to/directory/hello_world +``` diff --git a/src/XrdMacaroons/XrdMacaroons.cc b/src/XrdMacaroons/XrdMacaroons.cc new file mode 100644 index 00000000000..0f3f94e808d --- /dev/null +++ b/src/XrdMacaroons/XrdMacaroons.cc @@ -0,0 +1,114 @@ + +#include +#include + +#include "XrdMacaroonsHandler.hh" +#include "XrdMacaroonsAuthz.hh" + +#include "XrdOuc/XrdOucString.hh" +#include "XrdOuc/XrdOucPinPath.hh" +#include "XrdSys/XrdSysError.hh" +#include "XrdSys/XrdSysLogger.hh" +#include "XrdHttp/XrdHttpExtHandler.hh" +#include "XrdAcc/XrdAccAuthorize.hh" +#include "XrdVersion.hh" + +XrdVERSIONINFO(XrdAccAuthorizeObject, XrdMacaroons); +XrdVERSIONINFO(XrdHttpGetExtHandler, XrdMacaroons); + +// Trick to access compiled version and directly call for the default object +// is taken from xrootd-scitokens. +static XrdVERSIONINFODEF(compiledVer, XrdAccTest, XrdVNUMBER, XrdVERSION); +extern XrdAccAuthorize *XrdAccDefaultAuthorizeObject(XrdSysLogger *lp, + const char *cfn, + const char *parm, + XrdVersionInfo &myVer); + + +extern "C" { + +XrdAccAuthorize *XrdAccAuthorizeObject(XrdSysLogger *log, + const char *config, + const char *parms) +{ + XrdAccAuthorize *chain_authz; + + if (parms && parms[0]) { + XrdOucString parms_str(parms); + XrdOucString chained_lib; + XrdSysError *err = new XrdSysError(log, "authlib"); + int from = parms_str.tokenize(chained_lib, 0, ' '); + const char *chained_parms = NULL; + err->Emsg("Config", "Will chain library", chained_lib.c_str()); + if (from > 0) + { + parms_str.erasefromstart(from); + if (parms_str.length()) + { + err->Emsg("Config", "Will chain parameters", parms_str.c_str()); + chained_parms = parms_str.c_str(); + } + } + char resolvePath[2048]; + bool usedAltPath{true}; + if (!XrdOucPinPath(chained_lib.c_str(), usedAltPath, resolvePath, 2048)) { + err->Emsg("Config", "Failed to locate appropriately versioned chained auth library:", parms); + delete err; + return NULL; + } + void *handle_base = dlopen(resolvePath, RTLD_LOCAL|RTLD_NOW); + if (handle_base == NULL) { + err->Emsg("Config", "Failed to base plugin ", resolvePath, dlerror()); + delete err; + return NULL; + } + + XrdAccAuthorize *(*ep)(XrdSysLogger *, const char *, const char *); + ep = (XrdAccAuthorize *(*)(XrdSysLogger *, const char *, const char *)) + (dlsym(handle_base, "XrdAccAuthorizeObject")); + if (!ep) + { + err->Emsg("Config", "Unable to chain second authlib after macaroons", parms); + delete err; + return NULL; + } + chain_authz = (*ep)(log, config, chained_parms); + } + else + { + chain_authz = XrdAccDefaultAuthorizeObject(log, config, parms, compiledVer); + } + try + { + return new Macaroons::Authz(log, config, chain_authz); + } + catch (std::runtime_error e) + { + XrdSysError err(log, "macaroons"); + err.Emsg("Config", "Configuration of Macaroon authorization handler failed", e.what()); + return NULL; + } +} + + +XrdHttpExtHandler *XrdHttpGetExtHandler( + XrdSysError *log, const char * config, + const char * parms, XrdOucEnv *env) +{ + XrdAccAuthorize *def_authz = XrdAccDefaultAuthorizeObject(log->logger(), + config, parms, compiledVer); + + log->Emsg("Initialize", "Creating new Macaroon handler object"); + try + { + return new Macaroons::Handler(log, config, env, def_authz); + } + catch (std::runtime_error e) + { + log->Emsg("Config", "Generation of Macaroon handler failed", e.what()); + return NULL; + } +} + + +} diff --git a/src/XrdMacaroons/XrdMacaroonsAuthz.cc b/src/XrdMacaroons/XrdMacaroonsAuthz.cc new file mode 100644 index 00000000000..44625939417 --- /dev/null +++ b/src/XrdMacaroons/XrdMacaroonsAuthz.cc @@ -0,0 +1,396 @@ + +#include +#include + +#include + +#include "macaroons.h" + +#include "XrdOuc/XrdOucEnv.hh" +#include "XrdSec/XrdSecEntity.hh" + +#include "XrdMacaroonsHandler.hh" +#include "XrdMacaroonsAuthz.hh" + +using namespace Macaroons; + + +namespace { + +class AuthzCheck +{ +public: + 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;} + + static int verify_before_s(void *authz_ptr, + const unsigned char *pred, + size_t pred_sz); + + static int verify_activity_s(void *authz_ptr, + const unsigned char *pred, + size_t pred_sz); + + static int verify_path_s(void *authz_ptr, + const unsigned char *pred, + size_t pred_sz); + + static int verify_name_s(void *authz_ptr, + const unsigned char *pred, + size_t pred_sz); + +private: + int verify_before(const unsigned char *pred, size_t pred_sz); + int verify_activity(const unsigned char *pred, size_t pred_sz); + int verify_path(const unsigned char *pred, size_t pred_sz); + int verify_name(const unsigned char *pred, size_t pred_sz); + + ssize_t m_max_duration; + XrdSysError &m_log; + const std::string m_path; + std::string m_desired_activity; + std::string m_sec_name; + Access_Operation m_oper; + time_t m_now; +}; + + +static XrdAccPrivs AddPriv(Access_Operation op, XrdAccPrivs privs) +{ + int new_privs = privs; + switch (op) { + case AOP_Any: + break; + case AOP_Chmod: + new_privs |= static_cast(XrdAccPriv_Chmod); + break; + case AOP_Chown: + new_privs |= static_cast(XrdAccPriv_Chown); + break; + case AOP_Create: + new_privs |= static_cast(XrdAccPriv_Create); + break; + case AOP_Delete: + new_privs |= static_cast(XrdAccPriv_Delete); + break; + case AOP_Insert: + new_privs |= static_cast(XrdAccPriv_Insert); + break; + case AOP_Lock: + new_privs |= static_cast(XrdAccPriv_Lock); + break; + case AOP_Mkdir: + new_privs |= static_cast(XrdAccPriv_Mkdir); + break; + case AOP_Read: + new_privs |= static_cast(XrdAccPriv_Read); + break; + case AOP_Readdir: + new_privs |= static_cast(XrdAccPriv_Readdir); + break; + case AOP_Rename: + new_privs |= static_cast(XrdAccPriv_Rename); + break; + case AOP_Stat: + new_privs |= static_cast(XrdAccPriv_Lookup); + break; + case AOP_Update: + new_privs |= static_cast(XrdAccPriv_Update); + break; + }; + return static_cast(new_privs); +} + +} + + +Authz::Authz(XrdSysLogger *log, char const *config, XrdAccAuthorize *chain) + : m_max_duration(86400), + m_chain(chain), + m_log(log, "macarons_") +{ + if (!Handler::Config(config, nullptr, &m_log, m_location, m_secret, m_max_duration)) + { + throw std::runtime_error("Macaroon authorization config failed."); + } +} + + +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 (!authz || strncmp(authz, "Bearer%20", 9) || oper == AOP_Any) + { + //m_log.Emsg("Access", "No bearer token present"); + return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None; + } + authz += 9; + + struct macaroon_verifier *verifier = macaroon_verifier_create(); + if (!verifier) + { + m_log.Emsg("Access", "Failed to create a new macaroon verifier"); + return XrdAccPriv_None; + } + if (!path) + { + m_log.Emsg("Access", "Request with no provided path."); + return XrdAccPriv_None; + } + + AuthzCheck check_helper(path, oper, m_max_duration, m_log); + + macaroon_returncode mac_err = MACAROON_SUCCESS; + if (macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_before_s, &check_helper, &mac_err) || + macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_activity_s, &check_helper, &mac_err) || + macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_name_s, &check_helper, &mac_err) || + macaroon_verifier_satisfy_general(verifier, AuthzCheck::verify_path_s, &check_helper, &mac_err)) + { + m_log.Emsg("Access", "Failed to configure caveat verifier:"); + macaroon_verifier_destroy(verifier); + return XrdAccPriv_None; + } + + struct macaroon* macaroon = macaroon_deserialize( + authz, + &mac_err); + if (!macaroon) + { + m_log.Emsg("Access", "Failed to parse the macaroon"); + macaroon_verifier_destroy(verifier); + return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None; + } + + const unsigned char *macaroon_loc; + size_t location_sz; + macaroon_location(macaroon, &macaroon_loc, &location_sz); + if (strncmp(reinterpret_cast(macaroon_loc), m_location.c_str(), location_sz)) + { + m_log.Emsg("Access", "Macaroon is for incorrect location", reinterpret_cast(macaroon_loc)); + macaroon_verifier_destroy(verifier); + return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None; + } + + if (macaroon_verify(verifier, macaroon, + reinterpret_cast(m_secret.c_str()), + m_secret.size(), + NULL, 0, // discharge macaroons + &mac_err)) + { + m_log.Log(LogMask::Debug, "Access", "Macaroon verification failed"); + macaroon_verifier_destroy(verifier); + macaroon_destroy(macaroon); + return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None; + } + const unsigned char *macaroon_id; + size_t id_sz; + macaroon_identifier(macaroon, &macaroon_id, &id_sz); + std::string macaroon_id_str(reinterpret_cast(macaroon_id), id_sz); + m_log.Log(LogMask::Info, "Access", "Macaroon verification successful; ID", macaroon_id_str.c_str()); + + // Copy the name, if present into the macaroon, into the credential object. + if (Entity && check_helper.GetSecName().size()) { + m_log.Log(LogMask::Debug, "Access", "Setting the security name to", check_helper.GetSecName().c_str()); + XrdSecEntity &myEntity = *const_cast(Entity); + if (myEntity.name) {free(myEntity.name);} + myEntity.name = strdup(check_helper.GetSecName().c_str()); + } + + // We passed verification - give the correct privilege. + return AddPriv(oper, XrdAccPriv_None); +} + + +AuthzCheck::AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t max_duration, XrdSysError &log) + : m_max_duration(max_duration), + m_log(log), + m_path(req_path), + m_oper(req_oper), + m_now(time(NULL)) +{ + switch (m_oper) + { + case AOP_Any: + break; + case AOP_Chmod: + case AOP_Chown: + m_desired_activity = "UPDATE_METADATA"; + break; + case AOP_Insert: + case AOP_Lock: + case AOP_Mkdir: + case AOP_Rename: + case AOP_Update: + m_desired_activity = "MANAGE"; + break; + case AOP_Create: + m_desired_activity = "UPLOAD"; + break; + case AOP_Delete: + m_desired_activity = "DELETE"; + break; + case AOP_Read: + m_desired_activity = "DOWNLOAD"; + break; + case AOP_Readdir: + m_desired_activity = "LIST"; + break; + case AOP_Stat: + m_desired_activity = "READ_METADATA"; + }; +} + + +int +AuthzCheck::verify_before_s(void *authz_ptr, + const unsigned char *pred, + size_t pred_sz) +{ + return static_cast(authz_ptr)->verify_before(pred, pred_sz); +} + + +int +AuthzCheck::verify_activity_s(void *authz_ptr, + const unsigned char *pred, + size_t pred_sz) +{ + return static_cast(authz_ptr)->verify_activity(pred, pred_sz); +} + + +int +AuthzCheck::verify_path_s(void *authz_ptr, + const unsigned char *pred, + size_t pred_sz) +{ + return static_cast(authz_ptr)->verify_path(pred, pred_sz); +} + + +int +AuthzCheck::verify_name_s(void *authz_ptr, + const unsigned char *pred, + size_t pred_sz) +{ + return static_cast(authz_ptr)->verify_name(pred, pred_sz); +} + + +int +AuthzCheck::verify_before(const unsigned char * pred, size_t pred_sz) +{ + std::string pred_str(reinterpret_cast(pred), pred_sz); + if (strncmp("before:", pred_str.c_str(), 7)) + { + return 1; + } + m_log.Log(LogMask::Debug, "AuthzCheck", "running verify before", 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]); + 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]); + 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"); + 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"); + return result; +} + + +int +AuthzCheck::verify_activity(const unsigned char * pred, size_t pred_sz) +{ + if (!m_desired_activity.size()) {return 1;} + std::string pred_str(reinterpret_cast(pred), pred_sz); + if (strncmp("activity:", pred_str.c_str(), 9)) {return 1;} + m_log.Log(LogMask::Debug, "AuthzCheck", "running verify activity", pred_str.c_str()); + + std::stringstream ss(pred_str.substr(9)); + for (std::string activity; std::getline(ss, activity, ','); ) + { + // Any allowed activity also implies "READ_METADATA" + if (m_desired_activity == "READ_METADATA") {return 0;} + if (activity == m_desired_activity) + { + m_log.Log(LogMask::Debug, "AuthzCheck", "macaroon has desired activity", activity.c_str()); + return 0; + } + } + m_log.Log(LogMask::Info, "AuthzCheck", "macaroon does NOT have desired activity", m_desired_activity.c_str()); + return 1; +} + + +int +AuthzCheck::verify_path(const unsigned char * pred, size_t pred_sz) +{ + std::string pred_str(reinterpret_cast(pred), pred_sz); + if (strncmp("path:", pred_str.c_str(), 5)) {return 1;} + m_log.Log(LogMask::Debug, "AuthzCheck", "running verify path", pred_str.c_str()); + + if ((m_path.find("/./") != std::string::npos) || + (m_path.find("/../") != std::string::npos)) + { + m_log.Log(LogMask::Info, "AuthzCheck", "invalid requested path", m_path.c_str()); + return 1; + } + size_t compare_chars = pred_str.size() - 5; + if (pred_str[compare_chars + 5 - 1] == '/') {compare_chars--;} + + int result = strncmp(pred_str.c_str() + 5, m_path.c_str(), compare_chars); + if (!result) + { + m_log.Log(LogMask::Debug, "AuthzCheck", "path request verified for", m_path.c_str()); + } + // READ_METADATA permission for /foo/bar automatically implies permission + // to READ_METADATA for /foo. + else if (m_oper == AOP_Stat) + { + result = strncmp(m_path.c_str(), pred_str.c_str() + 5, m_path.size()); + if (!result) {m_log.Log(LogMask::Debug, "AuthzCheck", "READ_METADATA path request verified for", m_path.c_str());} + else {m_log.Log(LogMask::Debug, "AuthzCheck", "READ_METADATA path request NOT allowed", m_path.c_str());} + } + else + { + m_log.Log(LogMask::Debug, "AuthzCheck", "path request NOT allowed", m_path.c_str()); + } + + return result; +} + + +int +AuthzCheck::verify_name(const unsigned char * pred, size_t pred_sz) +{ + std::string pred_str(reinterpret_cast(pred), pred_sz); + if (strncmp("name:", pred_str.c_str(), 5)) {return 1;} + if (pred_str.size() < 6) {return 1;} + m_log.Log(LogMask::Debug, "AuthzCheck", "Verifying macaroon with", pred_str.c_str()); + + // Make a copy of the name for the XrdSecEntity; this will be used later. + m_sec_name = pred_str.substr(5); + + return 0; +} diff --git a/src/XrdMacaroons/XrdMacaroonsAuthz.hh b/src/XrdMacaroons/XrdMacaroonsAuthz.hh new file mode 100644 index 00000000000..7081f8d8912 --- /dev/null +++ b/src/XrdMacaroons/XrdMacaroonsAuthz.hh @@ -0,0 +1,44 @@ + +#include "XrdAcc/XrdAccAuthorize.hh" +#include "XrdSys/XrdSysError.hh" + + +class XrdSysError; + +namespace Macaroons +{ + +class Authz : public XrdAccAuthorize +{ +public: + Authz(XrdSysLogger *lp, const char *parms, XrdAccAuthorize *chain); + + virtual ~Authz() {} + + virtual XrdAccPrivs Access(const XrdSecEntity *Entity, + const char *path, + const Access_Operation oper, + XrdOucEnv *env); + + virtual int Audit(const int accok, const XrdSecEntity *Entity, + const char *path, const Access_Operation oper, + XrdOucEnv *Env) + { + return 0; + } + + virtual int Test(const XrdAccPrivs priv, + const Access_Operation oper) + { + return 0; + } + +private: + ssize_t m_max_duration; + XrdAccAuthorize *m_chain; + XrdSysError m_log; + std::string m_secret; + std::string m_location; +}; + +} diff --git a/src/XrdMacaroons/XrdMacaroonsConfigure.cc b/src/XrdMacaroons/XrdMacaroonsConfigure.cc new file mode 100644 index 00000000000..fccf8d25f9c --- /dev/null +++ b/src/XrdMacaroons/XrdMacaroonsConfigure.cc @@ -0,0 +1,236 @@ + +#include + +#include +#include + +#include + +#include "XrdMacaroonsHandler.hh" + + +using namespace Macaroons; + +bool Handler::Config(const char *config, XrdOucEnv *env, XrdSysError *log, + std::string &location, std::string &secret, ssize_t &max_duration) +{ + XrdOucStream config_obj(log, getenv("XRDINSTANCE"), env, "=====> "); + + // Open and attach the config file + // + int cfg_fd; + if ((cfg_fd = open(config, O_RDONLY, 0)) < 0) { + return log->Emsg("Config", errno, "open config file", config); + } + config_obj.Attach(cfg_fd); + + // Set default mask for logging. + log->setMsgMask(LogMask::Error | LogMask::Warning); + + // Set default maximum duration (24 hours). + max_duration = 24*3600; + + // Process items + // + char *orig_var, *var; + bool success = true, ismine; + while ((orig_var = config_obj.GetMyFirstWord())) { + var = orig_var; + if ((ismine = !strncmp("all.sitename", var, 12))) var += 4; + else if ((ismine = !strncmp("macaroons.", var, 10)) && var[10]) var += 10; + + + + if (!ismine) {continue;} + + if (!strcmp("secretkey", var)) {success = xsecretkey(config_obj, log, secret);} + else if (!strcmp("sitename", var)) {success = xsitename(config_obj, log, location);} + else if (!strcmp("trace", var)) {success = xtrace(config_obj, log);} + else if (!strcmp("maxduration", var)) {success = xmaxduration(config_obj, log, max_duration);} + else { + log->Say("Config warning: ignoring unknown directive '", orig_var, "'."); + config_obj.Echo(); + continue; + } + if (!success) { + config_obj.Echo(); + break; + } + } + + if (success && !location.size()) + { + log->Emsg("Config", "all.sitename must be specified to use macaroons."); + return false; + } + + return success; +} + + +bool Handler::xtrace(XrdOucStream &config_obj, XrdSysError *log) +{ + char *val = config_obj.GetWord(); + if (!val || !val[0]) + { + log->Emsg("Config", "macaroons.trace requires at least one directive [all | error | warning | info | debug | none]"); + return false; + } + // If the config option is given, reset the log mask. + log->setMsgMask(0); + + do { + if (!strcmp(val, "all")) + { + log->setMsgMask(log->getMsgMask() | LogMask::All); + } + else if (!strcmp(val, "error")) + { + log->setMsgMask(log->getMsgMask() | LogMask::Error); + } + else if (!strcmp(val, "warning")) + { + log->setMsgMask(log->getMsgMask() | LogMask::Warning); + } + else if (!strcmp(val, "info")) + { + log->setMsgMask(log->getMsgMask() | LogMask::Info); + } + else if (!strcmp(val, "debug")) + { + log->setMsgMask(log->getMsgMask() | LogMask::Debug); + } + else if (!strcmp(val, "none")) + { + log->setMsgMask(0); + } + else + { + log->Emsg("Config", "macaroons.trace encountered an unknown directive:", val); + return false; + } + val = config_obj.GetWord(); + } while (val); + + return true; +} + + +bool Handler::xmaxduration(XrdOucStream &config_obj, XrdSysError *log, ssize_t &max_duration) +{ + char *val = config_obj.GetWord(); + if (!val || !val[0]) + { + log->Emsg("Config", "macaroons.maxduration requires a value"); + return false; + } + char *endptr = NULL; + long int max_duration_parsed = strtoll(val, &endptr, 10); + if (endptr == val) + { + log->Emsg("Config", "Unable to parse macaroons.maxduration as an integer", val); + return false; + } + if (errno != 0) + { + log->Emsg("Config", "Failure when parsing macaroons.maxduration as an integer", strerror(errno)); + } + max_duration = max_duration_parsed; + + return true; +} + +bool Handler::xsitename(XrdOucStream &config_obj, XrdSysError *log, std::string &location) +{ + char *val = config_obj.GetWord(); + if (!val || !val[0]) + { + log->Emsg("Config", "all.sitename requires a name"); + return false; + } + + location = val; + return true; +} + +bool Handler::xsecretkey(XrdOucStream &config_obj, XrdSysError *log, std::string &secret) +{ + char *val = config_obj.GetWord(); + if (!val || !val[0]) + { + log->Emsg("Config", "Shared secret key not specified"); + return false; + } + + FILE *fp = fopen(val, "rb"); + + if (fp == NULL) { + log->Emsg("Config", "Cannot open shared secret key file '", val, "'"); + log->Emsg("Config", "Cannot open shared secret key file. err: ", strerror(errno)); + return false; + } + + BIO *bio, *b64, *bio_out; + char inbuf[512]; + int inlen; + + b64 = BIO_new(BIO_f_base64()); + if (!b64) + { + log->Emsg("Config", "Failed to allocate base64 filter"); + return false; + } + bio = BIO_new_fp(fp, 0); // fp will be closed when BIO is freed. + if (!bio) + { + BIO_free_all(b64); + log->Emsg("Config", "Failed to allocate BIO filter"); + return false; + } + bio_out = BIO_new(BIO_s_mem()); + if (!bio_out) + { + BIO_free_all(b64); + BIO_free_all(bio); + log->Emsg("Config", "Failed to allocate BIO output"); + return false; + } + + BIO_push(b64, bio); + while ((inlen = BIO_read(b64, inbuf, 512)) > 0) + { + if (inlen < 0) { + if (errno == EINTR) continue; + break; + } else { + BIO_write(bio_out, inbuf, inlen); + } + } + if (inlen < 0) { + BIO_free_all(b64); + BIO_free_all(bio_out); + log->Emsg("Config", "Failure when reading secret key", strerror(errno)); + return false; + } + if (!BIO_flush(bio_out)) { + BIO_free_all(b64); + BIO_free_all(bio_out); + log->Emsg("Config", "Failure when flushing secret key", strerror(errno)); + return false; + } + + char *decoded; + long data_len = BIO_get_mem_data(bio_out, &decoded); + BIO_free_all(b64); + + secret = std::string(decoded, data_len); + + BIO_free_all(bio_out); + + if (secret.size() < 32) { + log->Emsg("Config", "Secret key is too short; must be 32 bytes long. Try running 'openssl rand -base64 -out", val, "64' to generate a new key"); + return false; + } + + return true; +} diff --git a/src/XrdMacaroons/XrdMacaroonsHandler.cc b/src/XrdMacaroons/XrdMacaroonsHandler.cc new file mode 100644 index 00000000000..095693a6717 --- /dev/null +++ b/src/XrdMacaroons/XrdMacaroonsHandler.cc @@ -0,0 +1,324 @@ + +#include +#include +#include +#include + +#include "uuid.h" +#include "json.h" +#include "macaroons.h" + +#include "XrdAcc/XrdAccPrivs.hh" +#include "XrdAcc/XrdAccAuthorize.hh" +#include "XrdSys/XrdSysError.hh" +#include "XrdSec/XrdSecEntity.hh" + +#include "XrdMacaroonsHandler.hh" + +using namespace Macaroons; + +static +ssize_t determine_validity(const std::string& input) +{ + ssize_t duration = 0; + if (input.find("PT") != 0) + { + return -1; + } + size_t pos = 2; + std::string remaining = input; + do + { + remaining = remaining.substr(pos); + if (remaining.size() == 0) break; + long cur_duration; + try + { + cur_duration = stol(remaining, &pos); + } catch (...) + { + return -1; + } + if (pos >= remaining.size()) + { + return -1; + } + char unit = remaining[pos]; + switch (unit) { + case 'S': + break; + case 'M': + cur_duration *= 60; + break; + case 'H': + cur_duration *= 3600; + break; + default: + return -1; + }; + pos ++; + duration += cur_duration; + } while (1); + return duration; +} + + +Handler::~Handler() +{ + delete m_chain; +} + + +std::string +Handler::GenerateID(const XrdSecEntity &entity, const std::string &activities, + const std::string &before) +{ + uuid_t uu; + uuid_generate_random(uu); + char uuid_buf[37]; + uuid_unparse(uu, uuid_buf); + std::string result(uuid_buf); + + std::stringstream ss; + ss << "ID=" << result << ", "; + if (entity.prot[0] != '\0') {ss << "protocol=" << entity.prot << ", ";} + if (entity.name) {ss << "name=" << entity.name << ", ";} + if (entity.host) {ss << "host=" << entity.host << ", ";} + if (entity.vorg) {ss << "vorg=" << entity.vorg << ", ";} + if (entity.role) {ss << "role=" << entity.role << ", ";} + if (entity.grps) {ss << "groups=" << entity.grps << ", ";} + if (entity.endorsements) {ss << "endorsements=" << entity.endorsements << ", ";} + if (activities.size()) {ss << "activity=" << activities << ", ";} + ss << "expires=" << before; + + m_log->Emsg("MacaroonGen", ss.str().c_str()); + return result; +} + +std::string +Handler::GenerateActivities(const XrdHttpExtReq & req) const +{ + std::string result = "activity:READ_METADATA"; + // TODO - generate environment object that includes the Authorization header. + XrdAccPrivs privs = m_chain ? m_chain->Access(&req.GetSecEntity(), req.resource.c_str(), AOP_Any, NULL) : XrdAccPriv_None; + if ((privs & XrdAccPriv_Create) == XrdAccPriv_Create) {result += ",UPLOAD";} + if (privs & XrdAccPriv_Read) {result += ",DOWNLOAD";} + if (privs & XrdAccPriv_Delete) {result += ",DELETE";} + if ((privs & XrdAccPriv_Chown) == XrdAccPriv_Chown) {result += ",MANAGE,UPDATE_METADATA";} + if (privs & XrdAccPriv_Readdir) {result += ",LIST";} + return result; +} + +// See if the macaroon handler is interested in this request. +// We intercept all POST requests as we will be looking for a particular +// header. +bool +Handler::MatchesPath(const char *verb, const char *path) +{ + return !strcmp(verb, "POST"); +} + + +// Process a macaroon request. +int Handler::ProcessReq(XrdHttpExtReq &req) +{ + auto header = req.headers.find("Content-Type"); + if (header == req.headers.end()) + { + return req.SendSimpleResp(400, NULL, NULL, "Content-Type missing; not a valid macaroon request?", 0); + } + if (header->second != "application/macaroon-request") + { + return req.SendSimpleResp(400, NULL, NULL, "Content-Type must be set to `application/macaroon-request' to request a macaroon", 0); + } + header = req.headers.find("Content-Length"); + if (header == req.headers.end()) + { + return req.SendSimpleResp(400, NULL, NULL, "Content-Length missing; not a valid POST", 0); + } + ssize_t blen; + try + { + blen = std::stoll(header->second); + } + catch (...) + { + return req.SendSimpleResp(400, NULL, NULL, "Content-Length not parseable.", 0); + } + if (blen <= 0) + { + return req.SendSimpleResp(400, NULL, NULL, "Content-Length has invalid value.", 0); + } + //for (const auto &header : req.headers) { printf("** Request header: %s=%s\n", header.first.c_str(), header.second.c_str()); } + char *request_data; + if (req.BuffgetData(blen, &request_data, true) != blen) + { + return req.SendSimpleResp(400, NULL, NULL, "Missing or invalid body of request.", 0); + } + json_object *macaroon_req = json_tokener_parse(request_data); + if (!macaroon_req) + { + return req.SendSimpleResp(400, NULL, NULL, "Invalid JSON serialization of macaroon request.", 0); + } + json_object *validity_obj; + if (!json_object_object_get_ex(macaroon_req, "validity", &validity_obj)) + { + return req.SendSimpleResp(400, NULL, NULL, "JSON request does not include a `validity`", 0); + } + const char *validity_cstr = json_object_get_string(validity_obj); + if (!validity_cstr) + { + return req.SendSimpleResp(400, NULL, NULL, "validity key cannot be cast to a string", 0); + } + std::string validity_str(validity_cstr); + ssize_t validity = determine_validity(validity_str); + if (validity <= 0) + { + return req.SendSimpleResp(400, NULL, NULL, "Invalid ISO 8601 duration for validity key", 0); + } + time_t now; + time(&now); + if (m_max_duration > 0) + { + now += (validity > m_max_duration) ? m_max_duration : validity; + } + else + { + now += validity; + } + char utc_time_buf[21]; + if (!strftime(utc_time_buf, 21, "%FT%TZ", gmtime(&now))) + { + return req.SendSimpleResp(500, NULL, NULL, "Internal error constructing UTC time", 0); + } + std::string utc_time_str(utc_time_buf); + std::stringstream ss; + ss << "before:" << utc_time_str; + std::string utc_time_caveat = ss.str(); + + json_object *caveats_obj; + std::vector other_caveats; + if (json_object_object_get_ex(macaroon_req, "caveats", &caveats_obj)) + { + if (json_object_is_type(caveats_obj, json_type_array)) + { // Caveats were provided. Let's record them. + // TODO - could just add these in-situ. No need for the other_caveats vector. + int array_length = json_object_array_length(caveats_obj); + other_caveats.reserve(array_length); + for (int idx=0; idx(m_location.c_str()), + m_location.size(), + reinterpret_cast(m_secret.c_str()), + m_secret.size(), + reinterpret_cast(macaroon_id.c_str()), + macaroon_id.size(), &mac_err); + if (!mac) { + return req.SendSimpleResp(500, NULL, NULL, "Internal error constructing the macaroon", 0); + } + + // Embed the SecEntity name, if present. + struct macaroon *mac_with_name; + const char * sec_name = req.GetSecEntity().name; + if (sec_name) { + std::stringstream name_caveat_ss; + name_caveat_ss << "name:" << sec_name; + std::string name_caveat = name_caveat_ss.str(); + mac_with_name = macaroon_add_first_party_caveat(mac, + reinterpret_cast(name_caveat.c_str()), + name_caveat.size(), + &mac_err); + macaroon_destroy(mac); + } else { + mac_with_name = mac; + } + if (!mac_with_name) + { + return req.SendSimpleResp(500, NULL, NULL, "Internal error adding default activities to macaroon", 0); + } + + struct macaroon *mac_with_activities = macaroon_add_first_party_caveat(mac_with_name, + reinterpret_cast(activities.c_str()), + activities.size(), + &mac_err); + macaroon_destroy(mac_with_name); + if (!mac_with_activities) + { + return req.SendSimpleResp(500, NULL, NULL, "Internal error adding default activities to macaroon", 0); + } + + + for (const auto &caveat : other_caveats) + { + struct macaroon *mac_tmp = mac_with_activities; + mac_with_activities = macaroon_add_first_party_caveat(mac_tmp, + reinterpret_cast(caveat.c_str()), + caveat.size(), + &mac_err); + macaroon_destroy(mac_tmp); + if (!mac_with_activities) + { + return req.SendSimpleResp(500, NULL, NULL, "Internal error adding user caveat to macaroon", 0); + } + } + + std::string path_caveat = "path:" + req.resource; + struct macaroon *mac_with_path = macaroon_add_first_party_caveat(mac_with_activities, + reinterpret_cast(path_caveat.c_str()), + path_caveat.size(), + &mac_err); + macaroon_destroy(mac_with_activities); + if (!mac_with_path) { + return req.SendSimpleResp(500, NULL, NULL, "Internal error adding path to macaroon", 0); + } + + struct macaroon *mac_with_date = macaroon_add_first_party_caveat(mac_with_path, + reinterpret_cast(utc_time_caveat.c_str()), + utc_time_caveat.size(), + &mac_err); + macaroon_destroy(mac_with_path); + if (!mac_with_date) { + return req.SendSimpleResp(500, NULL, NULL, "Internal error adding date to macaroon", 0); + } + + size_t size_hint = macaroon_serialize_size_hint(mac_with_date); + + std::vector macaroon_resp; macaroon_resp.reserve(size_hint); + if (macaroon_serialize(mac_with_date, &macaroon_resp[0], size_hint, &mac_err)) + { + printf("Returned macaroon_serialize code: %lu\n", size_hint); + return req.SendSimpleResp(500, NULL, NULL, "Internal error serializing macaroon", 0); + } + + json_object *response_obj = json_object_new_object(); + if (!response_obj) + { + return req.SendSimpleResp(500, NULL, NULL, "Unable to create new JSON response object.", 0); + } + json_object *macaroon_obj = json_object_new_string_len(&macaroon_resp[0], strlen(&macaroon_resp[0])); + if (!macaroon_obj) + { + return req.SendSimpleResp(500, NULL, NULL, "Unable to create a new JSON macaroon string.", 0); + } + json_object_object_add(response_obj, "macaroon", macaroon_obj); + + const char *macaroon_result = json_object_to_json_string_ext(response_obj, JSON_C_TO_STRING_PRETTY); + int retval = req.SendSimpleResp(200, NULL, NULL, macaroon_result, 0); + json_object_put(response_obj); + return retval; +} + diff --git a/src/XrdMacaroons/XrdMacaroonsHandler.hh b/src/XrdMacaroons/XrdMacaroonsHandler.hh new file mode 100644 index 00000000000..3cfb22f230c --- /dev/null +++ b/src/XrdMacaroons/XrdMacaroonsHandler.hh @@ -0,0 +1,65 @@ + +#include +#include +#include + +#include "XrdHttp/XrdHttpExtHandler.hh" + +class XrdOucEnv; +class XrdOucStream; +class XrdSecEntity; +class XrdAccAuthorize; + +namespace Macaroons { + +enum LogMask { + Debug = 0x01, + Info = 0x02, + Warning = 0x04, + Error = 0x08, + All = 0xff +}; + +class Handler : public XrdHttpExtHandler { +public: + Handler(XrdSysError *log, const char *config, XrdOucEnv *myEnv, + XrdAccAuthorize *chain) : + m_max_duration(86400), + m_chain(chain), + m_log(log) + { + if (!Config(config, myEnv, m_log, m_location, m_secret, m_max_duration)) + { + throw std::runtime_error("Macaroon handler config failed."); + } + } + + virtual ~Handler(); + + virtual bool MatchesPath(const char *verb, const char *path) override; + virtual int ProcessReq(XrdHttpExtReq &req) override; + + virtual int Init(const char *cfgfile) override {return 0;} + + // Static configuration method; made static to allow Authz object to reuse + // this code. + static bool Config(const char *config, XrdOucEnv *env, XrdSysError *log, + std::string &location, std::string &secret, ssize_t &max_duration); + +private: + std::string GenerateID(const XrdSecEntity &, const std::string &, const std::string &); + std::string GenerateActivities(const XrdHttpExtReq &) const; + + static bool xsecretkey(XrdOucStream &Config, XrdSysError *log, std::string &secret); + static bool xsitename(XrdOucStream &Config, XrdSysError *log, std::string &location); + static bool xtrace(XrdOucStream &Config, XrdSysError *log); + static bool xmaxduration(XrdOucStream &Config, XrdSysError *log, ssize_t &max_duration); + + ssize_t m_max_duration; + XrdAccAuthorize *m_chain; + XrdSysError *m_log; + std::string m_location; + std::string m_secret; +}; + +} diff --git a/src/XrdMacaroons/export-lib-symbols b/src/XrdMacaroons/export-lib-symbols new file mode 100644 index 00000000000..76a3ed9bb5d --- /dev/null +++ b/src/XrdMacaroons/export-lib-symbols @@ -0,0 +1,8 @@ +{ +global: + XrdAccAuthorizeObject*; + XrdHttpGetExtHandler*; + +local: + *; +};