[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/ssh352/rstudio/main/src/cpp/server/session/ServerSessionRpc.cpp [Back]  [Original]

/*
 * ServerSessionRpc.cpp
 *
 * Copyright (C) 2022 by Posit Software, PBC
 *
 * Unless you have received this program directly from Posit Software pursuant
 * to the terms of a commercial license agreement with Posit Software, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include 

#include 

#include 
#include 
#include 

#include 
#include 

#include 
#include 

#define kInvalidSecretEndpoint "/invalid_secret"

using namespace rstudio::core;
using namespace boost::placeholders;

namespace rstudio {
namespace server {
namespace session_rpc {

namespace overlay {

typedef boost::function ValidationHandler;

Error initialize(
   const boost::shared_ptr& pSessionRpcServer,
   const std::string& sessionSharedSecret,
   ValidationHandler validationHandler,
   http::AsyncUriHandlerFunction invalidRequestHandler);

void addHandler(
   const std::string& prefix,
   const auth::SecureAsyncUriHandlerFunction& handler,
   bool allowUserAccess);

void addHttpProxyHandler(
   const std::string& prefix,
   const auth::SecureAsyncUriHandlerFunction& handler);

}

namespace {

std::string s_sessionSharedSecret;
boost::shared_ptr s_pSessionRpcServer;

void sessionProfileFilter(core::r_util::SessionLaunchProfile* pProfile)
{
   pProfile->config.environment.push_back(
            std::make_pair(kServerRpcSecretEnvVar, s_sessionSharedSecret));
}

void writeInvalidRequest(boost::shared_ptr pConnection)
{
   http::Response& response = pConnection->response();
   response.setStatusCode(core::http::status::BadRequest);
   response.setStatusMessage("Invalid request.");
   pConnection->writeResponse();
}

// used when clients are sending RPC via TCP
// ensures messages are sent from a trusted (logged in) user
bool validateSecureCookie(boost::shared_ptr pConnection,
                          std::string* pUser,
                          bool fallbackAllowed)
{
   const http::Request& request = pConnection->request();

   std::string cookieValue = request.cookieValueFromHeader(kRStudioRpcCookieHeader);
   if (cookieValue.empty())
   {
      if (!fallbackAllowed)
         LOG_WARNING_MESSAGE("No auth cookie supplied for server RPC call");
      return false;
   }

   std::string user = core::http::secure_cookie::readSecureCookie(cookieValue);
   if (user.empty())
   {
      if (!fallbackAllowed)
         LOG_WARNING_MESSAGE("Invalid auth cookie supplied for server RPC call");
      return false;
   }

   *pUser = user;
   return true;
}

// validation requiring Rpc secrets
void validationHandler(
      const auth::SecureAsyncUriHandlerFunction& handler,
      http::AsyncUriHandlerFunction unauthorizedResponseFunction,
      bool fallbackAllowed, // failure should not be logged
      boost::shared_ptr pConnection)
{
   std::string username;

   // validate that the secret matches what we expect
   std::string secret =
         pConnection->request().headerValue(kServerRpcSecretHeader);

   // if there is no secret, check for a message signature instead
   if (secret.empty())
   {
      if (!validateSecureCookie(pConnection, &username, fallbackAllowed))
      {
         LOG_DEBUG_MESSAGE("validateSecure cookie failed for: " + pConnection->request().uri());
         unauthorizedResponseFunction(pConnection);
         return;
      }
   }
   else
   {
      // used for traditional unix socket mode
      if (secret != s_sessionSharedSecret)
      {
         if (!fallbackAllowed)
         {
            LOG_WARNING_MESSAGE("Session attempted to invoke server RPC with invalid "
                                "secret " + secret);
         }
         LOG_DEBUG_MESSAGE("invalid shared secret - auth failed for: " + pConnection->request().uri());
         unauthorizedResponseFunction(pConnection);
         return;
      }

      // get user on the other end of the socket (if available)
      int uid = pConnection->request().remoteUid();
      if (uid != -1)
      {
         core::system::User user;
         Error error = system::getUserFromUserId(uid, user);
         if (error)
         {
            LOG_WARNING_MESSAGE("Couldn't determine user for Server RPC request");
            LOG_ERROR(error);
            unauthorizedResponseFunction(pConnection);
            return;
         }

         username = user.getUsername();
      }
   }
   pConnection->setUsername(username);

   LOG_DEBUG_MESSAGE("Handling session rpc: " + pConnection->request().debugInfo());

   // invoke the wrapped async URI handler
   handler(username, pConnection);
}

} // anonymous namespace

void addHandler(const std::string& prefix,
                const auth::SecureAsyncUriHandlerFunction& handler,
                bool allowUserAccess)
{
   if (s_pSessionRpcServer)
   {
      s_pSessionRpcServer->addHandler(
               prefix, boost::bind(validationHandler,
                                   handler,
                                   writeInvalidRequest,
                                   false /*fallbackAllowed*/,
                                   _1));

      overlay::addHandler(prefix, handler, allowUserAccess);
   }
}

void addHttpProxyHandler(const std::string &prefix,
                         const auth::SecureAsyncUriHandlerFunction &handler)
{
   if (s_pSessionRpcServer)
   {
      s_pSessionRpcServer->addHandler(
               prefix, boost::bind(validationHandler,
                                   handler,
                                   writeInvalidRequest,
                                   false /*fallbackAllowed*/,
                                   _1));

      overlay::addHttpProxyHandler(prefix, handler);
   }
}

void addPeriodicCommand(boost::shared_ptr pCmd)
{
   if (s_pSessionRpcServer)
   {
      s_pSessionRpcServer->addScheduledCommand(pCmd);
   }
}

Error startup()
{
   // start the server (it might not exist if project sharing isn't on)
   if (s_pSessionRpcServer)
      return s_pSessionRpcServer->run();

   return Success();
}

Error initialize()
{
   // create the async server instance
   s_pSessionRpcServer = boost::make_shared(
            "Session RPCs",
            std::string(),
            core::FileMode::ALL_READ_WRITE);

   // initialize with path to our socket
   Error error = boost::static_pointer_cast(s_pSessionRpcServer)->init(
            serverRpcSocketPath());
   if (error)
      return error;

   s_pSessionRpcServer->setScheduledCommandInterval(
            boost::posix_time::milliseconds(kSessionRpcCmdPeriodMs));

   // create the shared secret
   error = key_file::readSecureKeyFile("session-rpc-key",
                                       &s_sessionSharedSecret);
   if (error)
      return error;

   // inject the shared secret into the session
   sessionManager().addSessionLaunchProfileFilter(sessionProfileFilter);

   return overlay::initialize(
      s_pSessionRpcServer,
      s_sessionSharedSecret,
      validationHandler,
      writeInvalidRequest);
}

} // namespace session_rpc
} // namespace server
} // namespace rstudio

Web Proxy Viewer  |  New URL  |  Original Page