From 6a3882fa4759913bd5364b83dbd29e80b68ec226 Mon Sep 17 00:00:00 2001 From: rublon-bwi <134260122+rublon-bwi@users.noreply.github.com> Date: Mon, 17 Jun 2024 08:57:26 +0200 Subject: [PATCH] Bwi/v2.0.2 rc1 (#10) * Remove unused options from rublon default config * Remove safe|secure options * Allow 9 digits long passcode for passcode bypass * Change name of 'Mobile Passcode' to 'Passcode' * Do not display any prompt when user is waiting * remove unused alloca.h header * Add autopushPrompt option * Change name OTP method * Change enrolement message handling * ad static string ctor * Addded postrm script * Rename 01_rublon_ssh.conf to 01-rublon-ssh.conf * restart sshd service after rublon package instalation * Fix sshd not restarting bug on ubuntu 24.04 * disable logging from websocket-io * change package name to match old package name * Fix compilation issue when using non owning ptr * Set version to 2.0.0 --- CMakeLists.txt | 5 +- PAM/ssh/CMakeLists.txt | 4 +- PAM/ssh/include/rublon/check_application.hpp | 107 +++++++++++++++--- PAM/ssh/include/rublon/configuration.hpp | 4 +- PAM/ssh/include/rublon/error.hpp | 1 + PAM/ssh/include/rublon/init.hpp | 25 ++-- PAM/ssh/include/rublon/method/OTP.hpp | 2 +- PAM/ssh/include/rublon/method/PUSH.hpp | 4 +- .../include/rublon/method/method_select.hpp | 67 +++++++---- .../rublon/method/passcode_based_auth.hpp | 16 +-- .../rublon/method/websocket_based_auth.hpp | 13 ++- PAM/ssh/include/rublon/non_owning_ptr.hpp | 2 + PAM/ssh/include/rublon/sign.hpp | 23 ++++ PAM/ssh/include/rublon/static_string.hpp | 47 +++++++- PAM/ssh/include/rublon/utils.hpp | 36 +++--- PAM/ssh/include/rublon/websockets.hpp | 4 +- PAM/ssh/lib/CMakeLists.txt | 16 +-- PAM/ssh/lib/pam.cpp | 8 +- pack.cmake | 9 +- rsc/rublon.config.defaults | 4 +- service/01-rublon-ssh.conf.default | 4 + service/postinst | 32 +++--- service/postrm | 23 ++++ 23 files changed, 336 insertions(+), 120 deletions(-) create mode 100644 service/01-rublon-ssh.conf.default create mode 100644 service/postrm diff --git a/CMakeLists.txt b/CMakeLists.txt index fbfcd79..76fad68 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ include(GNUInstallDirs) set(PROJECT_VERSION_MAJOR 2) set(PROJECT_VERSION_MINOR 0) -set(PROJECT_VERSION_PATCH 1) +set(PROJECT_VERSION_PATCH 0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED YES) @@ -16,7 +16,6 @@ set(CMAKE_CXX_EXTENSIONS NO) add_compile_options(-Wall -Wextra -Wpedantic -Wno-format-security) option(ENABLE_TESTS "Enable tests" OFF) - add_custom_target(CONFIG_IDE SOURCES ${CMAKE_CURRENT_LIST_DIR}/rsc/rublon.config.defaults) add_custom_target(INSTSCRIPTS_IDE SUORCES ${CMAKE_CURRENT_LIST_DIR}/service/postinst) @@ -26,6 +25,7 @@ add_custom_target(INSTSCRIPTS_IDE SUORCES ${CMAKE_CURRENT_LIST_DIR}/service/post install( FILES ${CMAKE_CURRENT_LIST_DIR}/rsc/rublon.config.defaults + ${CMAKE_CURRENT_LIST_DIR}/service/01-rublon-ssh.conf.default DESTINATION share/rublon COMPONENT @@ -41,5 +41,4 @@ if (${ENABLE_TESTS}) endif() add_subdirectory(PAM/ssh) - include(pack.cmake) diff --git a/PAM/ssh/CMakeLists.txt b/PAM/ssh/CMakeLists.txt index 664fdf6..84c13ea 100755 --- a/PAM/ssh/CMakeLists.txt +++ b/PAM/ssh/CMakeLists.txt @@ -22,7 +22,7 @@ set(INC ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/websockets.hpp ) -add_library(rublon-ssh +add_library(rublon-ssh-ifc INTERFACE ) @@ -31,7 +31,7 @@ if(${CMAKE_VERSION} VERSION_GREATER "3.19.0") add_library(rublon-ssh_ide INTERFACE ${INC}) endif() -target_include_directories(rublon-ssh +target_include_directories(rublon-ssh-ifc INTERFACE extern ${CMAKE_CURRENT_LIST_DIR}/include diff --git a/PAM/ssh/include/rublon/check_application.hpp b/PAM/ssh/include/rublon/check_application.hpp index b15c629..72abc70 100644 --- a/PAM/ssh/include/rublon/check_application.hpp +++ b/PAM/ssh/include/rublon/check_application.hpp @@ -1,29 +1,54 @@ #pragma once - #include #include #include #include +#include #include +#include #include +#include #include namespace rublon { +std::string exec(const char * cmd) { + std::array< char, 128 > buffer; + std::string result; + std::unique_ptr< FILE, decltype(&pclose) > pipe(popen(cmd, "r"), pclose); + if(!pipe) { + return ""; + } + while(fgets(buffer.data(), static_cast< int >(buffer.size()), pipe.get()) != nullptr) { + result += buffer.data(); + } + return result; +} + +std::map< std::string, std::string > getSSHDConfig() { + std::istringstream iss(exec("sshd -T")); + std::map< std::string, std::string > result; + for(std::string line; std::getline(iss, line);) { + auto first_token = line.substr(0, line.find(' ')); + result[first_token] = line.substr(line.find(' ') + 1); + } + return result; +} + class Status { std::string_view _statusDirPath = "/var/lib/rublon"; std::string_view _statusFilePath = "/var/lib/rublon/install.json"; - RapidJSONPMRStackAlloc< 4 * 1024 > _alloc; + RapidJSONPMRStackAlloc< 8 * 1024 > _alloc; Document _data; bool _statusUpdated; - std::string_view _appVersionKey = "/appVer"; - std::string_view _appTypeKey = "/type"; - std::string_view _paramSystemName = "/params/os"; - std::string_view _paramSSHDUsePamName = "/params/sshd_config/usePam"; + std::string_view _appVersionKey = "/appVer"; + std::string_view _appTypeKey = "/type"; + std::string_view _paramSystemName = "/params/os"; + std::string_view _paramSSHDBase = "/params/sshd_config/"; public: Status() : _data{&_alloc} { @@ -44,31 +69,68 @@ class Status { void updateAppVersion(std::string_view newVersion) { RapidJSONPMRStackAlloc< 128 > stackAlloc; - auto version = JSONPointer{_appVersionKey.data(), &stackAlloc}.Get(_data); + auto jsonPointer = JSONPointer{_appVersionKey.data(), &stackAlloc}; + auto version = jsonPointer.Get(_data); if(not version || version->GetString() != newVersion) { - _statusUpdated = true; - auto version = Value{newVersion.data(), _data.GetAllocator()}; - JSONPointer{_appVersionKey.data(), &stackAlloc}.Set(_data, version); + markUpdated(); + jsonPointer.Set(_data, Value{newVersion.data(), _data.GetAllocator()}); } } void updateSystemVersion(std::string_view system) { RapidJSONPMRStackAlloc< 128 > stackAlloc; - auto version = JSONPointer{_paramSystemName.data(), &stackAlloc}.Get(_data); + auto jsonPointer = JSONPointer{_paramSystemName.data(), &stackAlloc}; + auto version = jsonPointer.Get(_data); if(not version || version->GetString() != system) { - _statusUpdated = true; - auto version = Value{system.data(), _data.GetAllocator()}; - JSONPointer{_paramSystemName.data(), &stackAlloc}.Set(_data, version); + markUpdated(); + jsonPointer.Set(_data, Value{system.data(), _data.GetAllocator()}); } } + void updateSSHDConfig() { + using namespace std::string_view_literals; + constexpr auto keys = make_array("authenticationmethods"sv, + "challengeresponseauthentication"sv, + "kbdinteractiveauthentication"sv, + "logingracetime"sv, + "maxauthtries"sv, + "passwordauthentication"sv, + "permitemptypasswords"sv, + "permitrootlogin"sv, + "pubkeyauthentication"sv, + "usepam"sv); + + auto config = getSSHDConfig(); + + for(const auto key : keys) { + auto [currentPair, inserted] = config.try_emplace(std::string{key.data()}, "N/A"); + auto & [currentKey, currentValue] = *currentPair; + + const auto jsonPath = std::string{_paramSSHDBase.data()} + key.data(); + + RapidJSONPMRStackAlloc< 512 > stackAlloc; + auto jsonPointer = JSONPointer{jsonPath.c_str(), &stackAlloc}; + + auto oldValue = jsonPointer.Get(_data); + + if(not oldValue || oldValue->GetString() != currentValue) { + _statusUpdated = true; + jsonPointer.Set(_data, Value{currentValue.c_str(), _data.GetAllocator()}); + } + } + } + + void markUpdated() { + _statusUpdated = true; + } + bool updated() const { return _statusUpdated; } void save() { if(updated()) { - memory::Monotonic_1k_HeapResource tmpResource; + memory::Monotonic_8k_HeapResource tmpResource; RapidJSONPMRAlloc alloc{&tmpResource}; FileWriter s{_statusFilePath}; rapidjson::PrettyWriter< FileWriter, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc}; @@ -77,6 +139,18 @@ class Status { } } + std::string print() { + std::string result; + memory::Monotonic_8k_HeapResource tmpResource; + RapidJSONPMRAlloc alloc{&tmpResource}; + StringWriter s{result}; + rapidjson::PrettyWriter< StringWriter< std::string >, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc}; + writer.SetIndent(' ', 2); + _data.Accept(writer); + + return result; + } + Document & data() { return _data; } @@ -99,8 +173,9 @@ class CheckApplication { const auto persist = [&](const auto /*ok*/) { return this->persistStatus(status); }; - status.updateAppVersion("2.0.2"); + status.updateAppVersion("2.0.0"); status.updateSystemVersion(details::osName(&mr)); + status.updateSSHDConfig(); if(status.updated()) { auto & alloc = status.data().GetAllocator(); diff --git a/PAM/ssh/include/rublon/configuration.hpp b/PAM/ssh/include/rublon/configuration.hpp index 26d6d7c..4835def 100755 --- a/PAM/ssh/include/rublon/configuration.hpp +++ b/PAM/ssh/include/rublon/configuration.hpp @@ -64,9 +64,9 @@ namespace { template <> auto to(std::string_view arg) -> tl::expected< FailMode, ConfigurationError > { - if(arg == "safe" || "bypass") + if(arg == "bypass") return FailMode::bypass; - if(arg == "secure" || arg == "deny") + if(arg == "deny") return FailMode::deny; return tl::unexpected{ConfigurationError{ConfigurationError::ErrorClass::BadFailMode}}; } diff --git a/PAM/ssh/include/rublon/error.hpp b/PAM/ssh/include/rublon/error.hpp index cbf5aa4..827ac7c 100755 --- a/PAM/ssh/include/rublon/error.hpp +++ b/PAM/ssh/include/rublon/error.hpp @@ -117,6 +117,7 @@ class WerificationError { class RublonAuthenticationInterrupt { public: + // UserPending -> user has no methods configured enum ErrorClass { UserBaypass, UserDenied, UserPending, UserWaiting, UserNotFound }; constexpr static auto errorClassPrettyName = make_array("UserBypassedException", "UserDenied", "UserPending", "UserWaiting", "UserNotFoundException"); diff --git a/PAM/ssh/include/rublon/init.hpp b/PAM/ssh/include/rublon/init.hpp index 1021209..ec1c2aa 100755 --- a/PAM/ssh/include/rublon/init.hpp +++ b/PAM/ssh/include/rublon/init.hpp @@ -31,8 +31,8 @@ class Init : public AuthenticationStep { tl::expected< MethodSelect_t, Error > createMethod(const Document & coreResponse) const { const auto & rublonResponse = coreResponse["result"]; std::string tid = rublonResponse["tid"].GetString(); - g_tid = tid; - return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"], _session.config().prompt}; + g_tid = tid; ///TODO set tid in session + return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"], _session.config().prompt, _session.config().autopushPrompt}; } template < typename PamInfo_t > @@ -58,7 +58,7 @@ class Init : public AuthenticationStep { Value params{rapidjson::kObjectType}; params.AddMember("userIP", ip, alloc); - params.AddMember("appVer", "2.0.2", alloc); /// TODO add version to cmake + params.AddMember("appVer", "2.0.0", alloc); /// TODO add version to cmake params.AddMember("os", osNamePretty, alloc); coreRequest.AddMember("params", std::move(params), alloc); @@ -68,16 +68,21 @@ class Init : public AuthenticationStep { tl::expected< std::reference_wrapper< const Document >, Error > checkEnrolement(const Document & coreResponse, const Pam_T pam) const { using namespace std::string_view_literals; const auto & resp = coreResponse; - + + ///TODO refactor this 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); - if((status == "pending"sv || status == "waiting"sv) and resp["result"].HasMember("webURI")) { - const auto & weburi = resp["result"]["webURI"].GetString(); - pam.print("Visit %s", weburi); - return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserPending}}}; + if(status == "pending"sv ) { + if(resp["result"].HasMember("webURI")){ + const auto & weburi = resp["result"]["webURI"].GetString(); + pam.print("Visit %s", weburi); + } } - if(status == "denied"sv) { + else if(status == "waiting"sv){ + return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserWaiting}}}; + } + else if(status == "denied"sv) { return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserDenied}}}; } } @@ -86,7 +91,7 @@ class Init : public AuthenticationStep { } public: - const char * name = "Initialization"; + const char * _name = "Initialization"; const Session & _session; diff --git a/PAM/ssh/include/rublon/method/OTP.hpp b/PAM/ssh/include/rublon/method/OTP.hpp index 4f08300..8c5e60b 100755 --- a/PAM/ssh/include/rublon/method/OTP.hpp +++ b/PAM/ssh/include/rublon/method/OTP.hpp @@ -17,7 +17,7 @@ class OTP : public PasscodeBasedAuth { std::move(tid), "", "Mobile Passcode", - "Enter the passcode from the Rublon Authenticator mobile app: ", + "Enter the passcode from the Authenticator app: ", 6, true, PasscodeBasedAuth::Endpoint::ConfirmCode, diff --git a/PAM/ssh/include/rublon/method/PUSH.hpp b/PAM/ssh/include/rublon/method/PUSH.hpp index 5eefece..aca5e8c 100755 --- a/PAM/ssh/include/rublon/method/PUSH.hpp +++ b/PAM/ssh/include/rublon/method/PUSH.hpp @@ -12,8 +12,8 @@ namespace rublon::method { class PUSH : public WebsocketBasedAuth { public: - PUSH(std::string systemToken, std::string tid) - : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "Mobile PUSH") {} + PUSH(std::string systemToken, std::string tid, bool autopush) + : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "Mobile PUSH", autopush) {} }; } // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/method_select.hpp b/PAM/ssh/include/rublon/method/method_select.hpp index 0bc585c..95f36ca 100755 --- a/PAM/ssh/include/rublon/method/method_select.hpp +++ b/PAM/ssh/include/rublon/method/method_select.hpp @@ -1,11 +1,15 @@ #pragma once #include +#include +#include +#include + #include -#include - #include +#include +#include #include #include @@ -21,7 +25,6 @@ extern std::string g_tid; namespace rublon { - class MethodProxy { public: template < typename Method_t > @@ -32,7 +35,7 @@ class MethodProxy { coreHandler.createWSConnection(g_tid); return std::visit( [&](const auto & method) { - rublon::log(LogLevel::Info, "Using '%s' method", method.name); + log(LogLevel::Info, "Using '%s' method", method._name); return method.verify(coreHandler, pam); }, _impl); @@ -47,6 +50,7 @@ class PostMethod : public AuthenticationStep { const char * uri = "/api/transaction/methodSSH"; std::string _method; int _prompts; + bool _autopushPrompt; tl::expected< MethodProxy, Error > createMethod(const Document & coreResponse) const { const auto & rublonResponse = coreResponse["result"]; @@ -58,9 +62,9 @@ class PostMethod : public AuthenticationStep { } else if(_method == "sms") { return MethodProxy{method::SMS{this->_systemToken, std::move(tid), _prompts}}; } else if(_method == "push") { - return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}}; + return MethodProxy{method::PUSH{this->_systemToken, std::move(tid), _autopushPrompt}}; } else if(_method == "email") { - return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}}; + return MethodProxy{method::EMAIL{this->_systemToken, std::move(tid)}}; } else if(_method == "smsLink") { return MethodProxy{method::SmsLink{this->_systemToken, std::move(tid)}}; } else if(_method == "yotp") { @@ -75,10 +79,10 @@ class PostMethod : public AuthenticationStep { } public: - const char * name = "Confirm Method"; + const char * _name = "Confirm Method"; - PostMethod(std::string systemToken, std::string tid, std::string method, int prompts) - : base_t(std::move(systemToken), std::move(tid)), _method{method}, _prompts{prompts} {} + PostMethod(std::string systemToken, std::string tid, std::string method, int prompts, bool autopushPrompt) + : base_t(std::move(systemToken), std::move(tid)), _method{method}, _prompts{prompts}, _autopushPrompt{autopushPrompt} {} template < typename Hander_t, typename PamInfo_t = LinuxPam > tl::expected< MethodProxy, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const { @@ -102,19 +106,29 @@ class MethodSelect { std::string _accessToken; std::string _tid; int _prompts; + bool _autopushPrompt; - std::vector< std::string > _methods; + std::vector< std::string > _methodsAvailable; public: template < typename Array_t > - MethodSelect(std::string systemToken, std::string tid, const Array_t & methodsAvailableForUser, int prompts) - : _systemToken{std::move(systemToken)}, _tid{std::move(tid)}, _prompts{prompts} { - _methods.reserve(std::size(methodsAvailableForUser)); - std::transform( - std::begin(methodsAvailableForUser), std::end(methodsAvailableForUser), std::back_inserter(_methods), [](const auto & method) { - return method.GetString(); - }); - rublon::log(LogLevel::Debug, "User has %d methods available", _methods.size()); + MethodSelect(std::string systemToken, std::string tid, const Array_t & methodsAvailableForUser, int prompts, bool autopushPrompt) + : _systemToken{std::move(systemToken)}, _tid{std::move(tid)}, _prompts{prompts}, _autopushPrompt{autopushPrompt} { + using namespace std::string_view_literals; + memory::MonotonicStackResource< 2024 > stackResource; + std::pmr::vector< std::string_view > _methods; + _methodsAvailable.reserve(std::size(methodsAvailableForUser)); + + std::pmr::set< std::string_view > methodsSupported{{"totp"sv, "email"sv, "yotp"sv, "sms"sv, "push"sv, "smsLink"sv}, &stackResource}; + + transform_if( + std::begin(methodsAvailableForUser), + std::end(methodsAvailableForUser), + std::back_inserter(_methodsAvailable), + [&](const auto & method) { return method.GetString(); }, + [&](const auto & method) { return methodsSupported.find(method.GetString()) != methodsSupported.end(); }); + + rublon::log(LogLevel::Debug, "User has %d methods available", _methodsAvailable.size()); } template < typename Pam_t > @@ -122,9 +136,14 @@ class MethodSelect { rublon::log(LogLevel::Debug, "prompting user to select method"); memory::StrictMonotonic_4k_HeapResource memoryResource; std::pmr::map< int, std::string > methods_id{&memoryResource}; - std::pmr::map< int, std::string > methods_names{&memoryResource}; + std::pmr::map< int, std::pmr::string > methods_names{&memoryResource}; int prompts = _prompts; + if(_methodsAvailable.size() == 0) { + log(LogLevel::Warning, "None of provided methods are supported by the connector"); + return tl::unexpected(MethodError(MethodError::ErrorClass::NoMethodAvailable)); + } + pam.print("Select the authentication method to verify your identity: "); auto logMethodAvailable = [](auto & method) { // @@ -136,12 +155,12 @@ class MethodSelect { auto printAvailableMethods = [&]() -> tl::expected< int, MethodError > { int i{}; - for(const auto & method : _methods) { + for(const auto & method : _methodsAvailable) { if(method == "totp") { logMethodAvailable(method); - pam.print("%d: Mobile Passcode", i + 1); + pam.print("%d: Passcode", i + 1); methods_id[++i] = method; - methods_names[i] = "Mobile Passcode"; + methods_names[i] = "Passcode"; continue; } @@ -203,11 +222,11 @@ class MethodSelect { auto hasMethod = methods_id.find(methodid) != methods_id.end(); // pam.print("\t selected: %s", hasMethod ? methods_id.at(methodid).c_str() : "unknown option"); if(!hasMethod) { - log(LogLevel::Error, "User selected option %d, which is not corrent", methodid); + log(LogLevel::Error, "User selected option %d, which is not correct", methodid); return tl::unexpected{MethodError(MethodError::BadMethod)}; } else { log(LogLevel::Info, "User selected option %d{%s}", methodid, methods_names.at(methodid).c_str()); - return PostMethod{_systemToken, _tid, methods_id.at(methodid), _prompts}; + return PostMethod{_systemToken, _tid, methods_id.at(methodid), _prompts, _autopushPrompt}; } }; diff --git a/PAM/ssh/include/rublon/method/passcode_based_auth.hpp b/PAM/ssh/include/rublon/method/passcode_based_auth.hpp index 53dd05d..49d56e1 100755 --- a/PAM/ssh/include/rublon/method/passcode_based_auth.hpp +++ b/PAM/ssh/include/rublon/method/passcode_based_auth.hpp @@ -20,7 +20,9 @@ class PasscodeBasedAuth : public AuthenticationStep { static constexpr const char * fieldVericode = "vericode"; static constexpr const char * fieldOtp = "otp"; - + + static constexpr auto _bypassCodeLength = 9; + const char * userMessage{nullptr}; const uint_fast8_t length; @@ -36,11 +38,11 @@ class PasscodeBasedAuth : public AuthenticationStep { } bool hasValidLength(std::string_view userInput) const { - if(userInput.size() == length) { + if(userInput.size() == length || userInput.size() == _bypassCodeLength) { 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); + log(LogLevel::Warning, "User input size %d is different than %d", userInput.size(), length); return false; } } @@ -50,7 +52,7 @@ class PasscodeBasedAuth : public AuthenticationStep { log(LogLevel::Debug, "User input contains valid characters"); return true; } else { - log(LogLevel::Warning, "User input contains characters different then digits"); + log(LogLevel::Warning, "User input contains characters different than digits"); return false; } } @@ -101,7 +103,7 @@ class PasscodeBasedAuth : public AuthenticationStep { } public: - const char * name; + const char * _name; std::string token; enum class Endpoint { ConfirmCode, SecurityKeySSH }; @@ -109,7 +111,7 @@ class PasscodeBasedAuth : public AuthenticationStep { PasscodeBasedAuth(std::string systemToken, std::string tid, std::string token, - const char * name, + const char * _name, const char * userMessage, uint_fast8_t length, @@ -123,7 +125,7 @@ class PasscodeBasedAuth : public AuthenticationStep { length{length}, onlyDigits{numbersOnly}, _prompts{prompts}, - name{name}, + _name{_name}, token{std::move(token)} {} template < typename Hander_t, typename PamInfo_t = LinuxPam > diff --git a/PAM/ssh/include/rublon/method/websocket_based_auth.hpp b/PAM/ssh/include/rublon/method/websocket_based_auth.hpp index edf0c0e..e714106 100755 --- a/PAM/ssh/include/rublon/method/websocket_based_auth.hpp +++ b/PAM/ssh/include/rublon/method/websocket_based_auth.hpp @@ -13,16 +13,21 @@ namespace rublon::method { class WebsocketBasedAuth : public AuthenticationStep { public: - const char * name = ""; + const char * _name = ""; + const bool _autopushPrompt = true; - WebsocketBasedAuth(std::string systemToken, std::string tid, const char * name) - : AuthenticationStep(std::move(systemToken), std::move(tid)), name{name} {} + WebsocketBasedAuth(std::string systemToken, std::string tid, const char * name, bool autopushPrompt = true) + : AuthenticationStep(std::move(systemToken), std::move(tid)), _name{name}, _autopushPrompt{autopushPrompt} {} template < typename Hander_t, typename PamInfo_t = LinuxPam > tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const { log(LogLevel::Info, "starting WS"); auto listener = coreHandler.listen(); - pam.scan([](const auto/*ignored userinput*/){return "";}, "Rublon authentication initiated. Complete the authentication and press Enter to proceed"); + if(not _autopushPrompt) + pam.print("Autopush"); + else + pam.scan([](const auto /*ignored userinput*/) { return ""; }, + "Rublon authentication initiated. Complete the authentication and press Enter to proceed"); return listener->waitForEvent(); } }; diff --git a/PAM/ssh/include/rublon/non_owning_ptr.hpp b/PAM/ssh/include/rublon/non_owning_ptr.hpp index 9412436..4fa6a8a 100755 --- a/PAM/ssh/include/rublon/non_owning_ptr.hpp +++ b/PAM/ssh/include/rublon/non_owning_ptr.hpp @@ -1,5 +1,7 @@ #pragma once +#include + namespace rublon { template < typename T > diff --git a/PAM/ssh/include/rublon/sign.hpp b/PAM/ssh/include/rublon/sign.hpp index afae01c..7d61362 100755 --- a/PAM/ssh/include/rublon/sign.hpp +++ b/PAM/ssh/include/rublon/sign.hpp @@ -1,13 +1,36 @@ #pragma once #include +#include #include #include #include +#include + +#include namespace rublon { +inline std::array< char, SHA256_DIGEST_LENGTH * 2 + 1 > SHA256(const char * const path) { + std::string fileContent; + readFile(path, fileContent); + + std::array< char, SHA256_DIGEST_LENGTH * 2 + 1 > xRublon{}; + std::array< unsigned char, SHA256_DIGEST_LENGTH + 1 > hash{}; + + SHA256_CTX ctx; + SHA256_Init(&ctx); + + SHA256_Update(&ctx, fileContent.data(), fileContent.size()); + SHA256_Final(hash.data(), &ctx); + + for(unsigned int i = 0; i < SHA256_DIGEST_LENGTH; i++) + sprintf(&xRublon[i * 2], "%02x", ( unsigned int ) hash[i]); + + return xRublon; +} + // +1 for \0 inline std::array< char, 64 + 1 > signData(std::string_view data, std::string_view secretKey) { std::array< char, 64 + 1 > xRublon; diff --git a/PAM/ssh/include/rublon/static_string.hpp b/PAM/ssh/include/rublon/static_string.hpp index 43542f0..1d79624 100644 --- a/PAM/ssh/include/rublon/static_string.hpp +++ b/PAM/ssh/include/rublon/static_string.hpp @@ -3,12 +3,41 @@ #include #include #include +#include + +template +constexpr std::array toStdArray(T (&arr)[N], std::index_sequence) +{ + return {arr[Idx]...}; +} + +template +constexpr std::array resize(const std::array& arr, std::index_sequence) +{ + return {arr[Indexes]...}; +} + +template +constexpr std::array resize(const std::array& arr) +{ + constexpr std::size_t minSize = std::min(OldSize, NewSize); + return resize(arr, std::make_index_sequence()); +} // statically allocates a string buffer of (N+1) chars template < size_t N > class StaticString { public: constexpr StaticString() = default; + constexpr StaticString(const char (&chars)[N]) + : m_str(toStdArray(chars)) + { + } + + constexpr StaticString(std::array chars) + : m_str(std::move(chars)) + { + } constexpr StaticString(const char * str) { std::strncpy(m_str.data(), str, N); } @@ -18,18 +47,26 @@ class StaticString { } const char * c_str() const noexcept { - return &m_str[0]; + return m_str.data(); } - + const char * data() const noexcept { - return &m_str[0]; + return m_str.data(); } - std::size_t size() const { return strlen(m_str.data()); } - + + template + constexpr StaticString operator+(const StaticString &rhs) const + { + return join(resize(m_str), rhs.m_str); + } + + template + friend class StaticString; private: std::array< char, N + 1 > m_str{}; }; + diff --git a/PAM/ssh/include/rublon/utils.hpp b/PAM/ssh/include/rublon/utils.hpp index 51cb751..d85f525 100755 --- a/PAM/ssh/include/rublon/utils.hpp +++ b/PAM/ssh/include/rublon/utils.hpp @@ -1,6 +1,7 @@ #pragma once #include "tl/expected.hpp" + #include #include #include @@ -13,8 +14,6 @@ #include #include -#include - #include #include #include @@ -56,11 +55,10 @@ inline auto dateStr() { } constexpr const char * LogLevelNames[]{"Debug", "Info", "Warning", "Error"}; -LogLevel g_level = LogLevel::Debug; +LogLevel g_level = LogLevel::Debug; constexpr bool syncLogFile = true; static const char * application = ""; - // #include // #include // #include @@ -87,23 +85,22 @@ static const char * application = ""; // int file_descript; // unsigned long file_size; // char* file_buffer; - + // file_descript = open(filename, O_RDONLY); // if(file_descript < 0) exit(-1); - + // file_size = get_size_by_fd(file_descript); // printf("file size:\t%lu\n", file_size); - + // file_buffer =(char*)mmap(nullptr, file_size, PROT_READ, MAP_SHARED, file_descript, 0); // MD5((unsigned char*) file_buffer, file_size, result); -// munmap(file_buffer, file_size); - +// munmap(file_buffer, file_size); + // return 0; // } namespace details { - - + std::pmr::string osName(std::pmr::memory_resource * mr) { memory::MonotonicStackResource< 8 * 1024 > stackResource; @@ -223,15 +220,15 @@ class PrintUser { namespace conv { enum class Error { OutOfRange, NotANumber }; - + inline bool to_bool(std::string_view userinput) { if(userinput.size() > 16) { // todo return optional return false; } - std::array< char, 16 >buf{}; + std::array< char, 16 > buf{}; auto asciitolower = [](char in) { return in - ((in <= 'Z' && in >= 'A') ? ('Z' - 'z') : 0); }; - + std::transform(userinput.cbegin(), userinput.cend(), buf.data(), asciitolower); return strcmp(buf.data(), "true") == 0; } @@ -285,4 +282,15 @@ namespace details { } // namespace details +template < class InputIterator, class OutputIterator, class UnaryOperator, class Pred > +OutputIterator transform_if(InputIterator first1, InputIterator last1, OutputIterator result, UnaryOperator op, Pred pred) { + while(first1 != last1) { + if(pred(*first1)) { + *result = op(*first1); + ++result; + } + ++first1; + } + return result; +} } // namespace rublon diff --git a/PAM/ssh/include/rublon/websockets.hpp b/PAM/ssh/include/rublon/websockets.hpp index 75cb2d5..4f8dee1 100755 --- a/PAM/ssh/include/rublon/websockets.hpp +++ b/PAM/ssh/include/rublon/websockets.hpp @@ -212,7 +212,9 @@ class WebSocket { sio::client ws; public: - WebSocket() = default; + WebSocket(){ + ws.set_logs_quiet(); + }; ~WebSocket() = default; std::shared_ptr< WebSocketSingleShotEventListener > listen() { diff --git a/PAM/ssh/lib/CMakeLists.txt b/PAM/ssh/lib/CMakeLists.txt index 6f31017..aa21e87 100755 --- a/PAM/ssh/lib/CMakeLists.txt +++ b/PAM/ssh/lib/CMakeLists.txt @@ -1,26 +1,26 @@ -add_library(rublon-ssh-pam +add_library(rublon-ssh SHARED pam.cpp ) -set_target_properties(rublon-ssh-pam PROPERTIES PREFIX "") -set_target_properties(rublon-ssh-pam PROPERTIES OUTPUT_NAME "pam_rublon") +set_target_properties(rublon-ssh PROPERTIES PREFIX "") +set_target_properties(rublon-ssh PROPERTIES OUTPUT_NAME "pam_rublon") -target_compile_options(rublon-ssh-pam +target_compile_options(rublon-ssh PUBLIC -flto -Wno-deprecated-declarations ) -target_link_options(rublon-ssh-pam +target_link_options(rublon-ssh PUBLIC -fpic -flto ) -target_link_libraries(rublon-ssh-pam +target_link_libraries(rublon-ssh PUBLIC - rublon-ssh + rublon-ssh-ifc -lcurl -lssl -lcrypto @@ -42,7 +42,7 @@ endif() install( TARGETS - rublon-ssh-pam + rublon-ssh DESTINATION ${_destination} COMPONENT diff --git a/PAM/ssh/lib/pam.cpp b/PAM/ssh/lib/pam.cpp index b41f3a7..c439900 100755 --- a/PAM/ssh/lib/pam.cpp +++ b/PAM/ssh/lib/pam.cpp @@ -58,6 +58,10 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu if(not session.has_value()) { return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); } + + if(!session->config().logging){ + g_level = LogLevel::Warning; + } auto & CH = session.value().coreHandler(); @@ -89,7 +93,7 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu case RublonAuthenticationInterrupt::ErrorClass::UserWaiting: case RublonAuthenticationInterrupt::ErrorClass::UserPending: pam.print( - "Your account is awaiting administrator's approval. \n" + "Your account is awaiting administrator's approval.\n" "Contact your administrator and ask them to approve your account"); return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); case RublonAuthenticationInterrupt::ErrorClass::UserNotFound: @@ -104,7 +108,7 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu case MethodError::ErrorClass::BadUserInput: return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); case MethodError::ErrorClass::NoMethodAvailable: - return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); + return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); } } diff --git a/pack.cmake b/pack.cmake index 6b8b484..5d4b447 100755 --- a/pack.cmake +++ b/pack.cmake @@ -1,5 +1,5 @@ # these are cache variables, so they could be overwritten with -D, -set(CPACK_PACKAGE_NAME ${PROJECT_NAME} +set(CPACK_PACKAGE_NAME rublon-ssh CACHE STRING "The resulting package name" ) @@ -17,6 +17,7 @@ set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) set(CPACK_PACKAGE_CONTACT "bwi@rublon.com") +set(CPACK_DEBIAN_PAM_PACKAGE_NAME rublon-ssh) set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Bartosz Wieczorek") #set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") @@ -28,8 +29,12 @@ set(CPACK_DEB_COMPONENT_INSTALL YES) set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS YES) set(CPACK_GENERATOR "DEB") +#set(CPACK_GENERATOR "RPM") +#set(CPACK_RPM_SPEC_MORE_DEFINE "%define _build_id_links none") +#set(CPACK_RPM_FILE_NAME RPM-DEFAULT) # set(CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "libcurl4(>= 7.0.0), libc(>= 2.0)") # set(CPACK_DEBIAN_PACKAGE_DEPENDS "libcurl4(>= 7.0.0), libc(>= 2.0), libssl(>= 1.0)") -set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/service/postinst") +set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA + "${CMAKE_CURRENT_SOURCE_DIR}/service/postinst;${CMAKE_CURRENT_SOURCE_DIR}/service/postrm") include(CPack) diff --git a/rsc/rublon.config.defaults b/rsc/rublon.config.defaults index 583f45e..b651134 100755 --- a/rsc/rublon.config.defaults +++ b/rsc/rublon.config.defaults @@ -1,9 +1,7 @@ systemToken= secretKey= -userDomain= rublonApiServer=https://core.rublon.net -failMode=safe +failMode=deny prompt=1 logging=true -enablePasswdEmail=true autopushPrompt=false diff --git a/service/01-rublon-ssh.conf.default b/service/01-rublon-ssh.conf.default new file mode 100644 index 0000000..44faa24 --- /dev/null +++ b/service/01-rublon-ssh.conf.default @@ -0,0 +1,4 @@ +UsePAM yes +PasswordAuthentication yes +ChallengeResponseAuthentication yes +#KbdInteractiveAuthentication no diff --git a/service/postinst b/service/postinst index 1bd2921..c2be946 100755 --- a/service/postinst +++ b/service/postinst @@ -3,32 +3,36 @@ SSHD_CONF=/etc/ssh/sshd_config SSHD_PAM_CONF=/etc/pam.d/sshd RUBLON_CONFIG=/etc/rublon.config +RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf -if [ ! -f /etc/rublon.config ] +if [ ! -f $RUBLON_CONFIG ] then cp -a /usr/share/rublon/rublon.config.defaults $RUBLON_CONFIG chown root:root $RUBLON_CONFIG chmod 640 $RUBLON_CONFIG fi +if [ ! -f $RUBLON_SSH_CONFIG ] +then + cp -a /usr/share/rublon/01-rublon-ssh.conf.default $RUBLON_SSH_CONFIG + chown root:root $RUBLON_SSH_CONFIG + chmod 640 $RUBLON_SSH_CONFIG +fi + if [ -f /etc/os-release ] then . /etc/os-release fi -grep -qe "^PasswordAuthentication" $SSHD_CONF && \ - sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || \ - echo "PasswordAuthentication yes" >> $SSHD_CONF - -grep -qe "^ChallengeResponseAuthentication" $SSHD_CONF && \ - sed -i 's/^#*ChallengeResponseAuthentication[[:space:]]\+.*/ChallengeResponseAuthentication yes/' $SSHD_CONF || \ - echo "ChallengeResponseAuthentication yes" >> $SSHD_CONF - -grep -qe "^UsePAM" $SSHD_CONF && \ - sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || \ - echo "UsePAM yes" >> $SSHD_CONF - -sed -i 's/KbdInteractiveAuthentication/#KbdInteractiveAuthentication/' $SSHD_CONF +#if [ $ID == "rhel" ] +#then +# cd /home/vagrant/Rublon-Linux/service +# checkmodule -M -m -o login_rublon.mod login_rublon.te +# semodule_package -o login_rublon.pp -m login_rublon.mod +# semodule -i login_rublon.pp +#fi 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 + +deb-systemd-invoke restart ssh.service diff --git a/service/postrm b/service/postrm new file mode 100644 index 0000000..8cf958d --- /dev/null +++ b/service/postrm @@ -0,0 +1,23 @@ +#!/bin/bash + +RUBLON_CONFIG=/etc/rublon.config +RUBLON_SSH_CONFIG=/etc/ssh/sshd_config.d/01-rublon-ssh.conf +SSHD_PAM_CONF=/etc/pam.d/sshd + +if [ $1 == 'purge' ] +then + if [ -f $RUBLON_CONFIG ] + then + rm $RUBLON_CONFIG + fi + + if [ -f $RUBLON_SSH_CONFIG ] + then + rm $RUBLON_SSH_CONFIG + fi +fi + +sed -i '/auth required pam_rublon.so/d' $SSHD_PAM_CONF +sed -i '/account required pam_rublon.so/d' $SSHD_PAM_CONF + +deb-systemd-invoke restart ssh.service