diff --git a/PAM/ssh/include/rublon/core_handler.hpp b/PAM/ssh/include/rublon/core_handler.hpp index 714ae99..bf6b116 100644 --- a/PAM/ssh/include/rublon/core_handler.hpp +++ b/PAM/ssh/include/rublon/core_handler.hpp @@ -8,9 +8,14 @@ #include #include #include +#include + +#include #include +#include + namespace rublon { template < typename HttpHandler = CURL > @@ -33,13 +38,6 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { } bool containsException(const Document & coreResponse) const { - - // if(resp.HasMember("status") and resp["status"].IsObject() and resp["result"].HasMember("exception")) { - // const auto & exception = resp["result"]["exception"].GetString(); - // log(LogLevel::Error, "rublon core exception %s", exception); - // return handleCoreException(exception); - // } - using namespace std::string_view_literals; return coreResponse.HasMember("status") and coreResponse["status"].GetString() == "ERROR"sv; } @@ -57,7 +55,7 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { tl::expected< std::reference_wrapper< const Response >, Error > validateSignature(const Response & response) const { if(not responseSigned(response)) { - log(LogLevel::Error, "CoreHandlerError::BadSigature"); + log(LogLevel::Error, "rublon core response is not signed"); return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}}; } @@ -74,11 +72,11 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { log(LogLevel::Error, "Rublon Core responded with broken data"); return tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}}; } - + else if(containsException(resp)) { - const auto & exception = resp["result"]["exception"].GetString(); + const auto* exception = JSONPointer{"/result/exception", &alloc}.Get(resp); log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception); - return handleCoreException(exception); + return handleCoreException(exception->GetString()); } return resp; @@ -89,7 +87,7 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserBaypass}}}; } else { return tl::unexpected{ - Error{CoreHandlerError{CoreHandlerError::CoreException, std::string{exceptionString.data(), exceptionString.size()}}}}; + Error{CoreHandlerError{CoreHandlerError::RublonCoreException, std::string{exceptionString.data(), exceptionString.size()}}}}; } } @@ -130,6 +128,19 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { .and_then(validateResponse) .or_else(handleError); } + + ///TODO use WS_Handler type to allow mocking / changind implementation of WS + tl::expected< AuthenticationStatus, Error > waitForConfirmation(std::string_view tid) const { + bool aproved{}; + + WebSocket ws; + aproved = ws.connect(url, tid); + + log(LogLevel::Info, "websocket :s", aproved ? "approved" : "denied"); + + return aproved ? AuthenticationStatus{AuthenticationStatus::Action::Confirmed} : + AuthenticationStatus{AuthenticationStatus::Action::Denied}; + } }; } // namespace rublon diff --git a/PAM/ssh/include/rublon/core_handler_interface.hpp b/PAM/ssh/include/rublon/core_handler_interface.hpp index b03797d..58f4a85 100644 --- a/PAM/ssh/include/rublon/core_handler_interface.hpp +++ b/PAM/ssh/include/rublon/core_handler_interface.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace rublon { @@ -15,5 +16,10 @@ class CoreHandlerInterface { rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::request"); return static_cast< const Impl * >(this)->request(mr, path, body); } + + tl::expected< AuthenticationStatus, Error > waitForConfirmation(std::string_view tid) const { + rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::listen"); + return static_cast< const Impl * >(this)->waitForConfirmation(tid); + } }; } // namespace rublon diff --git a/PAM/ssh/include/rublon/curl.hpp b/PAM/ssh/include/rublon/curl.hpp index 22c0544..13166df 100644 --- a/PAM/ssh/include/rublon/curl.hpp +++ b/PAM/ssh/include/rublon/curl.hpp @@ -6,8 +6,10 @@ #include #include #include +#include #include #include +#include #include @@ -79,7 +81,7 @@ class CURL { memory::MonotonicStackResource< 8 * 1024 > stackResource; std::pmr::string response_data{&stackResource}; - response_data.reserve(6000); + response_data.reserve(7000); /// TODO this can be done on stack using pmr auto curl_headers = std::unique_ptr< curl_slist, void (*)(curl_slist *) >(nullptr, curl_slist_free_all); @@ -98,7 +100,13 @@ class CURL { curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data); - log(LogLevel::Debug, "%s request send, uri:%s body:\n%s\n", "CURL", uri.data(), request.body.c_str()); + + log(LogLevel::Debug, "Sending request to %s", uri.data()); + for(const auto &[name, value] : request.headers){ + log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str()); + } + log(LogLevel::Debug, "Body %s", request.body.c_str()); + const auto res = curl_easy_perform(curl.get()); if(res != CURLE_OK) { @@ -111,16 +119,20 @@ class CURL { if(http_code >= 500) { log(LogLevel::Error, "%s response with code %d ", "CURL", http_code); - return tl::unexpected{ConnectionError{ConnectionError::Error, http_code}}; + return tl::unexpected{ConnectionError{ConnectionError::HttpError, http_code}}; } - log(LogLevel::Debug, "Response:\n%s\n", response_data.c_str()); - - long size; + long size{}; curl_easy_getinfo(curl.get(), CURLINFO_HEADER_SIZE, &size); details::headers(response_data, response.headers); response.body = response_data.substr(size); + + log(LogLevel::Debug, "Received %d bytes", response_data.size()); + for(const auto &[name, value] : response.headers){ + log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str()); + } + log(LogLevel::Debug, "Body %s", response.body.c_str()); return response; } diff --git a/PAM/ssh/include/rublon/error.hpp b/PAM/ssh/include/rublon/error.hpp index 8a5f915..425bce5 100644 --- a/PAM/ssh/include/rublon/error.hpp +++ b/PAM/ssh/include/rublon/error.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -9,9 +10,10 @@ namespace rublon { #define names constexpr static inline const char * errorClassPrettyName[] + class ConnectionError { public: - enum ErrorClass { Timeout, Error }; + enum ErrorClass { Timeout, HttpError }; names = {"Timeout", "Error"}; constexpr static inline auto prettyName = "Connection Error"; @@ -29,8 +31,8 @@ class ConnectionError { class CoreHandlerError { public: - enum ErrorClass { BadSigature, CoreException, BrokenData }; - names = {"BadSigature", "CoreException", "BrokenData"}; + enum ErrorClass { BadSigature, RublonCoreException, BrokenData }; + names = {"BadSigature", "RublonCoreException", "BrokenData"}; constexpr static inline auto prettyName = "Core Handler Error"; diff --git a/PAM/ssh/include/rublon/init.hpp b/PAM/ssh/include/rublon/init.hpp index 1a21595..51785a4 100644 --- a/PAM/ssh/include/rublon/init.hpp +++ b/PAM/ssh/include/rublon/init.hpp @@ -13,36 +13,35 @@ class Verify {}; } // namespace rublon namespace rublon { -std::pmr::string osName(std::pmr::memory_resource * mr) { +std::pmr::string osName(std::pmr::memory_resource *mr) { memory::MonotonicStackResource< 8 * 1024 > stackResource; - /// TODO move this to utils + std::ifstream file(std::filesystem::path{"/etc/os-release"}); if(not file.good()) return {"unknown", mr}; - + std::pmr::string line{&stackResource}; line.reserve(100); - - /// TODO introduce toKeyValue function in utils + while(std::getline(file, line)) { std::pmr::string _key{&stackResource}; std::pmr::string _value{&stackResource}; - + if(!line.length()) continue; - + if(line[0] == '#' || line[0] == ';') continue; - + auto posEqual = line.find('='); - _key = line.substr(0, posEqual); - _value = line.substr(posEqual + 1); - - if(_key == "PRETTY_NAME") { + _key = line.substr(0, posEqual); + _value = line.substr(posEqual + 1); + + if(_key == "PRETTY_NAME"){ return {_value, mr}; } } - + return {"unknown", mr}; } @@ -52,6 +51,12 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > { const char * apiPath = "/api/transaction/init"; + tl::expected< MethodSelect_t, Error > createMethod(const Document & coreResponse) const { + const auto & rublonResponse = coreResponse["result"]; + std::string tid = rublonResponse["tid"].GetString(); + return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"]}; + } + template < typename PamInfo_t > void addPamInfo(Document & coreRequest, const PamInfo_t & pam) const { auto & alloc = coreRequest.GetAllocator(); @@ -86,7 +91,7 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > { const PamInfo_t & pam) const { using namespace std::string_view_literals; const auto & resp = coreResponse; - + if(resp.HasMember("result") and resp["result"].IsObject() and resp["result"].HasMember("status")) { const auto & status = resp["result"]["status"].GetString(); log(LogLevel::Warning, "Got enrolement message with stats %s", status); @@ -102,16 +107,8 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > { return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserDenied}}}; } } - - /// TODO server response is malformed - - return coreResponse; - } - tl::expected< MethodSelect_t, Error > createMethod(const Document & coreResponse) const { - const auto & rublonResponse = coreResponse["result"]; - std::string tid = rublonResponse["tid"].GetString(); - return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"]}; + return coreResponse; } public: diff --git a/PAM/ssh/include/rublon/memory.hpp b/PAM/ssh/include/rublon/memory.hpp index a355e94..bfe69fb 100644 --- a/PAM/ssh/include/rublon/memory.hpp +++ b/PAM/ssh/include/rublon/memory.hpp @@ -60,7 +60,8 @@ namespace memory { : MonotonicHeapResourceBase{N}, std::pmr::monotonic_buffer_resource{this->_buffer, this->_size, std::pmr::null_memory_resource()} {} }; - + + using StrictMonotonic_512_HeapResource = StrictMonotonicHeapResource< 512 >; using StrictMonotonic_1k_HeapResource = StrictMonotonicHeapResource< 1 * 1024 >; using StrictMonotonic_2k_HeapResource = StrictMonotonicHeapResource< 2 * 1024 >; using StrictMonotonic_4k_HeapResource = StrictMonotonicHeapResource< 4 * 1024 >; diff --git a/PAM/ssh/include/rublon/method/OTP.hpp b/PAM/ssh/include/rublon/method/OTP.hpp index 5e8322d..8f2ad56 100644 --- a/PAM/ssh/include/rublon/method/OTP.hpp +++ b/PAM/ssh/include/rublon/method/OTP.hpp @@ -13,7 +13,7 @@ namespace rublon::method { class OTP : public PasscodeBasedAuth { public: OTP(std::string systemToken, std::string tid) - : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "OTP", "Enter code from Rublon Authenticator: ") {} + : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "OTP", "Enter code from Rublon Authenticator: ", 6, true) {} }; } // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/PUSH.hpp b/PAM/ssh/include/rublon/method/PUSH.hpp index b6046f8..0cf3ee3 100644 --- a/PAM/ssh/include/rublon/method/PUSH.hpp +++ b/PAM/ssh/include/rublon/method/PUSH.hpp @@ -12,7 +12,8 @@ namespace rublon::method { class PUSH : public WebsocketBasedAuth { public: - PUSH(std::string systemToken, std::string tid) : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "PUSH") {} + PUSH(std::string systemToken, std::string tid) + : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "PUSH") {} }; } // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/SMS.hpp b/PAM/ssh/include/rublon/method/SMS.hpp index 501dcd3..bff562c 100644 --- a/PAM/ssh/include/rublon/method/SMS.hpp +++ b/PAM/ssh/include/rublon/method/SMS.hpp @@ -12,7 +12,7 @@ namespace rublon::method { class SMS : public PasscodeBasedAuth { public: - SMS(std::string systemToken, std::string tid) : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "SMS", "Enter SMS passcode: ") {} + SMS(std::string systemToken, std::string tid) : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "SMS", "Enter SMS passcode: ", 6, true) {} }; } // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/SmsLink.hpp b/PAM/ssh/include/rublon/method/SmsLink.hpp new file mode 100644 index 0000000..e4d1092 --- /dev/null +++ b/PAM/ssh/include/rublon/method/SmsLink.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include +#include +#include + +#include + +namespace rublon::method { + +class SmsLink : public WebsocketBasedAuth { + public: + SmsLink(std::string systemToken, std::string tid) + : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "smsLink") {} +}; + +} // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/YOTP.hpp b/PAM/ssh/include/rublon/method/YOTP.hpp new file mode 100644 index 0000000..9170220 --- /dev/null +++ b/PAM/ssh/include/rublon/method/YOTP.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include +#include +#include + +#include + +namespace rublon::method { + +class YOTP : public PasscodeBasedAuth { + public: + YOTP(std::string systemToken, std::string tid) + : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "YOTP", "Press Yubikey: ", 44, false) {} +}; + +} // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/method_select.hpp b/PAM/ssh/include/rublon/method/method_select.hpp index ccb597f..fd24263 100644 --- a/PAM/ssh/include/rublon/method/method_select.hpp +++ b/PAM/ssh/include/rublon/method/method_select.hpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include template < class F > struct return_type; @@ -56,7 +58,7 @@ class MethodProxy { } private: - std::variant< method::OTP, method::SMS, method::PUSH > _impl; + std::variant< method::OTP, method::SMS, method::PUSH, method::SmsLink, method::YOTP > _impl; }; class PostMethod : public rublon::AuthenticationStep< PostMethod > { @@ -73,8 +75,12 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > { return MethodProxy{method::OTP{this->_systemToken, std::move(tid)}}; } else if(_method == "sms") { return MethodProxy{method::SMS{this->_systemToken, std::move(tid)}}; - } else if(_method == "push") { + } else if(_method == "push"){ return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}}; + } else if(_method == "smsLink") { + return MethodProxy{method::SmsLink{this->_systemToken, std::move(tid)}}; + } else if(_method == "yotp") { + return MethodProxy{method::YOTP{this->_systemToken, std::move(tid)}}; } else @@ -83,10 +89,7 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > { void addParams(Document & body) const { auto & alloc = body.GetAllocator(); - body.AddMember("method", Value{_method.c_str(), alloc}, alloc); - body.AddMember("GDPRAccepted", "true", alloc); - body.AddMember("tosAccepted", "true", alloc); } public: @@ -153,7 +156,15 @@ class MethodSelect { methods_id[++i] = method; continue; } - + + /// Needs changes in Core to work + // if(method == "yotp") { + // logMethodAvailable(method); + // pam.print("%d: Yubikey", i + 1); + // methods_id[++i] = method; + // continue; + // } + if(method == "sms") { logMethodAvailable(method); pam.print("%d: SMS code", i + 1); @@ -167,6 +178,14 @@ class MethodSelect { methods_id[++i] = method; continue; } + + if(method == "smsLink") { + logMethodAvailable(method); + pam.print("%d: SMS Link", i + 1); + methods_id[++i] = method; + continue; + } + logMethodNotAvailable(method); } @@ -198,6 +217,7 @@ class MethodSelect { .scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_id.size()) // .transform_error(toMethodError) .and_then(createMethod); + /// TODO or_else(printErrorAndDenyAccess); ?? }; const auto askForMethod = [&](int methods_number) -> tl::expected< uint32_t, MethodError > { @@ -221,7 +241,7 @@ class MethodSelect { } }; - /// TODO make this some kind of global + const auto toGenericError = [](const MethodError & e) -> Error { return Error{e}; }; return printAvailableMethods() // diff --git a/PAM/ssh/include/rublon/method/passcode_based_auth.hpp b/PAM/ssh/include/rublon/method/passcode_based_auth.hpp index 7fe2761..2b96c57 100644 --- a/PAM/ssh/include/rublon/method/passcode_based_auth.hpp +++ b/PAM/ssh/include/rublon/method/passcode_based_auth.hpp @@ -9,20 +9,40 @@ namespace rublon::method { class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > { + protected: using base_t = AuthenticationStep< PasscodeBasedAuth >; const char * uri = "/api/transaction/confirmCode"; const char * userMessage{nullptr}; + const uint_fast8_t length; + const bool onlyDigits; + constexpr static bool isdigit(char ch) { return std::isdigit(static_cast< unsigned char >(ch)); } - - static bool digitsOnly(std::string_view userinput) { + + bool digitsOnly(std::string_view userinput) const { return std::all_of(userinput.cbegin(), userinput.cend(), isdigit); } - static bool hasProperLength(std::string_view userInput) { - return userInput.size() == 6; + bool hasValidLength(std::string_view userInput) const { + if(userInput.size() == length) { + log(LogLevel::Debug, "User input size %d is correct", userInput.size()); + return true; + } else { + log(LogLevel::Warning, "User input size %d is different then %d", userInput.size(), length); + return false; + } + } + + bool hasValidCharacters(std::string_view userInput) const { + if(onlyDigits ? digitsOnly(userInput) : true) { + log(LogLevel::Debug, "User input contains valid characters"); + return true; + } else { + log(LogLevel::Warning, "User input contains characters different then digits"); + return false; + } } template < typename PamInfo_t = LinuxPam > @@ -30,7 +50,7 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > { auto & alloc = body.GetAllocator(); auto vericode = pam.scan([](const char * userInput) { return std::string{userInput}; }, userMessage); - if(hasProperLength(vericode) and digitsOnly(vericode)) { + if(hasValidLength(vericode) and hasValidCharacters(vericode)) { body.AddMember("vericode", Value{vericode.c_str(), alloc}, alloc); return body; } @@ -40,7 +60,7 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > { template < typename PamInfo_t = LinuxPam > tl::expected< std::reference_wrapper< Document >, Error > askForPasscodeAgain(Document & body, const PamInfo_t & pam) const { - pam.print("passcode has wrong number of digits or illegal characters, please correct"); + pam.print("passcode has wrong number of digits or contains illegal characters, please correct"); return readPasscode(body, pam); } @@ -53,7 +73,7 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > { pam.print("Wrong code"); return tl::unexpected{Error{WerificationError{WerificationError::WrongCode}}}; } - + pam.print("Verification code validated"); return AuthenticationStatus{AuthenticationStatus::Action::Confirmed}; } @@ -61,8 +81,13 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > { public: const char * name; - PasscodeBasedAuth(std::string systemToken, std::string tid, const char * name, const char * userMessage) - : base_t(std::move(systemToken), std::move(tid)), userMessage{userMessage}, name{name} {} + PasscodeBasedAuth(std::string systemToken, + std::string tid, + const char * name, + const char * userMessage, + uint_fast8_t length, + bool numbersOnly) + : base_t(std::move(systemToken), std::move(tid)), userMessage{userMessage}, length{length}, onlyDigits{numbersOnly}, name{name} {} template < typename Hander_t, typename PamInfo_t = LinuxPam > tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const { diff --git a/PAM/ssh/include/rublon/method/websocket_based_auth.hpp b/PAM/ssh/include/rublon/method/websocket_based_auth.hpp index 2a39395..f7b866d 100644 --- a/PAM/ssh/include/rublon/method/websocket_based_auth.hpp +++ b/PAM/ssh/include/rublon/method/websocket_based_auth.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -15,21 +16,16 @@ class WebsocketBasedAuth : public AuthenticationStep< WebsocketBasedAuth > { public: const char * name = ""; - + WebsocketBasedAuth(std::string systemToken, std::string tid, const char * name) : base_t(std::move(systemToken), std::move(tid)), name{name} {} template < typename Hander_t, typename PamInfo_t = LinuxPam > - tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & /*coreHandler*/, - const PamInfo_t & pam) const { + tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const { log(LogLevel::Info, "starting WS"); - - WebSocket ws; - pam.print("Waiting for user approval"); - auto aproved = ws.connect(this->_tid); - return aproved ? AuthenticationStatus{AuthenticationStatus::Action::Confirmed} : - AuthenticationStatus{AuthenticationStatus::Action::Denied}; + pam.print("Waiting for approval"); + return coreHandler.waitForConfirmation(_tid); } }; diff --git a/PAM/ssh/include/rublon/pam_action.hpp b/PAM/ssh/include/rublon/pam_action.hpp index 3cba546..c69685a 100644 --- a/PAM/ssh/include/rublon/pam_action.hpp +++ b/PAM/ssh/include/rublon/pam_action.hpp @@ -2,10 +2,6 @@ namespace rublon { -enum class PamAction { accept, decline }; - -enum Authen {}; - class AuthenticationStatus { public: enum class Action { Denied, Confirmed, Bypass }; diff --git a/PAM/ssh/include/rublon/rublon.hpp b/PAM/ssh/include/rublon/rublon.hpp index e0d241a..88e223a 100644 --- a/PAM/ssh/include/rublon/rublon.hpp +++ b/PAM/ssh/include/rublon/rublon.hpp @@ -18,27 +18,55 @@ namespace rublon { // } // } - - class RublonSession { + public: + std::pmr::string _tid; + + RublonSession() { + /// create memoryResource + /// + } + void startTransaction(std::string_view tid) {} +}; + +class RublonExceptionHandler{ public: }; template < typename Pam_T, typename CoreHandler_T > class RublonBase { - Pam_T & pam; - Configuration config{}; + const Pam_T & _pam; + Configuration _config{}; void initializeLogs() { details::initLog(); } public: - RublonBase(Pam_T & pam) : pam{pam} {} + RublonBase(const Pam_T & pam, Configuration config) : _pam{pam}, _config{config} {} - // AuthenticationStatus authenticate() {} + AuthenticationStatus authenticate() const { + + } }; using RublonLinux = RublonBase< LinuxPam, CoreHandler< CURL > >; +class RublonFactory{ + public: + template + tl::expected create(const Pam_t &pam){ + auto config = ConfigurationFactory{}.systemConfig(); + if(not config.has_value()) { + pam.print("\n"); + pam.print("Rublon configuration does not exists or is invalid"); + pam.print("\tcheck '%s' for more details\n", details::logPath()); + return tl::unexpected{false}; + } + + return RublonLinux{pam, *config}; + } +}; + + } // namespace rublon diff --git a/PAM/ssh/include/rublon/websockets.hpp b/PAM/ssh/include/rublon/websockets.hpp index 8652073..f2722e5 100644 --- a/PAM/ssh/include/rublon/websockets.hpp +++ b/PAM/ssh/include/rublon/websockets.hpp @@ -6,10 +6,8 @@ #include #include -#include namespace rublon { -#define SERVER_ADDRESS "wss://staging-core.rublon.net/ws/socket.io" #define TIMEOUT_SECONDS 90 /// {"status":"ERROR","code":400,"result":{"exception":"MobileAppMissingException","code":32,"errorMessage":"No mobile app @@ -86,14 +84,15 @@ class WebSocketSingleShotEventListener { bool event_received = false; Status _status{}; - - void printobj (std::size_t i, const std::map< std::string, sio::message::ptr > & objects) { - for(const auto &[name, msg]:objects){ + + void printobj(std::size_t i, const std::map< std::string, sio::message::ptr > & objects) { + for(const auto & [name, msg] : objects) { log(LogLevel::Debug, "Object %ld: %s", i, name.c_str()); printMessage(i, msg); } }; - void printMessage (std::size_t i, const sio::message::ptr & message) { + + void printMessage(std::size_t i, const sio::message::ptr & message) { switch(message->get_flag()) { case sio::message::flag_integer: log(LogLevel::Debug, "message %ld integer : %d", i, message->get_int()); @@ -134,7 +133,6 @@ class WebSocketSingleShotEventListener { log(LogLevel::Debug, "event nsp : %s", e.get_nsp().c_str()); log(LogLevel::Debug, "event messages: %d", e.get_messages().size()); - for(std::size_t i = 0; i < e.get_messages().size(); i++) { const auto & message = e.get_messages().at(i); printMessage(i, message); @@ -145,13 +143,12 @@ class WebSocketSingleShotEventListener { void onConfirmed(sio::event & e) { generic(e, Status::Approved); - log(LogLevel::Info, "Autentication confirmed over PUSH"); + log(LogLevel::Info, "Autentication confirmed"); } void onDenied(sio::event & e) { generic(e, Status::Denied); log(LogLevel::Info, "Autentication denied by user"); - sleep(30); } void onExpired(sio::event & e) { @@ -169,7 +166,7 @@ class WebSocketSingleShotEventListener { void onAny(sio::event & e) { std::lock_guard lock{_lock}; event_received = true; - log(LogLevel::Info, "name %s", e.get_name().c_str()); + log(LogLevel::Info, "onAny message received: name %s", e.get_name().c_str()); _cond.notify_all(); } @@ -184,12 +181,15 @@ class WebSocketSingleShotEventListener { _ws.socket()->on_any([this](sio::event & e) { this->onAny(e); }); } + /// TODO pass timeout tl::expected< Status, bool > waitForEvent() { std::lock_guard lock{_lock}; if(!event_received) { - log(LogLevel::Info, "Waiting for confirmation"); + log(LogLevel::Info, "Waiting for WS event"); if(_cond.wait_until(_lock, std::chrono::system_clock::now() + std::chrono::minutes{2}) == std::cv_status::timeout) { - log(LogLevel::Info, "Waiting for confirmation failed due to WS timeout"); + log(LogLevel::Info, "Waiting for WS event failed due to WS timeout"); + }else{ + log(LogLevel::Info, "Event arrived"); } } @@ -198,29 +198,38 @@ class WebSocketSingleShotEventListener { }; class WebSocket { - sio::client ws; - + public: - std::string token; - - WebSocket() {} - - ~WebSocket() {} - - bool connect(std::string _token) { + bool connect(std::string_view uri, std::string_view tid) { std::mutex _lock; std::condition_variable_any _cond; + sio::client ws; + + /// needed here only for rublon core api URL, so 1k fine + memory::StrictMonotonic_1k_HeapResource memoryResource; + std::pmr::string str{uri.data(), uri.size(), &memoryResource}; + std::pmr::string httpsPrefix = {"https://", &memoryResource}; - token = _token; + if(uri.rfind(httpsPrefix, 0) == 0) { + str.replace(str.find(httpsPrefix), httpsPrefix.size(), "wss://"); + } + str += "/ws/socket.io/"; // ws.set_logs_verbose(); - log(LogLevel::Debug, "start listening"); - ws.connect("wss://staging-core.rublon.net/ws/socket.io/"); + log(LogLevel::Debug, "WS connect to %s", str.c_str()); + ws.connect(str.c_str()); const auto attachToTransactionConfirmationChannel = [&](const auto & /*status*/) -> tl::expected< bool, bool > { + memory::StrictMonotonic_1k_HeapResource resource; + std::pmr::string channel{&resource}; + channel.reserve(100); + channel += "transactionConfirmation."; + channel += tid; + /// TODO check status auto message = std::dynamic_pointer_cast< sio::object_message >(sio::object_message::create()); - message->insert("channel", "transactionConfirmation." + _token); + message->insert("channel", channel.c_str()); + log(LogLevel::Debug, "emiting %s message on subscribe channel", channel.c_str()); ws.socket()->emit("subscribe", {message}); return true; /// TODO @@ -231,15 +240,13 @@ class WebSocket { return eventListener.waitForEvent(); }; - WebSocketConnectionListener l(ws, _lock, _cond); - auto v = l.waitForConnection() // + auto v = WebSocketConnectionListener{ws, _lock, _cond} + .waitForConnection() // .and_then(attachToTransactionConfirmationChannel) .and_then(waitForUserAction) .value_or(WebSocketSingleShotEventListener::Status::Denied); - + return v == WebSocketSingleShotEventListener::Status::Approved; } - - void listen() {} }; } // namespace rublon diff --git a/PAM/ssh/lib/pam.cpp b/PAM/ssh/lib/pam.cpp index d51477f..8bb25c1 100644 --- a/PAM/ssh/lib/pam.cpp +++ b/PAM/ssh/lib/pam.cpp @@ -26,22 +26,23 @@ DLL_PUBLIC int pam_sm_acct_mgmt([[maybe_unused]] pam_handle_t * pamh, DLL_PUBLIC int pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) { using namespace rublon; - + // std::freopen(rublon::details::logPath(), "a+", stdout); LinuxPam pam{pamh}; - - RublonLinux rublon{pam}; - - auto rublonConfig = ConfigurationFactory{}.systemConfig(); - if(not rublonConfig.has_value()) { + // std::freopen(rublon::details::logPath(), "a+", stderr); + auto config = ConfigurationFactory{}.systemConfig(); + if(not config.has_value()) { pam.print("\n"); pam.print("Rublon configuration does not exists or is invalid"); pam.print("\tcheck '%s' for more details\n", details::logPath()); return PAM_SUCCESS; } - - RublonMemory mem; - CoreHandler CH{rublonConfig.value()}; + std::byte sharedMemory[32 * 1024] = {}; + std::pmr::monotonic_buffer_resource mr{sharedMemory, std::size(sharedMemory)}; + std::pmr::unsynchronized_pool_resource rublonPoolResource{&mr}; + std::pmr::set_default_resource(&rublonPoolResource); + + CoreHandler CH{*config}; auto selectMethod = [&](const MethodSelect & selector) { return selector.create(pam); }; auto confirmMethod = [&](const PostMethod & confirm) { return confirm.fire(CH); }; @@ -96,7 +97,7 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu if(error.is< ConnectionError >()) { - if(rublonConfig->bypass) { + if(config->bypass) { pam.print("Connection Error, bypass enabled"); return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); } else { @@ -106,13 +107,17 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu } if(error.is< CoreHandlerError >()) { - pam.print("\n RUBLON server returned '%s' exception", error.get< CoreHandlerError >().reson.c_str()); + const auto &reson = error.get< CoreHandlerError >().reson; + pam.print("\n RUBLON server returned '%s' exception", reson.c_str()); + + if(reson == "UserBypassedException" or reson == "UserNotFoundException") + return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); } return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); }; - auto ret = Init{rublonConfig.value()} + auto ret = Init{config.value()} .fire(CH, pam) // .and_then(selectMethod) .and_then(confirmMethod) diff --git a/PAM/ssh/tests/core_response_generator.hpp b/PAM/ssh/tests/core_response_generator.hpp index 3a9fd10..8bb25a6 100644 --- a/PAM/ssh/tests/core_response_generator.hpp +++ b/PAM/ssh/tests/core_response_generator.hpp @@ -5,9 +5,6 @@ #include #include -#include -#include -#include #include namespace io { @@ -47,4 +44,6 @@ static constexpr const char * wrongPasscode = // static constexpr const char * projectError = R"json({"status":"ERROR","code":400,"result":{"exception":"APIException","code":10,"errorMessage":"Project error","details":null}})json"; +static constexpr const char * blah= ""; + } // namespace diff --git a/PAM/ssh/tests/http_mock.hpp b/PAM/ssh/tests/http_mock.hpp index 9457e2d..1c8c641 100644 --- a/PAM/ssh/tests/http_mock.hpp +++ b/PAM/ssh/tests/http_mock.hpp @@ -191,7 +191,7 @@ class ResponseBase { } Generator & withServiceUnavailableError() { - options.error = rublon::Error{rublon::ConnectionError{rublon::ConnectionError::Error, 405}}; + options.error = rublon::Error{rublon::ConnectionError{rublon::ConnectionError::HttpError, 405}}; return static_cast< Generator & >(*this); } diff --git a/PAM/ssh/tests/passcode_auth_tests.cpp b/PAM/ssh/tests/passcode_auth_tests.cpp index 474682a..4d8e1e5 100644 --- a/PAM/ssh/tests/passcode_auth_tests.cpp +++ b/PAM/ssh/tests/passcode_auth_tests.cpp @@ -10,9 +10,11 @@ using namespace testing; using namespace rublon; + +///TODO test if lenght is checked class PasscodeBasedAuthTest : public Test { public: - PasscodeBasedAuthTest() : sut{systemToken, tid, name, userMessage} {} + PasscodeBasedAuthTest() : sut{systemToken, tid, name, userMessage, 6, true} {} std::string systemToken, tid; const char * name = "Test"; diff --git a/service/postinst b/service/postinst index 3c06fcd..6369730 100644 --- a/service/postinst +++ b/service/postinst @@ -1,17 +1,20 @@ #!/bin/bash +SSHD_CONF=/etc/ssh/sshd_config +SSHD_PAM_CONF=/etc/pam.d/sshd +RUBLON_CONFIG=/etc/rublon.config if [ ! -f /etc/rublon.config ] then mkdir -p /etc/rublon - cp -a /usr/share/rublon/rublon.config.defaults /etc/rublon.config + cp -a /usr/share/rublon/rublon.config.defaults $RUBLON_CONFIG + chown root:root $RUBLON_CONFIG + chmod 640 $RUBLON_CONFIG fi # get system id . /etc/os-release -SSHD_CONF=/etc/ssh/sshd_config -SSHD_PAM_CONF=/etc/pam.d/sshd grep -qe "^PasswordAuthentication" $SSHD_CONF && \ sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || \ @@ -25,3 +28,11 @@ grep -qe "^UsePAM" $SSHD_CONF && \ grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth required pam_rublon.so' $SSHD_PAM_CONF grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF + + +# ### UBUNTU 22.04 notes +# on ubuntu 22.04 options +# PasswordAuthentication yes +# ChallengeResponseAuthentication yes +# need to be set in configuration file AFTER UsePAM yes +# it will fail to use PAM (and will lock the machine) it this is not true