//------------------------------------------------------------------------------ // File: BoundIdentityProvider.cc // Author: Georgios Bitzes - CERN //------------------------------------------------------------------------------ /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2011 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "Utils.hh" #include "BoundIdentityProvider.hh" #include "EnvironmentReader.hh" #include "CredentialValidator.hh" #include "Logbook.hh" #include extern "C" { #include "krb5.h" } //------------------------------------------------------------------------------ // Constructor //------------------------------------------------------------------------------ BoundIdentityProvider::BoundIdentityProvider(SecurityChecker& checker, EnvironmentReader& reader, CredentialValidator& valid) : environmentReader(reader), validator(valid) { } //------------------------------------------------------------------------------ // Attempt to produce a BoundIdentity object out of KRB5 environment // variables. NO fallback to default paths. If not possible, return nullptr. //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::krb5EnvToBoundIdentity(const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope) { std::string path = env.get("KRB5CCNAME"); std::string key = env.get("EOS_FUSE_SECRET"); if (key.empty() && credConfig.encryptionKey.length()) { key = credConfig.encryptionKey; } //---------------------------------------------------------------------------- // Kerberos keyring? //---------------------------------------------------------------------------- if (startsWith(path, "KEYRING")) { LOGBOOK_INSERT(scope, "Found kerberos keyring: " << path << ", need to validate"); return userCredsToBoundIdentity(jail, UserCredentials::MakeKrk5(path, uid, gid, key), reconnect, scope); } //---------------------------------------------------------------------------- // Kerberos KCM? //---------------------------------------------------------------------------- if (startsWith(path, "KCM")) { LOGBOOK_INSERT(scope, "Found kerberos kcm: " << path << ", need to validate"); return userCredsToBoundIdentity(jail, UserCredentials::MakeKcm(path, uid, gid, key), reconnect, scope); } //---------------------------------------------------------------------------- // Drop FILE:, if exists //---------------------------------------------------------------------------- const std::string prefix = "FILE:"; if (startsWith(path, prefix)) { path = path.substr(prefix.size()); } if (path.empty()) { //-------------------------------------------------------------------------- // Early exit, no need to go through the trouble // of userCredsToBoundIdentity. //-------------------------------------------------------------------------- LOGBOOK_INSERT(scope, "Invalid KRB5CCNAME (size: " << path.size() << ")"); return {}; } LOGBOOK_INSERT(scope, "Found KRB5CCNAME: " << path << ", need to validate"); return userCredsToBoundIdentity(jail, UserCredentials::MakeKrb5(jail.id, path, uid, gid, key), reconnect, scope); } //------------------------------------------------------------------------------ // Attempt to produce a BoundIdentity object out of OAUTH2 environment // variables. NO fallback to default paths. If not possible, return nullptr. //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::oauth2EnvToBoundIdentity(const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope) { std::string path = env.get("OAUTH2_TOKEN"); std::string key = env.get("EOS_FUSE_SECRET"); if (key.empty() && credConfig.encryptionKey.length()) { key = credConfig.encryptionKey; } //---------------------------------------------------------------------------- // Drop FILE:, if exists //---------------------------------------------------------------------------- const std::string prefix = "FILE:"; if (startsWith(path, prefix)) { path = path.substr(prefix.size()); } if (path.empty()) { //-------------------------------------------------------------------------- // Early exit, no need to go through the trouble // of userCredsToBoundIdentity. //-------------------------------------------------------------------------- LOGBOOK_INSERT(scope, "Invalid OAUTH2_TOKEN (size: " << path.size() << ")"); return {}; } LOGBOOK_INSERT(scope, "Found OAUTH2_TOKEN: " << path << ", need to validate"); return userCredsToBoundIdentity(jail, UserCredentials::MakeOAUTH2(jail.id, path, uid, gid, key), reconnect, scope); } //------------------------------------------------------------------------------ // Attempt to produce a BoundIdentity object out of X509 environment // variables. NO fallback to default paths. If not possible, return nullptr. //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::x509EnvToBoundIdentity(const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope) { std::string path = env.get("X509_USER_PROXY"); std::string key = env.get("EOS_FUSE_SECRET"); if (key.empty() && credConfig.encryptionKey.length()) { key = credConfig.encryptionKey; } if (path.empty()) { //-------------------------------------------------------------------------- // Early exit, no need to go through the trouble // of userCredsToBoundIdentity. //-------------------------------------------------------------------------- LOGBOOK_INSERT(scope, "Invalid X509_USER_PROXY (size: " << path.size() << ")"); return {}; } LOGBOOK_INSERT(scope, "Found X509_USER_PROXY: " << path << ", need to validate"); return userCredsToBoundIdentity(jail, UserCredentials::MakeX509(jail.id, path, uid, gid, key), reconnect, scope); } //------------------------------------------------------------------------------ // Attempt to produce a BoundIdentity object out of ZTN environment // variables. NO fallback to default paths. If not possible, return nullptr. //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::ztnEnvToBoundIdentity(const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope) { std::string key = env.get("EOS_FUSE_SECRET"); std::string btf = env.get("BEARER_TOKEN_FILE"); std::string btfd = env.get("XDG_RUNTIME_DIR"); std::string path; if (btf.length()) { path = btf; } else if (btfd.length()) { path = btfd + std::string("/bt_u") + std::to_string(uid); } if (path.empty()) { //-------------------------------------------------------------------------- // Early exit, no need to go through the trouble // of userCredsToBoundIdentity. //-------------------------------------------------------------------------- LOGBOOK_INSERT(scope, "No bearer token file specified (size: " << path.size() << ")"); return {}; } LOGBOOK_INSERT(scope, "Found token: " << path << ", need to validate"); return userCredsToBoundIdentity(jail, UserCredentials::MakeZTN(jail.id, path, uid, gid, key), reconnect, scope); } //------------------------------------------------------------------------------ // Attempt to produce a BoundIdentity object out of SSS environment // variables. If not possible, return nullptr. //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::sssEnvToBoundIdentity(const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope) { std::string endorsement = env.get("XrdSecsssENDORSEMENT"); std::string key = env.get("EOS_FUSE_SECRET"); if (key.empty() && credConfig.encryptionKey.length()) { key = credConfig.encryptionKey; } LOGBOOK_INSERT(scope, "Found SSS endorsement of size " << endorsement.size()); return userCredsToBoundIdentity(jail, UserCredentials::MakeSSS(endorsement, uid, gid, key), reconnect, scope); } //------------------------------------------------------------------------------ // Attempt to produce a BoundIdentity object out of given environment // variables. If not possible, return nullptr. //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::environmentToBoundIdentity(const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope, bool skip_sss) { std::shared_ptr output; //---------------------------------------------------------------------------- // No SSS.. should we try KRB5 first, or second? //---------------------------------------------------------------------------- if (credConfig.tryKrb5First) { if (credConfig.use_user_krb5cc) { output = krb5EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope); if (output) { return output; } } //-------------------------------------------------------------------------- // No krb5.. what about x509.. //-------------------------------------------------------------------------- if (credConfig.use_user_gsiproxy) { output = x509EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope); if (output) { return output; } } //-------------------------------------------------------------------------- // No x509.. what about ztn.. //-------------------------------------------------------------------------- if (credConfig.use_user_ztn) { output = ztnEnvToBoundIdentity(jail, env, uid, gid, reconnect, scope); if (output) { return output; } } //---------------------------------------------------------------------------- // Try to use OAUTH2 if available. //---------------------------------------------------------------------------- if (credConfig.use_user_oauth2) { output = oauth2EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope); if (output) { return output; } } //---------------------------------------------------------------------------- // Try to use SSS if available. //---------------------------------------------------------------------------- if (credConfig.use_user_sss && ((!skip_sss) || (!env.get("XrdSecsssENDORSEMENT").empty()))) { output = sssEnvToBoundIdentity(jail, env, uid, gid, reconnect, scope); if (output) { return output; } } //-------------------------------------------------------------------------- // Nothing, bail out //-------------------------------------------------------------------------- return {}; } //---------------------------------------------------------------------------- // We should try krb5 second. //---------------------------------------------------------------------------- if (credConfig.use_user_gsiproxy) { output = x509EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope); if (output) { return output; } } //-------------------------------------------------------------------------- // No x509.. what about krb5.. //-------------------------------------------------------------------------- if (credConfig.use_user_krb5cc) { output = krb5EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope); if (output) { return output; } } //---------------------------------------------------------------------------- // Try to us OAUTH2 if available. //---------------------------------------------------------------------------- if (credConfig.use_user_oauth2) { output = oauth2EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope); if (output) { return output; } } //---------------------------------------------------------------------------- // Try to use SSS if available. //---------------------------------------------------------------------------- if (credConfig.use_user_sss && (!skip_sss || !env.get("XrdSecsssENDORSEMENT").empty())) { output = sssEnvToBoundIdentity(jail, env, uid, gid, reconnect, scope); if (output) { return output; } } //-------------------------------------------------------------------------- // Nothing, bail out //-------------------------------------------------------------------------- return {}; } //------------------------------------------------------------------------------ // Register SSS credentials //------------------------------------------------------------------------------ void BoundIdentityProvider::registerSSS(const BoundIdentity& bdi) { const UserCredentials uc = bdi.getCreds()->getUC(); if ((uc.type == CredentialType::SSS) || (uc.type == CredentialType::OAUTH2)) { // by default we request the uid/gid name of the calling process // the xrootd server rejects to map these if the sss key is not issued for anyuser/anygroup XrdSecEntity* newEntity = new XrdSecEntity("sss"); int errc_uid = 0; std::string username = eos::common::Mapping::UidToUserName(uc.uid, errc_uid); int errc_gid = 0; std::string groupname = eos::common::Mapping::GidToGroupName(uc.gid, errc_gid); if (errc_uid) { newEntity->name = strdup("nobody"); } else { newEntity->name = strdup(username.c_str()); } if (errc_gid) { newEntity->grps = strdup("nogroup"); } else { newEntity->grps = strdup(groupname.c_str()); } // store the endorsement from the environment if (!uc.endorsement.empty()) { newEntity->endorsements = strdup(uc.endorsement.c_str()); } // register new ID XrdSecsssIDInstance().Register(bdi.getLogin().getStringID().c_str(), newEntity); } } //------------------------------------------------------------------------------ // Given a set of user-provided, non-trusted UserCredentials, attempt to // translate them into a BoundIdentity object. (either by allocating a new // connection, or re-using a cached one) // // If such a thing is not possible, return false. //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::userCredsToBoundIdentity(const JailInformation& jail, const UserCredentials& creds, bool reconnect, LogbookScope& scope) { //---------------------------------------------------------------------------- // Make a proper LogbookScope, and pretty-print UserCredentials //---------------------------------------------------------------------------- LogbookScope subscope( scope.makeScope("Attempt to translate UserCredentials -> BoundIdentity")); //---------------------------------------------------------------------------- // First check: Is the item in the cache? //---------------------------------------------------------------------------- std::shared_ptr cached = credentialCache.retrieve(creds); //---------------------------------------------------------------------------- // Invalidate result if asked to reconnect //---------------------------------------------------------------------------- if (cached && reconnect) { LOGBOOK_INSERT(subscope, "Cache entry UserCredentials -> BoundIdentity already exists (" << cached->getLogin().describe() << ") - invalidating"); credentialCache.invalidate(creds); cached->getCreds()->invalidate(); cached = {}; } if (cached) { //-------------------------------------------------------------------------- // Item is in the cache, and reconnection was not requested. Still valid? //-------------------------------------------------------------------------- if (validator.checkValidity(jail, *cached->getCreds())) { return cached; } } //---------------------------------------------------------------------------- // Alright, we have a cache miss. Can we promote UserCredentials into // TrustedCredentials? //---------------------------------------------------------------------------- std::unique_ptr bdi(new BoundIdentity()); if (!validator.validate(jail, creds, *bdi->getCreds(), subscope)) { //-------------------------------------------------------------------------- // Nope, these UserCredentials are unusable. //-------------------------------------------------------------------------- return {}; } //---------------------------------------------------------------------------- // We made it, the crowd goes wild, allocate a new connection //---------------------------------------------------------------------------- bdi->getLogin() = LoginIdentifier(connectionCounter++); LOGBOOK_INSERT(subscope, "UserCredentials registerSSS (" << bdi->getLogin().getStringID() << ")"); LOGBOOK_INSERT(subscope, "Endorsement (" << bdi->getCreds()->getUC().endorsement.c_str() << ")"); registerSSS(*bdi); //---------------------------------------------------------------------------- // Store into the cache //---------------------------------------------------------------------------- credentialCache.store(creds, std::move(bdi), cached); return cached; } //------------------------------------------------------------------------------ // Fallback to unix authentication. Guaranteed to always return a valid // BoundIdentity object. (whether this is accepted by the server is another // matter) //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::unixAuth(pid_t pid, uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope, const Environment& pidEnv) { LOGBOOK_INSERT(scope, "Producing UNIX identity out of pid=" << pid << ", uid=" << uid << ", gid=" << gid); return unixAuthenticator.createIdentity(pid, uid, gid, reconnect, pidEnv.get("EOS_FUSE_SECRET")); } //------------------------------------------------------------------------------ // Attempt to produce a BoundIdentity object out of default paths, such // as /tmp/krb5cc_. // If not possible, return nullptr. //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::defaultPathsToBoundIdentity(const JailInformation& jail, uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope, const Environment& pidEnv) { // Pretend as if the environment of the process simply contained the default values, // and follow the usual code path. Environment defaultEnv; { // get the default cache from KRB5 krb5_context krb_ctx; krb5_error_code ret = krb5_init_context(&krb_ctx); if (ret == 0) { std::string default_name = krb5_cc_default_name(krb_ctx); if ((default_name.substr(0, 5) == "FILE:") || (default_name.substr(0, 5) == "/tmp/")) { defaultEnv.push_back("KRB5CCNAME=FILE:/tmp/krb5cc_" + std::to_string(uid)); } else if (default_name.substr(0, 18) == "KEYRING:persistent") { defaultEnv.push_back("KRB5CCNAME=KEYRING:persistent:" + std::to_string(uid)); } else { defaultEnv.push_back("KRB5CCNAME=" + default_name); } krb5_free_context(krb_ctx); } } defaultEnv.push_back("X509_USER_PROXY=/tmp/x509up_u" + std::to_string(uid)); defaultEnv.push_back("OAUTH2_TOKEN=FILE:/tmp/oauthtk_" + std::to_string(uid)); defaultEnv.push_back("BEARER_TOKEN_FILE=/tmp/bt_u" + std::to_string(uid)); LogbookScope subscope(scope.makeScope( SSTR("Attempting to produce BoundIdentity out of default paths for uid=" << uid))); // attach secret key and endorsement defaultEnv.push_back("EOS_FUSE_SECRET=" + pidEnv.get("EOS_FUSE_SECRET")); defaultEnv.push_back("XrdSecsssENDORSEMENT=" + pidEnv.get("XrdSecsssENDORSEMENT")); return environmentToBoundIdentity(jail, defaultEnv, uid, gid, reconnect, subscope, false); } //------------------------------------------------------------------------------ // Attempt to produce a BoundIdentity object out of the global eosfusebind // binding. If not possible, return nullptr. //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::globalBindingToBoundIdentity(const JailInformation& jail, uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope, const Environment& pidEnv) { // Pretend as if the environment of the process simply contained the eosfusebind // global bindings, and follow the usual code path. Environment defaultEnv; defaultEnv.push_back(SSTR("KRB5CCNAME=FILE:/var/run/eos/credentials/uid" << uid << ".krb5")); defaultEnv.push_back(SSTR("X509_USER_PROXY=/var/run/eos/credentials/uid" << uid << ".x509")); LogbookScope subscope(scope.makeScope( SSTR("Attempting to produce BoundIdentity out of eosfusebind " << "global binding for uid=" << uid))); defaultEnv.push_back("EOS_FUSE_SECRET=" + pidEnv.get("EOS_FUSE_SECRET")); defaultEnv.push_back("XrdSecsssENDORSEMENT=" + pidEnv.get("XrdSecsssENDORSEMENT")); return environmentToBoundIdentity(jail, defaultEnv, uid, gid, reconnect, subscope, true); } //------------------------------------------------------------------------------ // Attempt to produce a BoundIdentity object out of environment variables // of the given PID. If not possible, return nullptr. //------------------------------------------------------------------------------ std::shared_ptr BoundIdentityProvider::pidEnvironmentToBoundIdentity( const JailInformation& jail, pid_t pid, uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope, Environment& env) { LogbookScope subscope(scope.makeScope( SSTR("Attempting to produce BoundIdentity out of process environment, pid=" << pid))); // First, let's read the environment to build up a UserCredentials object. FutureEnvironment response = environmentReader.stageRequest(pid, uid); if (!response.waitUntilDeadline( std::chrono::milliseconds(credConfig.environ_deadlock_timeout))) { eos_static_info("Timeout when retrieving environment for pid %d (uid %d) - we're doing an execve!", pid, uid); LOGBOOK_INSERT(subscope, "FAILED in retrieving environment variables for pid=" << pid << ": TIMEOUT after " << credConfig.environ_deadlock_timeout << " ms"); return {}; } LOGBOOK_INSERT(subscope, "Succeeded in retrieving environment " "variables for pid=" << pid); // store environment env = response.get(); return environmentToBoundIdentity(jail, env, uid, gid, reconnect, subscope, true); } //------------------------------------------------------------------------------ // Check if the given BoundIdentity object is still valid. //------------------------------------------------------------------------------ bool BoundIdentityProvider::checkValidity(const JailInformation& jail, const BoundIdentity& identity) { if (!identity.getCreds()) { return false; } if (identity.getAge() > std::chrono::hours(24)) { return false; } return validator.checkValidity(jail, *identity.getCreds()); }