/* * 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