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
This commit is contained in:
parent
9415174eba
commit
6a3882fa47
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,29 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <rapidjson/prettywriter.h>
|
||||
#include <rapidjson/rapidjson.h>
|
||||
|
||||
#include <rublon/core_handler.hpp>
|
||||
#include <rublon/curl.hpp>
|
||||
#include <rublon/error.hpp>
|
||||
#include <rublon/json.hpp>
|
||||
#include <rublon/memory.hpp>
|
||||
#include <rublon/utils.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
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();
|
||||
|
||||
@ -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}};
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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);
|
||||
@ -69,15 +69,20 @@ class Init : public AuthenticationStep {
|
||||
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;
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include <rublon/core_handler.hpp>
|
||||
#include <rublon/error.hpp>
|
||||
#include <rublon/memory.hpp>
|
||||
#include <rublon/pam.hpp>
|
||||
#include <rublon/pam_action.hpp>
|
||||
|
||||
@ -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};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -21,6 +21,8 @@ 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 >
|
||||
|
||||
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace rublon {
|
||||
|
||||
template < typename T >
|
||||
|
||||
@ -1,13 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <rublon/utils.hpp>
|
||||
|
||||
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;
|
||||
|
||||
@ -3,12 +3,41 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
template <typename T, std::size_t N, std::size_t... Idx>
|
||||
constexpr std::array<T, N> toStdArray(T (&arr)[N], std::index_sequence<Idx...>)
|
||||
{
|
||||
return {arr[Idx]...};
|
||||
}
|
||||
|
||||
template <std::size_t NewSize, typename T, std::size_t OldSize, std::size_t... Indexes>
|
||||
constexpr std::array<T, NewSize> resize(const std::array<T, OldSize>& arr, std::index_sequence<Indexes...>)
|
||||
{
|
||||
return {arr[Indexes]...};
|
||||
}
|
||||
|
||||
template <std::size_t NewSize, typename T, std::size_t OldSize>
|
||||
constexpr std::array<T, NewSize> resize(const std::array<T, OldSize>& arr)
|
||||
{
|
||||
constexpr std::size_t minSize = std::min(OldSize, NewSize);
|
||||
return resize(arr, std::make_index_sequence<minSize>());
|
||||
}
|
||||
|
||||
// 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<const char, N> 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 <std::size_t M>
|
||||
constexpr StaticString<N + M - 1> operator+(const StaticString<M> &rhs) const
|
||||
{
|
||||
return join(resize<N-1>(m_str), rhs.m_str);
|
||||
}
|
||||
|
||||
template <std::size_t M>
|
||||
friend class StaticString;
|
||||
private:
|
||||
std::array< char, N + 1 > m_str{};
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "tl/expected.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
@ -13,8 +14,6 @@
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
||||
#include <alloca.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <security/pam_appl.h>
|
||||
#include <security/pam_modules.h>
|
||||
@ -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 <openssl/md5.h>
|
||||
// #include <sys/types.h>
|
||||
// #include <sys/stat.h>
|
||||
@ -103,7 +101,6 @@ static const char * application = "";
|
||||
|
||||
namespace details {
|
||||
|
||||
|
||||
std::pmr::string osName(std::pmr::memory_resource * mr) {
|
||||
memory::MonotonicStackResource< 8 * 1024 > stackResource;
|
||||
|
||||
@ -229,7 +226,7 @@ namespace conv {
|
||||
// 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);
|
||||
@ -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
|
||||
|
||||
@ -212,7 +212,9 @@ class WebSocket {
|
||||
sio::client ws;
|
||||
|
||||
public:
|
||||
WebSocket() = default;
|
||||
WebSocket(){
|
||||
ws.set_logs_quiet();
|
||||
};
|
||||
~WebSocket() = default;
|
||||
|
||||
std::shared_ptr< WebSocketSingleShotEventListener > listen() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -59,6 +59,10 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu
|
||||
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
|
||||
}
|
||||
|
||||
if(!session->config().logging){
|
||||
g_level = LogLevel::Warning;
|
||||
}
|
||||
|
||||
auto & CH = session.value().coreHandler();
|
||||
|
||||
auto selectMethod = [&](const MethodSelect & selector) { return selector.create(pam); };
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
systemToken=
|
||||
secretKey=
|
||||
userDomain=
|
||||
rublonApiServer=https://core.rublon.net
|
||||
failMode=safe
|
||||
failMode=deny
|
||||
prompt=1
|
||||
logging=true
|
||||
enablePasswdEmail=true
|
||||
autopushPrompt=false
|
||||
|
||||
4
service/01-rublon-ssh.conf.default
Normal file
4
service/01-rublon-ssh.conf.default
Normal file
@ -0,0 +1,4 @@
|
||||
UsePAM yes
|
||||
PasswordAuthentication yes
|
||||
ChallengeResponseAuthentication yes
|
||||
#KbdInteractiveAuthentication no
|
||||
@ -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
|
||||
|
||||
23
service/postrm
Normal file
23
service/postrm
Normal file
@ -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
|
||||
Loading…
Reference in New Issue
Block a user